<?php
namespace nur\sery\php;

use nur\sery\cl;
use nur\sery\str;
use ReflectionClass;
use ReflectionException;

/**
 * Class oprop: accéder aux propriétés d'un objet
 *
 * @see valx
 */
class oprop {
  /** obtenir la valeur d'une propriété */
  static final function get(object $object, string $property, $default=null) {
    $c = new ReflectionClass($object);
    try {
      $p = $c->getProperty($property);
      $p->setAccessible(true);
      return $p->getValue($object);
    } catch (ReflectionException $e) {
      if (property_exists($object, $property)) return $object->$property;
      else return $default;
    }
  }

  static final function _set(ReflectionClass $c, object $object, string $property, $value) {
    try {
      $p = $c->getProperty($property);
      $p->setAccessible(true);
      $p->setValue($object, $value);
    } catch (ReflectionException $e) {
      $object->$property = $value;
    }
    return $value;
  }

  /** spécifier la valeur d'une propriété */
  static final function set(object $object, string $property, $value) {
    $c = new ReflectionClass($object);
    return self::_set($c, $object, $property, $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]);
      }
    }
  }

  /** incrémenter la valeur d'une propriété */
  static final function inc(object $object, string $property): int {
    $c = new ReflectionClass($object);
    try {
      $p = $c->getProperty($property);
      $p->setAccessible(true);
      $value = (int)$p->getValue($object);
      $value++;
      $p->setValue($object, $value);
    } catch (ReflectionException $e) {
      if (property_exists($object, $property)) {
        $value = (int)$object->$property;
        $value++;
      } else {
        $value = 1;
      }
      $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 {
    $c = new ReflectionClass($object);
    try {
      $p = $c->getProperty($property);
      $p->setAccessible(true);
      $value = (int)$p->getValue($object);
      if ($allow_negative || $value > 0) {
        $value --;
        $p->setValue($object, $value);
      }
    } catch (ReflectionException $e) {
      if (property_exists($object, $property)) {
        $value = (int)$object->$property;
      } else {
        $value = 0;
      }
      if ($allow_negative || $value > 0) $value--;
      $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 {
    $c = new ReflectionClass($object);
    try {
      $p = $c->getProperty($property);
      $p->setAccessible(true);
      $values = cl::with($p->getValue($object));
      $values = cl::merge($values, cl::with($array));
      $p->setValue($object, $values);
    } catch (ReflectionException $e) {
      if (property_exists($object, $property)) {
        $values = cl::with($object->$property);
      } else {
        $values = [];
      }
      $values = cl::merge($values, cl::with($array));
      $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 {
    $c = new ReflectionClass($object);
    try {
      $p = $c->getProperty($property);
      $p->setAccessible(true);
      $values = cl::with($p->getValue($object));
      $values[] = $value;
      $p->setValue($object, $values);
    } catch (ReflectionException $e) {
      if (property_exists($object, $property)) {
        $values = cl::with($object->$property);
      } else {
        $values = [];
      }
      $values[] = $value;
      $object->$property = $values;
    }
  }
}
