<?php
namespace nulib\cache;

use IteratorAggregate;
use nulib\cl;
use nulib\db\CapacitorChannel;
use nulib\db\CapacitorStorage;
use nulib\php\func;
use Traversable;

class CursorChannel extends CapacitorChannel implements IteratorAggregate {
  static function with($cursorId=null, ?iterable $rows=null, ?CapacitorStorage $storage=null): self {
    $storage ??= cache::storage();
    $channel = (new static($cursorId))->initStorage($storage);
    if ($rows !== null) $channel->rechargeAll($rows);
    return $channel;
  }

  const NAME = "cursor";
  const TABLE_NAME = "cursor";

  const COLUMN_DEFINITIONS = [
    "group_id_" => "varchar(32) not null", // groupe de curseur
    "id_" => "varchar(128) not null", // nom du curseur
    "key_index_" => "integer not null",
    "key_" => "varchar(128) not null",
    "search_" => "varchar(255)",

    "primary key (group_id_, id_, key_index_)",
  ];

  const ADD_COLUMNS = null;

  protected function COLUMN_DEFINITIONS(): ?array {
    return cl::merge(self::COLUMN_DEFINITIONS, static::ADD_COLUMNS);
  }

  /**
   * @param array|string $cursorId
   */
  function __construct($cursorId) {
    parent::__construct();
    cache::verifix_id($cursorId);
    [
      "group_id" => $this->groupId,
      "id" => $this->id,
    ] = $cursorId;
  }

  protected string $groupId;

  protected string $id;

  function getCursorId(): array {
    return [
      "group_id" => $this->groupId,
      "id" => $this->id,
    ];
  }

  function getBaseFilter(): ?array {
    return [
      "group_id_" => $this->groupId,
      "id_" => $this->id,
    ];
  }

  protected int $index = 0;

  protected function getSearch($item): ?string {
    $search = cl::filter_n(cl::with($item));
    $search = implode(" ", $search);
    return substr($search, 0, 255);
  }

  function getItemValues($item, $key=null): ?array {
    $index = $this->index++;
    $key = $key ?? $index;
    $key = substr(strval($key), 0, 128);
    $addColumns = static::ADD_COLUMNS ?? [];
    $addColumns = cl::select($item,
      array_filter(array_keys($addColumns), function ($key) {
        return is_string($key);
      }));
    return cl::merge($addColumns, [
      "group_id_" => $this->groupId,
      "id_" => $this->id,
      "key_index_" => $index,
      "key_" => $key,
      "search_" => $this->getSearch($item),
    ]);
  }

  function reset(bool $recreate=false): void {
    $this->index = 0;
    parent::reset($recreate);
  }

  function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
    if ($items === null) return 0;
    $count = 0;
    if ($func !== null) $func = func::with($func, $args)->bind($this);
    foreach ($items as $key => $item) {
      $count += $this->charge($item, $func, [$key]);
    }
    return $count;
  }

  function rechargeAll(?iterable $items): self {
    $this->delete(null);
    $this->index = 0;
    $this->chargeAll($items);
    return $this;
  }

  function getIterator(): Traversable {
    $rows = $this->dbAll([
      "cols" => ["key_", "item__"],
      "where" => $this->getBaseFilter(),
    ]);
    foreach ($rows as $row) {
      $key = $row["key_"];
      $item = $this->unserialize($row["item__"]);
      yield $key => $item;
    }
  }
}
