<?php
namespace nur\b\params;

use nur\A;
use nur\b\ValueException;
use nur\data\types\Metadata;
use nur\md;
use nur\php\Autogen;
use nur\prop;
use nur\str;

class parametrable_utils {
  private static function get_pp_property(string $key): string {
    return str::us2camel("pp_$key");
  }

  private static function get_pp_setter(string $key, ?Metadata $md=null) {
    if ($md !== null) return "pp_".$md->getType($key)->getSetterName($key);
    else return "pp_".prop::get_setter_name($key);
  }

  private static function get_getter(string $key, ?Metadata $md=null) {
    if ($md !== null) return $md->getType($key)->getGetterName($key);
    else return prop::get_getter_name($key);
  }

  private static function get_setter(string $key, ?Metadata $md=null) {
    if ($md !== null) return $md->getType($key)->getSetterName($key);
    else return prop::get_setter_name($key);
  }

  static function set_param(object $parametrable, string $key, $value, ?Metadata $md=null): void {
    if ($md !== null) {
      $property = self::get_pp_property($key);
      $setter = self::get_pp_setter($key, $md);
      prop::set($parametrable, $property, $value, $setter);
    } else {
      $property = self::get_pp_property($key);
      $setter = self::get_pp_setter($key);
      prop::set($parametrable, $property, $value, $setter);
    }
  }

  static function set_defaults(?array $parametrables, IParametrable $defaultParametrable, $schema): void {
    if ($schema === null) return;
    if ($schema instanceof Metadata) {
      $sfields = $schema->getSfields();
      foreach ($sfields as $key => $sfield) {
        $default = $sfield["default"];
        if ($default !== null) {
          $parametrable = A::get($parametrables, $key, $defaultParametrable);
          self::set_param($parametrable, $key, $default, $schema);
        }
      }
    } else {
      md::normalize_schema($schema);
      foreach ($schema as $key => $sfield) {
        $default = $sfield["default"];
        if ($default !== null) {
          $parametrable = A::get($parametrables, $key, $defaultParametrable);
          self::set_param($parametrable, $key, $default);
        }
      }
    }
  }

  static function set_params(?array $parametables, IParametrable $defaultParametrable, ?array $params, $schema): array {
    if ($params === null || $schema === null) return [];
    $modifiedKeys = [];
    if ($schema instanceof Metadata) {
      foreach ($schema->getKeys() as $key) {
        if ($schema->has($params, $key)) {
          $parametrable = A::get($parametables, $key, $defaultParametrable);
          $value = $schema->get($params, $key);
          self::set_param($parametrable, $key, $value, $schema);
          $modifiedKeys[] = $key;
        }
      }
    } else {
      $indexes = md::normalize_schema($schema);
      foreach (array_keys($schema) as $key) {
        if (md::_has($params, $key, $schema, $indexes)) {
          $parametrable = A::get($parametables, $key, $defaultParametrable);
          $value = md::_get($params, $key, $schema, null, true, $indexes);
          self::set_param($parametrable, $key, $value);
          $modifiedKeys[] = $key;
        }
      }
    }
    return $modifiedKeys;
  }

  /** générer une liste de nom de méthodes pour les clés du schéma */
  static function _auto_getters($schema, ?string $class=null): array {
    $md = Metadata::with($schema);
    $class_methods = get_class_methods($class);
    $auto_setters = [];
    foreach ($md->getKeys() as $key) {
      $ppSetter = self::get_pp_setter($key, $md);
      if ($class_methods !== null && in_array($ppSetter, $class_methods)) {
        # ne pas générer de getter s'il existe un ppSetter
        continue;
      }
      $method = self::get_getter($key, $md);
      $auto_setters[$method] = $key;
    }
    return $auto_setters;
  }

  /** générer une liste de nom de méthodes pour les clés du schéma */
  static function _auto_setters($schema): array {
    $md = Metadata::with($schema);
    $auto_setters = [];
    foreach ($md->getKeys() as $key) {
      $method = self::get_setter($key, $md);
      $auto_setters[$method] = $key;
    }
    return $auto_setters;
  }

  /** générer une liste de signatures de méthodes getters pour les clés du schéma */
  static function _autogen_methods_getters($schema, ?string $class=null): array {
    $md = Metadata::with($schema);
    $class_methods = get_class_methods($class);
    $auto_getters = self::_auto_getters($md, $class);
    $methods = [];
    foreach ($auto_getters as $method => $key) {
      if ($class_methods !== null && in_array($method, $class_methods)) continue;
      $property = self::get_pp_property($key);
      if (!property_exists($class, $property)) continue;
      [$phpType, $returnType] = Autogen::fix_type($md->getType($key)->getPhpType());
      $method .= "()";
      if ($phpType !== null) $method = "$returnType $method";
      $methods[] = $method;
    }
    return $methods;
  }

  /** générer une liste de signatures de méthodes setters pour les clés du schéma */
  static function _autogen_methods_setters($schema, ?string $class=null): array {
    $md = Metadata::with($schema);
    $class_methods = get_class_methods($class);
    $auto_setters = self::_auto_setters($md);
    $methods = [];
    foreach ($auto_setters as $method => $key) {
      if ($class_methods !== null && in_array($method, $class_methods)) continue;
      [$phpType, $returnType] = Autogen::fix_type($md->getType($key)->getPhpType());
      if ($phpType !== null) $method .= "($phpType \$value)";
      else $method .= "(\$value)";
      if ($phpType !== null) $method = "$returnType $method";
      $methods[] = $method;
    }
    return $methods;
  }

  /** vérifier si le nom de la méthode spécifiée est une méthode automatique */
  static function should_call(string $name, ?array $auto_getters, ?array $auto_setters, $schema): bool {
    $md = Metadata::with($schema);

    if ($auto_setters === null) $auto_setters = self::_auto_setters($schema);
    $key = A::get($auto_setters, $name);
    if ($key !== null && $md->isKey($key)) return true;

    if ($auto_getters === null) $auto_getters = self::_auto_getters($schema);
    $key = A::get($auto_getters, $name);
    if ($key !== null && $md->isKey($key)) return true;

    return false;
  }

  /** appeler la méthode automatique spécifiée pour mettre à jour un paramètre */
  static function call(IParametrable $parametrable, string $name, array $args, ?array $auto_getters, ?array $auto_setters, $schema) {
    if ($auto_setters === null) $auto_setters = self::_auto_setters($schema);
    $key = A::get($auto_setters, $name);
    if ($key !== null) {
      $schema = Metadata::with($schema);
      $value = $schema->getType($key)->with(A::first($args));
      $property = self::get_pp_property($key);
      $setter = self::get_pp_setter($key, $schema);
      $oldValue = prop::get($parametrable, $property);
      prop::set($parametrable, $property, $value, $setter);
      return $oldValue;
    }
    if ($auto_getters === null) $auto_getters = self::_auto_getters($schema);
    $key = A::get($auto_getters, $name);
    if ($key !== null) {
      $property = self::get_pp_property($key);
      return prop::get($parametrable, $property);
    }
    throw ValueException::invalid_value($name);
  }

  static function _autogen_literals($schema, ?string $class=null, bool $dynamicSchema=false): array {
    $literals = [
      [self::class, '\\nur\\b\\params\\parametrable_utils::class'],
    ];
    if ($class !== null) $literals[] = [$class, "self::class"];
    if (!$dynamicSchema) {
      $literals[] = [$schema, "self::PARAMETRABLE_PARAMS_SCHEMA"];
    }
    return $literals;
  }
  static function _autogen_methods($schema, ?string $class=null): array {
    return [
      [self::class, "_autogen_methods_getters", $schema, $class],
      [self::class, "_autogen_methods_setters", $schema, $class],
    ];
  }
  static function _AUTOGEN_CONSTS($schema, ?string $class=null, bool $dynamicSchema=false): array {
    return [
      "_AUTOGEN_LITERALS" => self::_autogen_literals($schema, $class, $dynamicSchema),
      "_AUTOGEN_METHODS" => self::_autogen_methods($schema, $class),
      "_AUTO_GETTERS" => self::_auto_getters($schema, $class),
      "_AUTO_SETTERS" => self::_auto_setters($schema),
    ];
   }
}