<?php
namespace nur\php;

use nur\A;

class SrcGenerator {
  const INDENT = "  ";

  function __construct(string $indent="") {
    $this->indent = $indent;
    $this->lines = [];
  }

  /** @var string */
  private $indent;

  function indent(): self {
    $this->indent .= self::INDENT;
    return $this;
  }

  function dedent(): self {
    $this->indent = substr($this->indent, strlen(self::INDENT));
    return $this;
  }

  private $lines;

  function getLines(): array {
    return $this->lines;
  }

  private static function export($value): string {
    if ($value === null) $value = "null";
    else $value = var_export($value, true);
    return $value;
  }

  private static function check_literals(&$value, ?array $literals): bool {
    if ($literals === null) return false;
    foreach ($literals as [$lfrom, $lto]) {
      if ($value === $lfrom) {
        $value = $lto;
        return true;
      }
    }
    return false;
  }

  private static function is_scalar_array(array $array, bool $check_size=true): bool {
    if (!A::is_seq($array)) return false;
    $scalar_array = true;
    foreach ($array as $value) {
      if (is_array($value)) {
        $scalar_array = false;
        break;
      }
    }
    if ($scalar_array && $check_size) {
      # vérifier si tout le tableau tient sur 80 caractères
      # si le tableau fait plus de 80 caractères, considérer qu'il n'est pas
      # scalaire afin que chaque élément soit affiché sur une ligne à part
      $size = 2; # pour '[]'
      $first = true;
      foreach ($array as $value) {
        $size += strlen(self::export($value));
        if ($first) $first = false;
        else $size += 2; # pour ', '
      }
      $scalar_array = $size <= 80;
    }
    return $scalar_array;
  }

  private function addScalarArrayItems(array $array, ?string $prefix=null, ?string $suffix=null, ?array $literals=null): void {
    $parts = [];
    $parts[] = $prefix;
    $parts[] = "[";
    $first = true;
    foreach ($array as $value) {
      if ($first) $first = false;
      else $parts[] = ", ";
      if (!self::check_literals($value, $literals)) $value = self::export($value);
      $parts[] = $value;
    }
    $parts[] = "]";
    $parts[] = $suffix;
    $this->lines[] = implode("", $parts);
  }

  private function addGenericArrayItems(array $values, ?string $prefix=null, ?string $suffix=null, ?array $literals=null): void {
    $this->lines[] = $prefix."[";
    $this->indent();
    $this->addArrayItems($values, $literals);
    $this->dedent();
    $this->lines[] = $this->indent."]".$suffix;
  }

  private function addArrayItems(array $values, ?array $literals=null): void {
    $seq = A::is_seq($values);
    $index = 0;
    foreach ($values as $key => $value) {
      $prefix = $this->indent;
      if (!$seq && $key !== $index) $prefix .= self::export($key)." => ";
      if ($key === $index) $index++;
      if (self::check_literals($value, $literals)) {
        $this->lines[] = $prefix.$value.",";
      } elseif (is_array($value)) {
        if (self::is_scalar_array($value)) {
          # si value ne contient que des valeurs scalaires, tout mettre sur une
          # même ligne
          $this->addScalarArrayItems($value, $prefix, ",", $literals);
        } else {
          $this->addGenericArrayItems($value, $prefix, ",", $literals);
        }
      } else {
        $this->lines[] = $prefix.self::export($value).",";
      }
    }
  }

  function addValue($value, ?string $prefix=null, ?string $suffix=null, ?array $literals=null): self {
    if (self::check_literals($value, $literals)) {
      $this->lines[] = $prefix.$value.$suffix;
    } elseif (is_array($value)) {
      if (self::is_scalar_array($value)) {
        # si value ne contient que des valeurs scalaires, tout mettre sur une
        # même ligne
        $this->addScalarArrayItems($value, $prefix, $suffix, $literals);
      } else {
        $this->addGenericArrayItems($value, $prefix, $suffix, $literals);
      }
    } else {
      $this->lines[] = $prefix.self::export($value).$suffix;
    }
    return $this;
  }

  function addLiteral($value, ?string $prefix=null, ?string $suffix=null): self {
    $this->lines[] = $prefix.$value.$suffix;
    return $this;
  }

  function genSof(): self {
    $this->lines[] = "<?php # -*- coding: utf-8 mode: php -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8";
    return $this;
  }

  function genConst(string $name, $value, ?string $prefix=null, ?array $literals=null, bool $literalValue=false, ?string $valuePrefix=null): self {
    $prefix = "$this->indent${prefix}const $name = ${valuePrefix}";
    if ($literalValue) return $this->addLiteral($value, $prefix, ";");
    else return $this->addValue($value, $prefix, ";", $literals);
  }

  function genVar(string $name, $value, ?string $prefix=null, ?array $literals=null, bool $literalValue=false, ?string $valuePrefix=null): self {
    $prefix = "$this->indent${prefix}\$$name = ${valuePrefix}";
    if ($literalValue) return $this->addLiteral($value, $prefix, ";");
    else return $this->addValue($value, $prefix, ";", $literals);
  }

  function genReturn($value, ?string $prefix=null, ?array $literals=null, bool $literalValue=false, ?string $valuePrefix=null): self {
    $prefix = "$this->indent${prefix}return ${valuePrefix}";
    if ($literalValue) return $this->addLiteral($value, $prefix, ";");
    else return $this->addValue($value, $prefix, ";", $literals);
  }

  function genLiteral($line, ?string $prefix=null, ?string $suffix=null): self {
    $prefix = "$this->indent$prefix";
    return $this->addLiteral($line, $prefix, $suffix);
  }

  /** fusionner les lignes générées avec $lines */
  function mergeInto(array &$lines, bool $reset=true) {
    A::merge($lines, $this->lines);
    if ($reset) $this->lines = [];
  }
}