<?php
namespace nur\data\types;

use nur\md;
use nur\types;

/**
 * Class CompositeType: squelette d'implémentation d'un type complexe
 *
 * un type complexe est la composition de plusieurs types simples. il n'est pas
 * prévu pour l'analyse ou le formatage, uniquement l'utilisation de la méthode
 * {@link verifix()}
 */
abstract class AbstractComplexType extends AbstractSimpleType {
  use _Tparametrable;

  const ALLOW_NULL = true;

  const COMPONENT_SCHEMA = [
    "name" => ["string", null, "nom de la composante", "required" => true],
    "type" => ["mixed", null, "type de la composante", "required" => true],
    "title" => ["?string", null, "libellé de la composante"],
  ];

  /** @var array la définition des composantes */
  const COMPONENTS = [];

  /** @var string le séparateur entre chaque composant utilisé lors du formatage */
  const SEPARATOR = " ";

  /** @var string le pattern qui matche le séparateur entre les composants */
  const SEPARATOR_PATTERN = '/^\s+/';

  #############################################################################

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

  function setComponents(array $components): self {
    foreach ($components as $key => &$component) {
      md::ensure_schema($component, self::COMPONENT_SCHEMA, $key, false);
      $type = $component["type"];
      if (is_array($type) && count($type) == 1) {
        $type[] = $component;
      }
      $component["type"] = types::get($type);
    }; unset($component);
    $this->components = $components;
    return $this;
  }

  const PARAMETRABLE_PARAMS_SCHEMA = [
    "separator" => ["string", null, "séparateur pour le format"],
    "separator_pattern" => ["string", null, "séparateur pour l'analyse"],
  ];

  /** @var string */
  protected $ppSeparator;

  /** @var string */
  protected $ppSeparatorPattern;

  #############################################################################

  function __construct(?array $params=null) {
    $this->setComponents(static::COMPONENTS);
    $this->ppSeparator = static::SEPARATOR;
    $this->ppSeparatorPattern = static::SEPARATOR_PATTERN;
    parent::__construct($params);
  }

  function getClass(): string {
    return "string";
  }

  function canFormat(): bool {
    return false;
  }

  function canParse(): bool {
    return false;
  }

  function parse(string &$input, ?array $params=null) {
    $data = $input;
    $values = [];
    foreach ($this->components as $component) {
      $cname = $component["name"];
      # cette fonction sert uniquement à avoir la valeur dans le bon type
      /** @var IType $ctype */
      $ctype = $component["type"];
      if ($data !== "") {
        $value = $ctype->parse($data);
      } elseif ($ctype->isAllowEmpty()) {
        $value = "";
        $ctype->verifixReplaceEmpty($value);
      } else {
        $value = false;
      }
      if ($value === false) return false;
      if ($ctype instanceof AbstractSimpleType) $ctype->verifixConvert($value);
      $values[$cname] = $value;
      $data = preg_replace(static::SEPARATOR_PATTERN, "", $data);
    }
    $input = $data;
    return $values;
  }

  function verifixConvert(&$value): bool {
    $parts = [];
    foreach ($this->components as $component) {
      $cname = $component["name"];
      $ctype = $component["type"];
      $parts[] = $ctype->format($value[$cname]);
    }
    $value = implode($this->ppSeparator, $parts);
    return true;
  }
}
