<?php
namespace nur\v\html5;

use nur\A;
use nur\b\ValueException;
use nur\co;
use nur\config;
use nur\func;
use nur\msg;
use nur\str;
use nur\v\base\AbstractPageContainer;
use nur\v\base\MenuManager;
use nur\v\fo;
use nur\v\ly;
use nur\v\model\IComponent;
use nur\v\model\IPage;
use nur\v\model\IPlugin;
use nur\v\navbar;
use nur\v\prefix;
use nur\v\v;

class Html5VanillaPageContainer extends AbstractPageContainer {
  protected function initToolkit(): void {
    msg::set_messenger_class(Html5Messenger::class);
    fo::set_manager_class(Html5FormManager::class);
    ly::set_manager_class(Html5LayoutManager::class);
    navbar::set_manager_class(Html5NavbarManager::class);
    navbar::set_menu_manager_class(MenuManager::class);
  }

  protected function overrideSetup(IPage $page): void {
    $plugins =& $this->config["plugins"];
    foreach ($this->components as $c) {
      if ($c instanceof IComponent) {
        self::ensure_preparec($c, true);
        self::ensure_setupc($c, true);
      }
      if ($c instanceof IPlugin) $plugins[] = $c;
    }
    # Si la page courante est un plugin, l'ajouter aussi
    if ($page instanceof IPlugin) $plugins[] = $page;
    foreach ($plugins as &$plugin) {
      if (is_string($plugin)) $plugin = new $plugin();
      elseif (is_array($plugin)) $plugin = func::cons(...$plugin);
    }; unset($plugin);
  }

  protected function overridePrint(IPage $page): void {
    if ($page->haveContent()) {
      $this->doResolveConfig();
      $this->haveOutput = true;
      $this->printStartHtml();
      $this->printStartHead();
      $this->printCssLinks();
      $this->printCss();
      $this->printJsLinks();
      $this->printJs();
      $this->printScript();
      $this->printHeadTitle();
      $this->printEndHead();
      $this->printStartBody();
      $this->printContent();
      $this->printEndBody();
      $this->printEndHtml();
    }
  }

  const JQUERY_MIN_JS = "nur-base/jquery/jquery.min.js";
  const JQUERY_JS = "nur-base/jquery/jquery.js";

  protected $haveCss = [];
  protected function setHaveCss(string $name, bool $haveCss=true): void {
    $this->haveCss[$name] = $haveCss;
  }
  protected function haveCss(string $name): bool {
    return boolval(A::get($this->haveCss, $name));
  }
  protected $haveJs = [];
  protected function setHaveJs(string $name, bool $haveJs=true): void {
    $this->haveJs[$name] = $haveJs;
  }
  protected function haveJs(string $name): bool {
    return boolval(A::get($this->haveJs, $name));
  }

  /** @var string[] */
  protected $cssUrls;
  /** @var string[] */
  protected $jsUrls;
  /** @var bool */
  protected $needsJquery;

  function doResolveConfig(): void {
    $cssUrls = $this->getCssUrls();
    $jsUrls = $this->getJsUrls();
    $this->needsJquery = config::is_fact(config::FACT_NEEDS_JQUERY);
    $plugins = $this->getPlugins();
    foreach ($plugins as $plugin) {
      if ($plugin instanceof IPlugin) {
        $this->needsJquery |= $plugin->haveJquery();

        $urls = A::with($plugin->getCss());
        if ($plugin->isDynamicCss()) {
          A::merge($cssUrls, $urls);
        } else {
          $index = 0;
          foreach ($urls as $key => $url) {
            if (!$url) continue;
            if ($key === $index) {
              if (in_array($url, $cssUrls)) continue;
              $cssUrls[] = $url;
            } else {
              if ($this->haveCss($key)) continue;
              if (in_array($url, $cssUrls)) continue;
              $cssUrls[$key] = $url;
              $this->setHaveCss($key);
            }
            $index++;
          }
        }
        $urls = A::with($plugin->getJs());
        if ($plugin->isDynamicJs()) {
          A::merge($jsUrls, $urls);
        } else {
          $index = 0;
          foreach ($urls as $key => $url) {
            if (!$url) continue;
            if (array_key_exists($key, $jsUrls)) continue;
            if ($key === $index) {
              if (in_array($url, $jsUrls)) continue;
              $jsUrls[] = $url;
            } else {
              if ($this->haveJs($key)) continue;
              if (in_array($url, $jsUrls)) continue;
              $jsUrls[$key] = $url;
              $this->setHaveJs($key);
            }
            $index++;
          }
        }
      } else {
        throw ValueException::unexpected_type(IPlugin::class, $plugin);
      }
    }
    if ($this->needsJquery && !$this->haveJs("jquery")) {
      # toujours mettre jquery en premier
      $prevJsUrls = $jsUrls;
      $jsUrls = ["jquery" => self::JQUERY_MIN_JS];
      A::merge($jsUrls, $prevJsUrls);
    }
    $this->cssUrls = $cssUrls;
    $this->jsUrls = $jsUrls;
  }

  function printStartHtml(): void {
    ?>
<!DOCTYPE html>
<html lang="fr" xmlns="http://www.w3.org/1999/xhtml">
<?php
  }

function printStartHead(): void {
  $prefix = $this->getSelfRelativePrefix();
  ?>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="<?=$prefix?>nur-base/base.css" rel="stylesheet"/>
<?php
}

  function printCssLinks(): void {
    $prefix = $this->getSelfRelativePrefix();
    foreach ($this->cssUrls as $url) {
      $url = prefix::add($url, $prefix);
      ?>
<link href="<?=$url?>" rel="stylesheet"/>
<?php
    }
  }

  function printCss(): void {
    foreach ($this->getPlugins() as $plugin) {
      $plugin->printCss();
    }
  }

  function printJsLinks(): void {
    $prefix = $this->getSelfRelativePrefix();
    foreach ($this->jsUrls as $url) {
      $url = prefix::add($url, $prefix);
      ?>
<script src="<?=$url?>" type="text/javascript"></script>
<?php
    }
  }

  function printJs(): void {
    foreach ($this->getPlugins() as $plugin) {
      $plugin->printJs();
    }
  }

  protected function beforeCapture(): void {
    ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
  }

  private static function strip_lines(array &$lines): void {
    while (count($lines) > 0 && !$lines[0]) {
      # enlever les lignes vides au début
      array_shift($lines);
    }
    while (($count = count($lines)) > 0 && !$lines[$count - 1]) {
      # enlever les lignes vides à la fin
      array_pop($lines);
    }
  }

  const RE_START_SCRIPT = '/^\s*<script\s+type="(?:text\/)?javascript">\s*(.*)/';
  const RE_END_SCRIPT = '/(.*?)\s*<\/script>\s*$/';

  private static function unwrap_script(array &$lines): void {
    $count = count($lines);
    if ($count >= 2) {
      $first = $lines[0];
      $last = $lines[$count - 1];
      if (preg_match(self::RE_START_SCRIPT, $first)
        && preg_match(self::RE_END_SCRIPT, $last)) {
        $last = preg_replace(self::RE_END_SCRIPT, '$1', $last);
        if ($last) $lines[$count - 1] = $last;
        else $lines = array_slice($lines, 0, $count - 1);
        $first = preg_replace(self::RE_START_SCRIPT, '$1', $first);
        if ($first) $lines[0] = $first;
        else $lines = array_slice($lines, 1);
      }
    }
  }

  protected $scripts = [];
  protected function captureScript(): void {
    $lines = trim(ob_get_clean());
    if ($lines) {
      $lines = str::split_nl($lines);
      self::strip_lines($lines);
      self::unwrap_script($lines);
      $this->scripts[] = implode("\n", $lines);
    }
  }

  const RE_START_JQUERY = '/^\s*jQuery(?:.noConflict\(\))?\(function\(\$\) {\s*(.*)/';
  const RE_END_JQUERY = '/(.*?)\s*}\);\s*$/';

  private static function unwrap_jquery(array &$lines): void {
    $count = count($lines);
    if ($count >= 2) {
      $first = $lines[0];
      $last = $lines[$count - 1];
      if (preg_match(self::RE_START_JQUERY, $first)
        && preg_match(self::RE_END_JQUERY, $last)) {
        $last = preg_replace(self::RE_END_JQUERY, '$1', $last);
        if ($last) $lines[$count - 1] = $last;
        else $lines = array_slice($lines, 0, $count - 1);
        $first = preg_replace(self::RE_START_JQUERY, '$1', $first);
        if ($first) $lines[0] = $first;
        else $lines = array_slice($lines, 1);
      }
    }
  }

  protected $jqueries = [];
  protected function captureJquery(): void {
    $lines = trim(ob_get_clean());
    if ($lines) {
      $lines = str::split_nl($lines);
      self::strip_lines($lines);
      self::unwrap_script($lines);
      self::unwrap_jquery($lines);
      $this->jqueries[] = implode("\n", $lines);
    }
  }

  protected function resolvePluginsScripts(): void {
    foreach ($this->getPlugins() as $plugin) {
      if ($plugin->haveScript()) {
        $this->beforeCapture();
        $plugin->printScript();
        $this->captureScript();
      }
      if ($plugin->haveJquery()) {
        $this->beforeCapture();
        $plugin->printJquery();
        $this->captureJquery();
      }
    }
  }

  protected function printMergedScripts(): void {
    $scripts = $this->scripts;
    $jqueries = $this->jqueries;
    if ($scripts || $jqueries) {
      ?>
<script type="text/javascript">
<?php
      foreach ($scripts as $script) {
        echo "$script\n";
      }
    }
    if ($jqueries) {
      ?>
jQuery.noConflict()(function($) {
<?php
      foreach ($jqueries as $jquery) {
        echo "$jquery\n";
      }
      ?>
});
<?php
    }
    if ($scripts || $jqueries) {
?>
</script>
<?php
    }
  }

  function printScript(): void {
    $this->resolvePluginsScripts();
    $this->printMergedScripts();
  }

  function printHeadTitle(): void {
    co::_print(v::tag("title", $this->getTitle()));
  }

  function printEndHead(): void {
    ?>
</head>
<?php
  }

  function printStartBody(): void {
    ?>
<body>
<?php
  }

  function printContent(): void {
    co::_write([$this->page]);
    ly::end();
  }

  function printEndBody(): void {
    ?>
</body>
<?php
  }

  function printEndHtml(): void {
    ?>
</html>
<?php
  }
}
