<?php
namespace nur\v\base;

use nur\A;
use nur\v\model\ILayoutManager;

abstract class AbstractLayoutManager implements ILayoutManager {
  function __construct() {
    $this->stack = [[
      "mark" => null,
      "pending" => null,
      "end" => null,
      "have_content" => false,
    ]];
    $this->index = 0;
  }

  /** @var array */
  protected $stack;

  /** @var int */
  protected $index;

  function haveContent(): bool {
    return $this->stack[$this->index]["have_content"];
  }

  function add($vs): void {
    if ($vs !== null) {
      $pending =& $this->stack[$this->index]["pending"];
      if ($pending === null) $pending = [];
      $pending[] = $vs;
    }
  }

  function flushContent(bool $haveContent=false): ?array {
    $content = null;
    foreach ($this->stack as &$item) {
      if ($haveContent) A::append_nn($content, $item["pending"]);
      $item["pending"] = null;
    }; unset($item);
    $this->stack[$this->index]["have_content"] = $haveContent;
    return $content;
  }

  function flush(bool $haveContent=false): ?array {
    $currentHaveContent = $this->haveContent();
    $content = $this->flushContent($currentHaveContent);
    if ($haveContent != $currentHaveContent) $this->flushContent($haveContent);
    return $content;
  }

  function push($mark, ?array $start=null, ?array $end=null): ?array {
    $content = $this->flush();
    A::append_nn($content, $start);
    $this->stack[] = [
      "mark" => $mark,
      "pending" => null,
      "end" => $end,
      "have_content" => false,
    ];
    $this->index++;
    return $content;
  }

  protected function haveMark($mark): bool {
    $index = $this->index;
    while ($index > 0) {
      $item = $this->stack[$index--];
      if ($item["mark"] === $mark) return true;
    }
    return false;
  }

  function pop($mark): ?array {
    if (!$this->haveMark($mark)) return null;
    $content = null;
    while ($this->index > 0) {
      $item = array_pop($this->stack);
      $this->index--;
      if ($item["have_content"]) A::append_nn($content, $item["pending"]);
      A::append_nn($content, $item["end"]);
      if ($item["mark"] === $mark) break;
    }
    return $content;
  }

  protected abstract function getRowTags($options): array;

  function startRow($options=null): ?array {
    $content = $this->flush();
    $rowTags = $this->getRowTags($options);
    A::append_nn($content, $this->push([$this, "row"], ...$rowTags));
    return $content;
  }

  function row($options=null): ?array {
    $content = $this->endRow();
    A::append_nn($content, $this->startRow($options));
    return $content;
  }

  protected abstract function getColTags($size, $options): array;

  function startCol($size, $options=null): ?array {
    $content = $this->flush();
    $colTags = $this->getColTags($size, $options);
    A::append_nn($content, $this->push([$this, "col"], ...$colTags));
    return $content;
  }

  function col($size, $options=null): ?array {
    $content = $this->endCol();
    A::append_nn($content, $this->startCol($size, $options));
    return $content;
  }

  protected abstract function getPanelTags($title, $options): array;

  function startPanel($title, $options=null): ?array {
    $content = $this->flush(true);
    $panelTags = $this->getPanelTags($title, $options);
    A::append_nn($content, $this->push([$this, "panel"], ...$panelTags));
    return $content;
  }

  function panel($title, $options=null): ?array {
    $content = $this->endPanel();
    A::append_nn($content, $this->startPanel($title, $options));
    return $content;
  }

  function endPanel(): ?array {
    return $this->pop([$this, "panel"]);
  }

  function endCol(): ?array {
    return $this->pop([$this, "col"]);
  }

  function endRow(): ?array {
    return $this->pop([$this, "row"]);
  }

  function end(): ?array {
    $content = null;
    A::append_nn($content, $this->flush());
    A::append_nn($content, $this->endPanel());
    A::append_nn($content, $this->endCol());
    A::append_nn($content, $this->endRow());
    return $content;
  }
}