<?php
namespace nur\sery\php\content;

use Closure;
use nur\sery\cl;
use nur\sery\php\nur_func;

/**
 * Class c: classe outil pour gérer du contenu
 */
class c {
  /** s'assure que $c est un contenu. quoter $c si c'est une chaine */
  static final function q($content): iterable {
    if (is_string($content)) return [htmlspecialchars($content)];
    elseif (is_iterable($content)) return $content;
    elseif ($content === null || $content === false) return [];
    else return [$content];
  }
  const q = [self::class, "q"];

  /** s'assure que $c est un contenu. ne jamais quoter la valeur */
  static final function nq($content): iterable {
    if (is_iterable($content)) return $content;
    elseif ($content === null || $content === false) return [];
    else return [$content];
  }
  const nq = [self::class, "nq"];

  private static function add_static_content(array &$dest, iterable $values, $key, bool $seq): void {
    $sindex = 0;
    foreach ($values as $skey => $svalue) {
      if ($skey === $sindex) {
        $sindex++;
        if ($seq) {
          $dest[] = $svalue;
        } else {
          # la première sous-clé séquentielle est ajoutée avec la clé du
          # merge statique
          $dest[$key] = $svalue;
          $seq = true;
        }
      } else {
        $dest[$skey] = $svalue;
      }
    }

  }

  /** résoudre le contenu, et retourner la liste des valeurs */
  static final function resolve($content, $object_or_class=null, bool $quote=true, ?array &$dest=null): array {
    if ($dest === null) $dest = [];
    $content = $quote? self::q($content): self::nq($content);
    $index = 0;
    foreach ($content as $key => $value) {
      if ($key === $index) {
        $index++;
        $seq = true;
      } else {
        $seq = false;
      }
      if ($value instanceof Closure) {
        # contenu dynamique: le contenu est la valeur de retour de la fonction
        # ce contenu est rajouté à la suite après avoir été quoté avec self::q()
        $func = $value;
        nur_func::ensure_func($func, $object_or_class, $args);
        $values = self::q(nur_func::call($func, ...$args));
        self::add_static_content($dest, $values, $key, $seq);
        continue;
      }
      if (is_array($value)) {
        # contenu dynamique
        if (count($value) == 0) continue;
        $func = cl::first($value);
        $args = array_slice($value, 1);
        if ($func === []) {
          # merge statique
          self::add_static_content($dest, $args, $key, $seq);
          continue;
        } else {
          # chaque argument de la fonction à appeler est aussi un contenu
          foreach ($args as &$arg) {
            $array = is_array($arg);
            $arg = self::resolve($arg, $object_or_class, false);
            if (!$array) $arg = $arg[0];
          }; unset($arg);
          if (nur_func::is_static($func)) {
            nur_func::ensure_func($func, $object_or_class, $args);
            $value = nur_func::call($func, ...$args);
          } elseif (nur_func::is_class($func)) {
            nur_func::fix_class_args($func, $args);
            $value = nur_func::cons($func, ...$args);
          } else {
            nur_func::ensure_func($func, $object_or_class, $args);
            $value = nur_func::call($func, ...$args);
          }
        }
      }
      if ($seq) $dest[] = $value;
      else $dest[$key] = $value;
    }
    return $dest;
  }
  const resolve = [self::class, "resolve"];

  private static function wend(?string $value): bool {
    return $value !== null && preg_match('/(\w|\w\.)$/', $value);
  }
  private static function startw(?string $value): bool {
    return $value !== null && preg_match('/^\w/', $value);
  }

  private static function to_values($content, ?array &$values=null): void {
    $pvalue = cl::last($values);
    $wend = self::wend($pvalue);
    foreach ($content as $value) {
      if ($value === null || $value === false) {
        continue;
      } elseif ($value instanceof IContent) {
        self::to_values($value->getContent(), $values);
        continue;
      } elseif ($value instanceof IPrintable) {
        ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
        $value->print();
        $value = ob_get_clean();
      } else {
        $value = strval($value);
        #XXX rendre paramétrable le formatage de $value
      }
      if ($value !== "") {
        $startw = self::startw($value);
        if ($wend && $startw) $values[] = " ";
        $values[] = $value;
        $wend = self::wend($value);
      }
    }
  }

  /** écrire le contenu sur la resource spécifiée, qui vaut STDOUT par défaut */
  static final function write($content, $fd=null, bool $resolve=true): void {
    if ($resolve) $content = self::resolve($content);
    $wend = false;
    foreach ($content as $value) {
      if ($value === null || $value === false) {
        continue;
      } elseif ($value instanceof IPrintable) {
        if ($fd === null) {
          $value->print();
        } else {
          ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
          $value->print();
          fwrite($fd, ob_get_clean());
        }
        $wend = false;
        continue;
      } elseif ($value instanceof IContent) {
        $values = [];
        self::to_values($content, $values);
        $value = implode("", $values);
      } else {
        $value = strval($value);
        #XXX rendre paramétrable le formattage de $value
      }
      $startw = self::startw($value);
      if (!$wend && !$startw) $value = " $value";
      if ($fd === null) echo $value;
      else fwrite($fd, $value);
      $wend = self::wend($value);
    }
  }

  /** retourner le contenu sous forme de chaine */
  static final function to_string($content, bool $resolve=true): string {
    if ($resolve) $content = self::resolve($content);
    $values = [];
    self::to_values($content, $values);
    return implode("", $values);
  }
}
