<?php
namespace nur\sery;

use Traversable;

/**
 * Class A: gestion de tableaux ou d'instances de {@link IArrayWrapper}
 *
 * contrairement à {@link cl}, les méthodes de cette classes sont plutôt conçues
 * pour modifier le tableau en place
 */
class A {
  /**
   * s'assurer que $array est un array non null. retourner true si $array n'a
   * pas été modifié (s'il était déjà un array), false sinon.
   */
  static final function ensure_array(&$array): bool {
    if (is_array($array)) return true;
    if ($array instanceof IArrayWrapper) $array = $array->wrappedArray();
    if ($array === null || $array === false) $array = [];
    elseif ($array instanceof Traversable) $array =  cl::all($array);
    else $array = [$array];
    return false;
  }

  /**
   * s'assurer que $array est un array s'il est non null. retourner true si
   * $array n'a pas été modifié (s'il était déjà un array ou s'il valait null).
   */
  static final function ensure_narray(&$array): bool {
    if ($array instanceof IArrayWrapper) $array = $array->wrappedArray();
    if ($array === null || is_array($array)) return true;
    if ($array === false) $array = [];
    elseif ($array instanceof Traversable) $array = cl::all($array);
    else $array = [$array];
    return false;
  }

  /**
   * s'assurer que $array est un tableau de $size éléments, en complétant avec
   * des occurrences de $default si nécessaire
   *
   * @return bool true si le tableau a été modifié, false sinon
   */
  static final function ensure_size(?array &$array, int $size, $default=null): bool {
    $modified = false;
    if ($array === null) {
      $array = [];
      $modified = true;
    }
    if ($size < 0) return $modified;
    $count = count($array);
    if ($count == $size) return $modified;
    if ($count < $size) {
      # agrandir le tableau
      while ($count++ < $size) {
        $array[] = $default;
      }
      return true;
    }
    # rétrécir le tableau
    $tmparray = [];
    foreach ($array as $key => $value) {
      if ($size-- == 0) break;
      $tmparray[$key] = $value;
    }
    $array = $tmparray;
    return true;
  }

  static function merge(&$dest, ...$merges): void {
    self::ensure_narray($dest);
    $dest = cl::merge($dest, ...$merges);
  }

  static function merge2(&$dest, ...$merges): void {
    self::ensure_narray($dest);
    $dest = cl::merge2($dest, ...$merges);
  }

  static final function select(&$dest, ?array $mappings, bool $inverse=false): void {
    self::ensure_narray($dest);
    $dest = cl::select($dest, $mappings, $inverse);
  }

  static final function selectm(&$dest, ?array $mappings, ?array $merge=null): void {
    self::ensure_narray($dest);
    $dest = cl::selectm($dest, $mappings, $merge);
  }

  static final function mselect(&$dest, ?array $merge, ?array $mappings): void {
    self::ensure_narray($dest);
    $dest = cl::mselect($dest, $merge, $mappings);
  }

  static final function pselect(&$dest, ?array $pkeys): void {
    self::ensure_narray($dest);
    $dest = cl::pselect($dest, $pkeys);
  }

  static final function pselectm(&$dest, ?array $pkeys, ?array $merge=null): void {
    self::ensure_narray($dest);
    $dest = cl::pselectm($dest, $pkeys, $merge);
  }

  static final function mpselect(&$dest, ?array $merge, ?array $pkeys): void {
    self::ensure_narray($dest);
    $dest = cl::mpselect($dest, $merge, $pkeys);
  }

  static final function set_nn(&$dest, $key, $value) {
    self::ensure_narray($dest);
    if ($value !== null) {
      if ($key === null) $dest[] = $value;
      else $dest[$key] = $value;
    }
    return $value;
  }

  static final function append_nn(&$dest, $value) {
    return self::set_nn($dest, null, $value);
  }

  static final function set_nz(&$dest, $key, $value) {
    self::ensure_narray($dest);
    if ($value !== null && $value !== false) {
      if ($key === null) $dest[] = $value;
      else $dest[$key] = $value;
    }
    return $value;
  }

  static final function append_nz(&$dest, $value) {
    self::ensure_narray($dest);
    return self::set_nz($dest, null, $value);
  }

  static final function prepend_nn(&$dest, $value) {
    self::ensure_narray($dest);
    if ($value !== null) {
      if ($dest === null) $dest = [];
      array_unshift($dest, $value);
    }
    return $value;
  }

  static final function prepend_nz(&$dest, $value) {
    self::ensure_narray($dest);
    if ($value !== null && $value !== false) {
      if ($dest === null) $dest = [];
      array_unshift($dest, $value);
    }
    return $value;
  }

  static final function replace_nx(&$dest, $key, $value) {
    self::ensure_narray($dest);
    if ($dest !== null && !array_key_exists($key, $dest)) {
      return $dest[$key] = $value;
    } else {
      return $dest[$key] ?? null;
    }
  }

  static final function replace_n(&$dest, $key, $value) {
    self::ensure_narray($dest);
    $pvalue = $dest[$key] ?? null;
    if ($pvalue === null) $dest[$key] = $value;
    return $pvalue;
  }

  static final function replace_z(&$dest, $key, $value) {
    self::ensure_narray($dest);
    $pvalue = $dest[$key] ?? null;
    if ($pvalue === null || $pvalue === false) $dest[$key] = $value;
    return $pvalue;
  }

  static final function pop(&$dest, $key, $default=null) {
    if ($dest === null) return $default;
    self::ensure_narray($dest);
    if ($key === null) return array_pop($dest);
    $value = $dest[$key] ?? $default;
    unset($dest[$key]);
    return $value;
  }

  static final function popx(&$dest, ?array $keys): array {
    $values = [];
    if ($dest === null) return $values;
    self::ensure_narray($dest);
    if ($keys === null) return $values;
    foreach ($keys as $key) {
      $values[$key] = self::pop($dest, $key);
    }
    return $values;
  }

  static final function filter_if(&$dest, callable $cond): void {
    self::ensure_narray($dest);
    $dest = cl::filter_if($dest, $cond);
  }

  static final function filter_z($dest): void { self::filter_if($dest, [cv::class, "z"]);}
  static final function filter_nz($dest): void { self::filter_if($dest, [cv::class, "nz"]);}
  static final function filter_n($dest): void { self::filter_if($dest, [cv::class, "n"]);}
  static final function filter_nn($dest): void { self::filter_if($dest, [cv::class, "nn"]);}
  static final function filter_t($dest): void { self::filter_if($dest, [cv::class, "t"]);}
  static final function filter_f($dest): void { self::filter_if($dest, [cv::class, "f"]);}
  static final function filter_pt($dest): void { self::filter_if($dest, [cv::class, "pt"]);}
  static final function filter_pf($dest): void { self::filter_if($dest, [cv::class, "pf"]);}
  static final function filter_equals($dest, $value): void { self::filter_if($dest, cv::equals($value)); }
  static final function filter_not_equals($dest, $value): void { self::filter_if($dest, cv::not_equals($value)); }
  static final function filter_same($dest, $value): void { self::filter_if($dest, cv::same($value)); }
  static final function filter_not_same($dest, $value): void { self::filter_if($dest, cv::not_same($value)); }

  #############################################################################

  static final function sort(?array &$array, int $flags=SORT_REGULAR, bool $assoc=false): void {
    if ($array === null) return;
    if ($assoc) asort($array, $flags);
    else sort($array, $flags);
  }

  static final function ksort(?array &$array, int $flags=SORT_REGULAR): void {
    if ($array === null) return;
    ksort($array, $flags);
  }

  static final function usort(?array &$array, array $keys, bool $assoc=false): void {
    if ($array === null) return;
    if ($assoc) uasort($array, cl::compare($keys));
    else usort($array, cl::compare($keys));
  }
}
