<?php
namespace nur;

use nur\b\ui\IContent;
use nur\b\ui\IPrintable;

/**
 * Class c: gestion de contenu iterable, {@link IPrintable}, {@link IContent}
 *
 * @see co
 */
class c {
  /**
   * quoter $vs si c'est un scalaire non nul.
   * retourner null si $vs vaut null ou false.
   * sinon retourner le tableau tel quel
   */
  static final function qnz($vs): ?iterable {
    if ($vs === null || $vs === false) return null;
    elseif ($vs instanceof IPrintable) return [$vs];
    elseif ($vs instanceof IContent) return [$vs];
    elseif (is_iterable($vs)) return $vs;
    else return [htmlspecialchars(strval($vs))];
  }

  /**
   * quoter $vs si c'est un scalaire. sinon retourner le tableau tel quel
   *
   * NB: cette méthode est accessible via la fonction globale {@link q()}
   */
  static final function q($vs): iterable {
    if ($vs === null || $vs === false) return [];
    elseif ($vs instanceof IPrintable) return [$vs];
    elseif ($vs instanceof IContent) return [$vs];
    elseif (is_iterable($vs)) return $vs;
    else return [htmlspecialchars(strval($vs))];
  }

  /** retourner un tableau pour $vs sans le quoter si c'est un scalaire */
  static final function nq($vs): iterable {
    if ($vs === null || $vs === false) return [];
    elseif ($vs instanceof IPrintable) return [$vs];
    elseif ($vs instanceof IContent) return [$vs];
    elseif (is_iterable($vs)) return $vs;
    else return [strval($vs)];
  }

  /**
   * s'assurer que la valeur est une chaine si elle n'est pas nulle et que ce
   * n'est pas déjà une instance de {@link IPrintable} ou {@link IContent}
   */
  private static final function _strval($value) {
    if ($value === null) { #NOP
    } elseif ($value instanceof IPrintable) { #NOP
    } elseif ($value instanceof IContent) { #NOP
    } else $value = strval($value);
    return $value;
  }

  /** fusionner deux valeurs */
  private static final function _merge($pvalue, $value) {
    if (!$pvalue) {
      # prendre $value
      if (!is_iterable($value)) $value = self::_strval($value);
      return $value;
    } elseif (!$value) {
      # garder $pvalue
      return $pvalue;
    } else {
      if (!is_iterable($value)) $value = self::_strval($value);
      return array_merge(A::with($pvalue), A::with($value));
    }
  }

  /**
   * Applatir le tableau $values
   *
   * - [a, [b]] devient [a, b]
   * - [a => x, [a => y]] devient [a => [x, y]]
   *
   * si $recursive==true, alors
   * - [a => [x, [y]]] devient [a => [x, y]]
   */
  static final function flatten(?iterable $values, bool $recursive=true): array {
    $flattened = [];
    if ($values !== null) {
      $index = 0;
      foreach ($values as $key => $value) {
        if ($key === $index) {
          # valeurs séquentielles
          # elles ne sont jamais quotées
          $index++;
          if (is_iterable($value)) {
            $value = self::flatten($value);
            $index2 = 0;
            foreach ($value as $key2 => $value2) {
              if ($key2 === $index2) {
                # sequentiel
                $index2++;
                $flattened[] = $value2;
              } else {
                if (array_key_exists($key2, $flattened)) {
                  $pvalue2 = $flattened[$key2];
                  $value2 = self::_merge($pvalue2, $value2);
                }
                $flattened[$key2] = $value2;
              }
            }
          } else {
            $flattened[] = self::_strval($value);
          }
        } else {
          # valeurs associatives
          # la valeur est quotée si elle n'est pas dans un tableau
          if (array_key_exists($key, $flattened)) {
            $value = self::_merge($flattened[$key], $value);
          }
          if ($recursive && is_array($value)) {
            $value = self::flatten($value, true);
          }
          $flattened[$key] = $value;
        }
      }
    }
    return $flattened;
  }

  /** méthode de convenance pour quoter puis applatir $vs */
  static final function fq($vs): array {
    return self::flatten(self::q($vs));
  }

  /** retourner la chaine correspondant au tableau $vs applati */
  static final function string(iterable $vs): string {
    $pieces = [];
    foreach (c::flatten($vs) as $v) {
      if ($v instanceof IContent) {
        $pieces[] = self::string($v->getContent());
      } elseif ($v instanceof IPrintable) {
        ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
        $v->print();
        $pieces[] = ob_get_clean();
      } else {
        $pieces[] = $v;
      }
    }
    return implode("", $pieces);
  }
}