<?php
namespace nur\b\coll;

use nur\A;
use nur\b\IllegalAccessException;
use ReflectionMethod;

/**
 * Trait TAutomethods: support des méthodes automatiques
 */
trait TAutomethods {
  /**
   * return @array un tableau {$method => $key} qui associe des noms de getters
   * aux clés correspondantes. cette valeur est utilisée pour implémenter les
   * getters automatiques
   */
  protected static function _AUTO_GETTERS(): ?array {
    return static::_AUTO_GETTERS;
  }

  /**
   * return @array un tableau {$method => $key} qui associe des noms de setters
   * aux clés correspondantes. cette valeur est utilisée pour implémenter les
   * setters automatiques
   */
  protected static function _AUTO_SETTERS(): ?array {
    return static::_AUTO_SETTERS;
  }

  /**
   * return @array un tableau {$method => $key} qui associe des noms de deleters
   * aux clés correspondantes. cette valeur est utilisée pour implémenter les
   * deleters automatiques
   */
  protected static function _AUTO_DELETERS(): ?array {
    return static::_AUTO_DELETERS;
  }

  /**
   * return @array un tableau {$method => $key} qui associe des noms de getters
   * aux clés composites correspondantes. cette valeur est utilisée pour
   * implémenter les getters automatiques
   */
  protected static function _AUTO_CI_GETTERS(): ?array {
    return static::_AUTO_CI_GETTERS;
  }

  /**
   * return @array un tableau {$method => $key} qui associe des noms de setters
   * aux clés composites correspondantes. cette valeur est utilisée pour
   * implémenter les setters automatiques
   */
  protected static function _AUTO_CI_SETTERS(): ?array {
    return static::_AUTO_CI_SETTERS;
  }

  static function _TAutomethods__have_method(string $method): bool {
    $autoLists = [
      static::_AUTO_GETTERS,
      static::_AUTO_SETTERS,
      static::_AUTO_DELETERS,
      static::_AUTO_CI_GETTERS,
      static::_AUTO_CI_SETTERS,
    ];
    foreach ($autoLists as $method2properties) {
      if ($method2properties !== null && array_key_exists($method, $method2properties)) {
        return true;
      }
    }
    return false;
  }

  function _haveMethod(string $method): bool {
    if (method_exists($this, $method)) return true;
    return self::_TAutomethods__have_method($method);
  }

  private static function call_args(object $object, string $method, array $args) {
    $rf = new ReflectionMethod($object, $method);
    if (!$rf->isVariadic()) {
      $maxArgs = $rf->getNumberOfParameters();
      $args = array_slice($args, 0, $maxArgs);
      while (count($args) < $maxArgs) {
        $args[] = null;
      }
    }
    if (count($args) === 0) return $rf->invoke($object);
    else return $rf->invokeArgs($object, $args);
  }

  function __call(string $name, array $args) {
    $getter_keys = static::_AUTO_GETTERS();
    if ($getter_keys !== null && array_key_exists($name, $getter_keys)) {
      $key = $getter_keys[$name];
      $default = A::get($args, 0);
      return self::call_args($this, "get", [$key, $default]);
    }
    $setter_keys = static::_AUTO_SETTERS();
    if ($setter_keys !==null && array_key_exists($name, $setter_keys)) {
      $key = $setter_keys[$name];
      $value = A::get($args, 0);
      if (is_string($value)) {
        $type = $this->md()->getType($key, false);
        if ($type !== null) $value = $type->with($value);
      }
      return self::call_args($this, "set", [$key, $value]);
    }
    $deleter_keys = static::_AUTO_DELETERS();
    if ($deleter_keys !== null && array_key_exists($name, $deleter_keys)) {
      $key = $deleter_keys[$name];
      return self::call_args($this, "del", [$key]);
    }
    $ci_getter_keys = static::_AUTO_CI_GETTERS();
    if ($ci_getter_keys !== null && array_key_exists($name, $ci_getter_keys)) {
      $key = $ci_getter_keys[$name];
      return $this->md()->get($this->data, $key, null, false);
    }
    $ci_setter_keys = static::_AUTO_CI_SETTERS();
    if ($ci_setter_keys !==null && array_key_exists($name, $ci_setter_keys)) {
      $key = $ci_setter_keys[$name];
      $value = A::get($args, 0);
      $this->md()->set($this->data, $key, $value);
      return null;
    }
    throw IllegalAccessException::not_implemented($name);
  }
}