<?php
namespace nur\data\types;

use nur\prop;

class BoolType extends AbstractSimpleType {
  use _Tparametrable;

  const TITLE = "valeur booléenne";

  /**
   * @var bool
   *
   * par défaut, n'autoriser que deux état: true et false. remplacer par
   * true dans une classe dérivée pour avoir les 3 états true, false et null
   */
  const ALLOW_NULL = false;

  const OUINON_FORMAT = ["Oui", "Non", false];
  const OUINONNULL_FORMAT = ["Oui", "Non", ""];
  const ON_FORMAT = ["O", "N", false];
  const ONN_FORMAT = ["O", "N", ""];
  const XN_FORMAT = ["X", "", false];
  const OZ_FORMAT = ["1", "", false];
  const FORMATS = [
    "ouinon" => self::OUINON_FORMAT,
    "ouinonnull" => self::OUINONNULL_FORMAT,
    "on" => self::ON_FORMAT,
    "onn" => self::ONN_FORMAT,
    "xn" => self::XN_FORMAT,
    "oz" => self::OZ_FORMAT,
  ];

  const FORMAT = self::OUINON_FORMAT;

  /** liste de valeurs chaines à considérer comme 'OUI' */
  const YES_VALUES = array(
    # IMPORTANT: ordonner par taille décroissante pour compatibilité avec parse()
    "true", "vrai", "yes", "oui",
    "t", "v", "y", "o", "1",
  );

  /** liste de valeurs chaines à considérer comme 'NON' */
  const NO_VALUES = array(
    # IMPORTANT: ordonner par taille décroissante pour compatibilité avec parse()
    "false", "faux", "non", "no",
    "f", "n", "0",
  );

  /** Vérifier si $value est 'OUI' */
  static final function is_yes($value): bool {
    if ($value === null) return false;
    if (is_bool($value)) return $value;
    $value = strtolower(trim(strval($value)));
    if (in_array($value, self::YES_VALUES, true)) return true;
    // n'importe quelle valeur numérique
    if (preg_match('/^[0-9]+$/', $value)) return $value != 0;
    return false;
  }

  /** Vérifier si $value est 'NON' */
  static final function is_no($value): bool {
    if ($value === null) return true;
    if (is_bool($value)) return !$value;
    $value = strtolower(trim(strval($value)));
    return in_array($value, self::NO_VALUES, true);
  }

  static final function to_bool($value): bool {
    if (self::is_yes($value)) return true;
    elseif (self::is_no($value)) return false;
    else return boolval($value);
  }

  const PARAMETRABLE_PARAMS_SCHEMA = [
    "format" => [null, null, "format à appliquer"],
  ];

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

  function pp_setFormat($format): void {
    if ($format === null) $format = static::FORMAT;
    if (!is_array($format)) $format = static::FORMATS[$format];
    $this->ppFormat = $format;
  }

  function __construct(?array $params=null) {
    parent::__construct($params);
    if ($this->ppFormat === null) $this->pp_setFormat(null);
  }

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

  function isInstance($value, bool $strict=false): bool {
    return is_bool($value);
  }

  function getUndefValue() {
    return null;
  }

  function isUndef($value, $key=null): bool {
    if ($key !== null) {
      if (!is_array($value)) return $key !== 0;
      if (!array_key_exists($key, $value)) return true;
      return $value[$key] === null;
    }
    return $value === null;
  }

  function is2States(): bool {
    return true;
  }

  function get2States(): array {
    return [false, true];
  }

  function format($value): string {
    $format = $this->ppFormat;
    if ($value === null) {
      $fvalue = $format[2];
      if ($fvalue !== false) return $fvalue;
    }
    return !self::is_no($value)? $format[0]: $format[1];
  }

  function parse(string &$input) {
    if (preg_match('/^[0-9]+/', $input, $ms)) {
      $value = $ms[0];
      $length = strlen($value);
      $input = substr($input, $length);
      return $value;
    }
    foreach (self::YES_VALUES as $value) {
      $length = strlen($value);
      if (substr($input, 0, $length) === $value) {
        $input = substr($input, $length);
        return $value;
      }
    }
    foreach (self::NO_VALUES as $value) {
      $length = strlen($value);
      if (substr($input, 0, $length) === $value) {
        $input = substr($input, $length);
        return $value;
      }
    }
    return false;
  }

  function verifixConvert(&$value): bool {
    if ($value === null && $this->ppAllowNull) return true;
    $value = self::to_bool($value);
    return true;
  }

  protected function _verifix(&$value, array &$result=null): void {
    $orig = $value;
    if (!is_bool($value)) {
      if ($value === null || $value === "") $value = null;
      elseif (self::is_yes($value)) $value = true;
      elseif (self::is_no($value)) $value = false;
      else $value = boolval($value);
    }
    if ($value === null && !$this->ppAllowNull) $value = false;
    self::result_valid($result, $value, $orig);
  }

  function getGetterName(string $name): string {
    return prop::get_getter_name($name, !$this->ppAllowNull);
  }
}
