<?php
namespace nur;

use ReflectionClass;
use ReflectionException;

/**
 * Class prop: des outils pour accéder aux propriétés d'un objet. la différence
 * avec {@link oprop} est qu'une tentative est effectuée pour accéder d'abord à
 * la propriété via une méthode normalisée
 *
 * @see valx
 */
class prop {
  static function split_prefix_name(string $name): array {
    preg_match('/^(_*)(.*)/', $name, $ms);
    return [$ms[1], $ms[2]];
  }

  static function get_getter_name(string $property, bool $bool=false): string {
    [$prefix, $name] = self::split_prefix_name($property);
    $get = $bool? "is": "get";
    return $prefix.$get.str::upper1(str::us2camel($name));
  }

  static function get_setter_name(string $property): string {
    [$prefix, $name] = self::split_prefix_name($property);
    return $prefix."set".str::upper1(str::us2camel($name));
  }

  static function get_deletter_name(string $property): string {
    [$prefix, $name] = self::split_prefix_name($property);
    return $prefix."del".str::upper1(str::us2camel($name));
  }

  /** obtenir la valeur d'une propriété */
  static final function get(object $object, string $property, $default=null, ?string $method=null) {
    if ($method === null) $method = self::get_getter_name($property);
    $c = new ReflectionClass($object);
    try {
      $m = $c->getMethod($method);
    } catch (ReflectionException $e) {
      return oprop::get($object, $property, $default);
    }
    return func::call([$object, $m], $default);
  }

  /** spécifier la valeur d'une propriété */
  static final function set(object $object, string $property, $value, ?string $method=null) {
    $c = new ReflectionClass($object);
    return self::_set($c, $object, $property, $value, $method);
  }

  private static function _set(ReflectionClass $c, object $object, string $property, $value, ?string $method) {
    if ($method === null) $method = self::get_setter_name($property);
    try {
      $m = $c->getMethod($method);
    } catch (ReflectionException $e) {
      return oprop::_set($c, $object, $property, $value);
    }
    func::call([$object, $m], $value);
    return $value;
  }

  /**
   * initialiser $dest avec les valeurs de $values
   *
   * les noms des clés de $values sont transformées en camelCase pour avoir les
   * noms des propriétés correspondantes
   */
  static final function set_values(object $object, ?array $values, ?array $keys=null): void {
    if ($values === null) return;
    if ($keys === null) $keys = array_keys($values);
    $c = new ReflectionClass($object);
    foreach ($keys as $key) {
      if (array_key_exists($key, $values)) {
        $property = str::us2camel($key);
        self::_set($c, $object, $property, $values[$key], null);
      }
    }
  }

  /** incrémenter la valeur d'une propriété */
  static final function inc(object $object, string $property): int {
    $value = intval(self::get($object, $property, 0));
    $value++;
    self::set($object, $property, $value);
    return $value;
  }

  /** décrémenter la valeur d'une propriété */
  static final function dec(object $object, string $property, bool $allow_negative=false): int {
    $value = intval(self::get($object, $property, 0));
    if ($allow_negative || $value > 0) {
      $value--;
      self::set($object, $property, $value);
    }
    return $value;
  }

  /**
   * Fusionner la valeur à la propriété qui est transformée en tableau si
   * nécessaire
   */
  static final function merge(object $object, string $property, $array): void {
    $values = A::with(self::get($object, $property));
    A::merge($values, A::with($array));
    self::set($object, $property, $values);
  }

  /**
   * Ajouter la valeur à la propriété qui est transformée en tableau si
   * nécessaire
   */
  static final function append(object $object, string $property, $value): void {
    $values = A::with(self::get($object, $property));
    $values[] = $value;
    self::set($object, $property, $values);
  }
}
