<?php
namespace nur\m\base;

use nur\A;
use nur\b\ValueException;
use nur\iter;
use nur\m\IConn;

abstract class EntityManager {
  const TABLE_NAME = null;
  const PK_KEY = null;
  const PK_AUTO = null;

  /**
   * retourner les champs de la clé primaire
   * - soit une valeur chaine si c'est une clé simple
   * - soit un tableau si c'est une clé composite 
   */
  protected static function get_pk_key($row, $pk_key=null) {
    if ($pk_key === null) $pk_key = static::PK_KEY;
    if (is_array($pk_key) && count($pk_key) == 1) $pk_key = iter::first($pk_key);
    if ($pk_key === false) {
      # si $pk_key est false, alors toute la ligne est une clé: prendre les noms
      # des colonnes
      $pk_key = array_keys(A::with($row));
      if (is_array($pk_key) && count($pk_key) == 1) $pk_key = iter::first($pk_key);
    }
    return $pk_key;
  }

  /**
   * Obtenir la valeur de la clé primaire d'une ligne.
   *
   * - Si $pk_key vaut false, considérer que la clé est l'ensemble des colonnes.
   * - Si la clé est composite, retourner un tableau associatif avec toutes les
   * valeurs
   * - Sinon retourner uniquement la valeur de la clé.
   */
  protected static function get_pk_value($row, $pk_key=null) {
    $pk_key = self::get_pk_key($row, $pk_key);
    if (is_array($pk_key)) {
      if (!is_array($row)) throw ValueException::invalid_value($row, "row");
      $pk_value = [];
      foreach ($pk_key as $col) {
        $pk_value[$col] = A::get($row, $col);
      }
      return $pk_value;
    } elseif (is_array($row)) {
      return A::get($row, $pk_key);
    } else {
      return $row;
    }
  }

  /**
   * obtenir un tableau bindings pour filtrer une table sur la valeur de la clé
   * primaire
   */
  protected static function get_pk_bindings($row, $pk_key=null): array {
    $pk_value = self::get_pk_value($row, $pk_key);
    if (is_array($pk_value)) return $pk_value;
    $pk_key = self::get_pk_key($row, $pk_key);
    return [$pk_key => $pk_value];
  }

  /**
   * Obtenir la valeur de la clé primaire d'une ligne sous forme de chaine.
   *
   * Si la clé est composite, séparer chaque valeur par un tiret '-' pour faire
   * une seule chaine.
   */
  protected static function get_pk_string($row, $pk_key=null): string {
    $pk_value = self::get_pk_value($row, $pk_key);
    if (is_array($pk_value)) $pk_string = implode("-", $pk_value);
    else $pk_string = strval($pk_value);
    return $pk_string;
  }

  protected static function map_pks(iterable $rows, $pk_key=null): array {
    $mapped_rows = [];
    foreach ($rows as $row) {
      $pk_string = self::get_pk_string($row, $pk_key);
      $mapped_rows[$pk_string] = $row;
    }
    return $mapped_rows;
  }
  
  protected static function sql_select_all(&$filter=null): string {
    $sql = "select * from ". static::TABLE_NAME;
    if ($filter !== null) {
      if (!is_array($filter)) $filter = self::get_pk_bindings($filter);
      $and = " where";
      foreach ($filter as $col => $value) {
        $sql .= "$and $col = :$col";
        $and = " and";
      }
    }
    return $sql;
  }

  abstract static function conn(IConn $pdo=null): IConn;

  static function select_all($filter=null, $map_key=null): array {
    $sql = self::sql_select_all($filter);
    $rows = static::conn()->query($sql, $filter)->execute();
    return self::map_pks($rows, $map_key);
  }

  static function select_all_val(string $key, $filter=null): array {
    $sql = self::sql_select_all($filter);
    $rows = static::conn()->query($sql, $filter)->execute();
    $vals = [];
    foreach ($rows as $row) {
      $vals[] = A::get($row, $key);
    }
    return $vals;
  }

  static function select_first($filter=null, $default=null) {
    $sql = self::sql_select_all($filter);
    return static::conn()->query($sql, $filter)->first($default);
  }

  static function select_first_val(string $key, $filter=null, $default=null) {
    $sql = self::sql_select_all($filter);
    $first = static::conn()->query($sql, $filter)->first($default);
    return A::get($first, $key, $default);
  }

  static function select_one($filter=null, $default=null, ?bool $rewind=null): array {
    $sql = self::sql_select_all($filter);
    return static::conn()->query($sql, $filter)->one($default, $rewind);
  }
}
