<?php
namespace nur\data\types;

use Exception;
use nur\b\IllegalAccessException;
use nur\b\ValueException;

/**
 * Interface IType: un format d'affichage et de saisie pour un type de données
 */
interface IType {
  /**
   * @return string la classe des objets gérés par ce format: le type attendu
   * par {@link format()} et le type retourné par {@link verifix()}
   *
   * Les valeurs "mixed", "bool", "float", "int", "string" et "array" peuvent
   * aussi être retournées, bien qu'elles ne soient pas à proprement parler des
   * classes.
   *
   * La valeur "mixed" signifie qu'il peut s'agir de n'importe quelle classe
   * et/ou que la valeur ne peut pas être déterminée à l'avance.
   */
  function getClass(): string;

  /**
   * comme {@link getClass()} mais peut être utilisé directement comme type dans
   * une déclaration PHP. notamment, si le type est nullable et que
   * $allowNullable==true, il y a "?" devant le nom.
   *
   * Par exemple:
   * - pour un type chaine nullable, {@link getClass()} retournerait "string"
   * alors que cette méthode retournerait "?string".
   * - pour un type mixed, {@link getClass()} retournerait "mixed" alors que
   * cette méthode retournerait null
   */
  function getPhpType(bool $allowNullable=true): ?string;

  /**
   * vérifier si $value est de ce type
   *
   * si {@link getClass()} retourne "string" ou "array", on ne se contente pas
   * de vérifier que la donnée est au bon type: on vérifie aussi si le format de
   * la donnée est correct
   */
  function isInstance($value): bool;

  /**
   * obtenir la valeur correspondant à "non-existant"
   *
   * si la valeur retournée est null, ce n'est peut-être pas la valeur undef. la
   * seule façon de le savoir est d'appeler {@link isUndef()}
   */
  function getUndefValue();

  /**
   * vérifier si la valeur existe
   *
   * - si $key===null, alors indiquer si $value n'a pas la valeur "non-existant"
   * si cela a du sens pour ce type
   * - si $key!==null, alors indiquer si $value[$key] existe ou s'il n'a pas la
   * valeur "non-existant"
   *
   * d'une manière générale, "false" correspond à "non-existant", mais il peut
   * y avoir des exceptions d'où l'existence de cette méthode
   */
  function isUndef($value, $key=null): bool;

  /**
   * indiquer si c'est le type d'une valeur qui ne peut prendre que 2 états: une
   * "vraie" et une "fausse"
   */
  function is2States(): bool;

  /**
   * Si {@link is2States()} est vrai, retourner les deux valeurs [faux, vrai]
   */
  function get2States(): array;

  /**
   * indiquer si c'est le type d'une valeur qui ne peut prendre que 3 états: une
   * "vraie", une "fausse", et une "indéterminée"
   */
  function is3States(): bool;

  /**
   * Si {@link is3States()} est vrai, retourner les 3 valeurs [faux, vrai, undef]
   */
  function get3States(): array;

  /**
   * cet objet est-il capable de formatter des valeurs pour affichage? sinon,
   * la méthode format() lance l'exception {@link IllegalAccessException}
   *
   * @return bool true si les valeurs de ce type peuvent être formattées pour
   * affichage
   */
  function canFormat(): bool;

  /**
   * formatter $value pour affichage.
   *
   * $value peut valoir null ou false si ces valeurs sont autorisées. sinon,
   * elle doit être du bon type (c'est à dire que {@link isInstance()} doit
   * retourner true). sinon le comportement n'est pas défini
   */
  function format($value): string;

  /**
   * cet objet est-il capable d'analyser des valeurs chaines? sinon, la méthode
   * {@link parse()} ne fait pas partie de l'API publique de cet objet. elle ne
   * sera alors généralement pas implémentée
   *
   * @return bool true si cet objet est capable d'analyser une valeur chaine
   * fournie par l'utilisateur
   */
  function canParse(): bool;

  /**
   * analyser $input:
   * - si elle commence par une valeur de ce type, retourner la chaine pour
   * traitement par {@link verifix()}. $input est mis à jour pour pointer sur la
   * suite de la chaine, après la valeur analysée
   * - sinon, retourner false. dans ce cas, $input n'est pas modifié
   *
   * NB: une implémentation peut décider de retourner autre chose qu'une chaine
   * mais dans ce cas, il faut documenter le format de la valeur de retour, et
   * probablement que {@link canParse()} doit retourner false
   *
   * @param string $input la chaine à analyser
   * @return mixed|bool la partie de la chaine à traiter avec {@link verifix()}
   * @throws IllegalAccessException si {@link canParse()} retourne false
   */
  function parse(string &$input);

  /** avant d'analyser une chaine, faut-il la trimer? */
  function isTrim(): bool;

  /**
   * la chaine vide est-elle autorisée?
   *
   * si une chaine est fournie et que la chaine vide n'est pas autorisée, la
   * méthode {@link verifixReplaceEmpty()} est appelée pour la remplacer par une
   * autre valeur. par défaut, elle est remplacée par null.
   */
  function isAllowEmpty(): bool;

  /**
   * fonction de support: corriger la valeur "" le cas échéant
   *
   * par défaut, remplacer "" par null si null est autorisé
   */
  function verifixReplaceEmpty(&$value): void;

  /**
   * la valeur false est-elle autorisée?
   *
   * si false n'est pas autorisée, la fonction {@link verifixReplaceFalse()}
   * est appelée pour la remplacer par une autre valeur. par défaut, elle est
   * remplacée par null.
   */
  function isAllowFalse(): bool;

  /**
   * fonction de support: corriger la valeur false le cas échéant
   *
   * par défaut, remplacer false par null si false n'est pas autorisé.
   */
  function verifixReplaceFalse(&$value): void;

  /**
   * la valeur null est-elle autorisée?
   *
   * si null n'est pas autorisée, la fonction {@link verifixReplaceNull()}
   * est appelée pour la remplacer par une autre valeur. par défaut, aucun
   * remplacement n'est fait, ce qui provoque donc une erreur
   */
  function isAllowNull(): bool;

  /** fonction de support: corriger la valeur null le cas échéant */
  function verifixReplaceNull(&$value): void;

  const VERIFIX_RESULT_SCHEMA = [
    "valid" => [null, false, "est-ce que la valeur fournie est valide?"],
    "value" => [null, null, "valeur corrigée si elle est valide, valeur originale si elle est invalide"],
    "error" => [null, false, "message d'erreur si la valeur fournie est invalide"],
    "exception" => [null, null, "exception associée à l'erreur le cas échaéant"],
    "orig" => [null, null, "valeur originale avant correction"],
    "parsed" => [null, false, "partie de la chaine originale qui a pu être analysée si valid==false"],
    "remains" => [null, false, "partie de la chaine originale non analysée"],
  ];

  /**
   * vérifier $value qui a été fourni par l'utilisateur:
   * - si elle déjà du bon type et au bon format, la laisser en l'état
   * - si elle est du bon type mais pas au bon format, elle est normalisée
   * - sinon, ce doit être une chaine de caractère, qui est analysée et doit
   * correspondre entièrement (sauf si l'implémentation autorise que l'analyse
   * soit incomplète)
   *
   * si la valeur est invalide elle est laissée inchangée. dans tous les cas,
   * $result est mis à jour avec les clés suivantes:
   * - "valid" => true ou false, indique si la valeur est valide
   * - "value" => valeur normalisée si elle est valide
   * - "error" => message d'erreur si la valeur est invalide, ou false si la
   * valeur est valide
   * - "exception" => si la valeur est invalide, exception éventuellement
   * associée à l'erreur
   * - "orig" => valeur originale de $value
   * - "parsed" => partie de la chaine originale qui a pu être analysée si
   * la valeur est invalide
   * - "remains" => le reste non analysé de la chaine si l'implémentation
   * autorise que la chaine en entrée ne soit pas analysée entièrement ou si
   * la valeur est invalide
   *
   * si plusieurs messages d'erreur sont possibles, $params["messages"] permet
   * de spécifier des messages différents des valeurs par défaut. les clés
   * valides sont documentées dans l'implémentation
   *
   * @param mixed &$value la valeur à analyser et normaliser
   * @param array $result le résultat de l'analyse
   * @param bool $throw faut-il lancer une exception si la valeur est invalide?
   * @return bool si la valeur est valide
   * @throws IllegalAccessException si {@link canParse()} retourne false
   * @throws Exception si la valeur est invalide, que $throw==true et que le
   * champ exception de $result a été renseigné
   * @throws ValueException si la valeur est invalide, que $throw==true et que
   * le champ exception de $result n'a pas été renseigné
   */
  function verifix(&$value, array &$result=null, bool $throw=false): bool;

  /**
   * Méthode de convenance pour retourner la valeur normalisée et lancer une
   * exception en cas d'erreur
   */
  function with($value);

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

  /** @return string le nom d'un getter pour une valeur de ce type */
  function getGetterName(string $name): string;

  /** @return string le nom d'un setter pour une valeur de ce type */
  function getSetterName(string $name): string;

  /** @return string le nom d'un deleter pour une valeur de ce type */
  function getDeleterName(string $name): string;

  /**
   * @return string le nom d'une constante de classe pour une valeur de ce type
   */
  function getClassConstName(string $name): string;

  /**
   * @return string le nom d'une propriété d'une classe pour une valeur de ce
   * type
   */
  function getObjectPropertyName(string $name): string;

  /** @return string le nom d'une clé d'un tableau pour une valeur de ce type */
  function getArrayKeyName(string $name): string;
}
