<?php
namespace nur\sery\db\sqlite;

use nur\sery\cl;
use nur\sery\php\func;
use nur\sery\ValueException;

/**
 * Class SqliteCapacitor: objet permettant d'accumuler des données pour les
 * réutiliser plus tard
 */
class SqliteCapacitor {
  function __construct($sqlite) {
    $this->sqlite = Sqlite::with($sqlite);
  }

  /** @var Sqlite */
  protected $sqlite;

  protected function getTableName(?string $channel): string {
    return ($channel ?? "default")."_channel";
  }

  /** tester si le canal spécifié existe */
  function exists(?string $channel=null): bool {
    #XXX maintenir une table channels avec la liste des canaux valides
  }

  /** supprimer le canal spécifié */
  function reset(?string $channel=null) {
    $tableName = $this->getTableName($channel);
    $this->sqlite->exec("drop table if exists $tableName");
    #XXX maj de la tables channels
  }

  /** @var array */
  protected $created;

  protected function getKeyDefinitions(?string $channel): ?array {
    return null;
  }

  protected function getKeyValues($item, ?string $channel): ?array {
    return null;
  }

  protected function create(?string $channel): void {
    if ($this->created[$channel] ?? false) return;
    $columns = cl::merge([
      "_id" => "integer primary key autoincrement",
      "_item" => "text",
    ], $this->getKeyDefinitions($channel));
    #XXXvvv migrer vers la syntaxe tableau de create
    $index = 0;
    foreach ($columns as $column => &$definition) {
      if ($column === $index) {
        $index++;
      } else {
        $definition = "$column $definition";
      }
    }; unset($definition);
    $query = implode("", [
      "create table if not exists ",
      $this->getTableName($channel),
      " (",
      implode(", ", $columns),
      ")",
    ]);
    #XXX^^^ migrer vers la syntaxe tableau de create
    $this->sqlite->exec($query);
    #XXX maj de la tables channels
    $this->created[$channel] = true;
  }

  function charge($item, ?string $channel=null) {
    $this->create($channel);
    $values = cl::merge($this->getKeyValues($item, $channel), [
      "_item" => serialize($item),
    ]);
    $this->sqlite->exec([
      "insert",
      "into" => $this->getTableName($channel),
      "values" => $values,
    ]);
  }

  /** décharger les données du canal spécifié */
  function discharge(?string $channel=null, bool $reset=true): iterable {
    $rows = $this->sqlite->all([
      "select _item",
      "from" => $this->getTableName($channel),
    ]);
    foreach ($rows as $row) {
      $item = unserialize($row['_item']);
      yield $item;
    }
    if ($reset) $this->reset($channel);
  }

  /**
   * appeler une fonction pour chaque élément du canal spécifié.
   *
   * $keys permet de filtrer parmi les élements chargés
   *
   * si $func retourne un tableau, il est utilisé pour mettre à jour
   * l'enregistrement.
   */
  function each(callable $func, ?array $keys, ?string $channel=null): void {
    $context = func::_prepare($func);
    foreach ($this->discharge($channel, false) as $item) {
      $result = func::_call($context, [$item]);
      #XXX
    }
  }

  /**
   * obtenir l'élément identifié par les clés spécifiées sur le canal spécifié
   *
   * si $keys n'est pas un tableau, il est transformé en ["_id" => $keys]
   */
  function get($keys, ?string $channel=null) {
    if ($keys === null) throw ValueException::null("keys");
    if (!is_array($keys)) $keys = ["_id" => $keys];
    #XXX
  }

  function close(): void {
    $this->sqlite->close();
  }

  function sqlite__exec(string $query): bool {
    return $this->sqlite->_exec($query);
  }

  function sqlite_exec($query, ?array $params=null): bool {
    return $this->sqlite->exec($query, $params);
  }

  function sqlite__get(string $query, bool $entireRow=false) {
    return $this->sqlite->_get($query, $entireRow);
  }

  function sqlite_get($query, ?array $params=null, bool $entireRow=false) {
    return $this->sqlite->get($query, $params, $entireRow);
  }

  function sqlite_one($query, ?array $params=null): ?array {
    return $this->sqlite->one($query, $params);
  }

  function sqlite_all($query, ?array $params=null): iterable {
    return $this->sqlite->all($query, $params);
  }
}
