<?php
namespace nulib\ldap;


/**
 * Class CompositeValue: une valeur composite
 */
abstract class CompositeValue extends BaseArray {
  /** @var array schéma des champs de la valeur composite */
  const SCHEMA = null;

  /** @var array syntaxes associées aux champs */
  const SYNTAXES = null;
  
  /** @var array liste et ordre des éléments obligatoires */
  const MANDATORY_KEYS = null;
  
  /** @var array liste et ordre des éléments facultatifs connus */
  const OPTIONAL_KEYS = null;

  /** @var array liste des clés qui identifient cet objet */
  const KEY_KEYS = null;

  static function compute_keys(array $values): string {
    $keys = static::KEY_KEYS;
    if ($keys === null) $keys = static::MANDATORY_KEYS;
    if ($keys === null) $keys = array_keys($values);
    $parts = [];
    foreach ($keys as $key) {
      $parts[] = A::get($values, $key);
    }
    return implode("-", $parts);
  }

  protected $ldapKeys, $keys, $optionalKeys;

  protected $syntaxes;

  /** initialiser l'objet */
  function setup(LdapConn $conn): self {
    $ldapKeys = [];
    $keys = [];
    $mandatoryKeys = ValueException::check_nn(static::MANDATORY_KEYS
      , "Vous devez définir MANDATORY_KEYS");
    $index = 0;
    foreach ($mandatoryKeys as $key => $ldapKey) {
      if ($key === $index) {
        $index++;
        $key = $ldapKey;
      }
      $ldapKeys[$key] = $ldapKey;
      $keys[$ldapKey] = $key;
    }
    $optionalKeys = [];
    $index = 0;
    foreach (A::with(static::OPTIONAL_KEYS) as $key => $ldapKey) {
      if ($key === $index) {
        $index++;
        $key = $ldapKey;
      }
      $ldapKeys[$key] = $ldapKey;
      $keys[$ldapKey] = $key;
      $optionalKeys[] = $key;
    }
    $schemaKeys = A::keys(static::SCHEMA);
    foreach ($schemaKeys as $key) {
      if (!in_array($key, $keys)) {
        $ldapKeys[$key] = $key;
        $keys[$key] = $key;
        $optionalKeys[] = $key;
      }
    }
    $this->ldapKeys = $ldapKeys;
    $this->keys = $keys;
    $this->optionalKeys = $optionalKeys;
    ##
    $syntaxClasses = static::SYNTAXES;
    if ($syntaxClasses !== null) {
      $syntaxes = [];
      foreach ($schemaKeys as $key) {
        $class = A::get($syntaxClasses, $key);
        if ($class !== null) {
          $syntaxes[$key] = $conn->getSyntax($class);
        }
      }
      $this->syntaxes = $syntaxes;
    }
    ##
    return $this;
  }

  function has($key): bool { return $this->_has($key); }
  function &get($key, $default=null) { return $this->_get($key, $default); }
  function set($key, $value): self { return $this->_set($key, $value); }
  function add($value): self { return $this->_set(null, $value); }
  function del($key): self { return $this->_del($key); }

  /** obtenir la clé qui identifie cet objet */
  function getKey(): string {
    return self::compute_keys($this->data);
  }

  /** initialiser cet objet avec une valeur LDAP */
  function parseLdap(string $value): self {
    if (!preg_match_all('/\[.*?]/', $value, $ms)) {
      throw ValueException::invalid_value($value, "composite value");
    }
    $this->data = [];
    foreach ($ms[0] as $nameValue) {
      if (preg_match('/\[(.*?)=(.*)]/', $nameValue, $ms)) {
        $ldapKey = names::ldap_unescape($ms[1]);
        $key = A::get($this->keys, $ldapKey, $ldapKey);
        $value = names::ldap_unescape($ms[2]);
        /** @var AbstractSyntax $syntax */
        $syntax = A::get($this->syntaxes, $key);
        if ($syntax !== null) $value = $syntax->ldap2php($value);
        $this->data[$key] = $value;
      }
    }
    return $this;
  }

  /** retourner cette valeur au format LDAP */
  function formatLdap(): string {
    $optionalKeys = $this->optionalKeys;
    $parts = [];
    foreach ($this->ldapKeys as $key => $ldapKey) {
      $value = A::get($this->data, $key);
      if ($value === null && in_array($key, $optionalKeys)) continue;
      /** @var AbstractSyntax $syntax */
      $syntax = A::get($this->syntaxes, $key);
      if ($syntax !== null) $value = $syntax->php2ldap($value);
      $ldapKey = ldap_escape($ldapKey, 0, LDAP_ESCAPE_FILTER);
      $value = ldap_escape($value, 0, LDAP_ESCAPE_FILTER);
      $parts[] = "[$ldapKey=$value]";
    }
    return implode("", $parts);
  }

  function reset(?array $values): CompositeValue {
    $md = Metadata::with(static::SCHEMA);
    $md->ensureSchema($values);
    $this->data = $values;
    return $this;
  }

  #############################################################################
  static function _AUTOGEN_PROPERTIES(): array {
    return cvalues::autogen_properties(static::SCHEMA);
  }
  ## rajouter ceci dans les classes dérivées
  #const _AUTOGEN_PROPERTIES = [[self::class, "_AUTOGEN_PROPERTIES"]];
}
