<?php
namespace nur\sery\file\app;

use nur\sery\cl;
use nur\sery\file\base\SharedFile;
use nur\sery\os\path;
use nur\sery\php\time\DateTime;
use nur\sery\str;

/**
 * Class RunFile: une classe permettant de suivre le fonctionnement d'une
 * application qui tourne en tâche de fond
 */
class RunFile {
  const RUN_EXT = ".run";
  const LOCK_EXT = ".lock";

  const NAME = null;

  function __construct(string $file, ?string $name=null) {
    $file = path::ensure_ext($file, self::RUN_EXT);
    $this->file = new SharedFile($file);
    $this->name = $name ?? static::NAME;
  }

  /** @var SharedFile */
  protected $file;

  /** @var ?string */
  protected $name;

  protected static function merge(array $data, array $merge): array {
    return cl::merge($data, [
      "serial" => $data["serial"] + 1,
    ], $merge);
  }

  protected function initData(bool $withDateStart=true): array {
    $dateStart = $withDateStart? new DateTime(): null;
    return [
      "name" => $this->name,
      "serial" => 0,
      "date_start" => $dateStart,
      "date_stop" => null,
      "action" => null,
      "action_date_start" => null,
      "action_max_step" => null,
      "action_current_step" => null,
      "action_date_step" => null,
    ];
  }

  function read(): array {
    $data = $this->file->unserialize();
    if (!is_array($data)) $data = $this->initData(false);
    return $data;
  }

  /** tester si l'application est démarrée */
  function isStarted(): bool {
    $data = $this->read();
    return $data["date_start"] !== null;
  }

  /** tester si l'application est arrêtée */
  function isStopped(): bool {
    $data = $this->read();
    return $data["date_stop"] !== null;
  }

  function haveWorked(int $serial, ?int &$currentSerial=null): bool {
    $data = $this->read();
    return $serial !== $data["serial"];
  }

  protected function willWrite(): array {
    $file = $this->file;
    $file->lockWrite();
    $data = $file->unserialize(null, false, true);
    if (!is_array($data)) {
      $data = $this->initData();
      $file->ftruncate();
      $file->serialize($data, false, true);
    }
    $file->ftruncate();
    return [$file, $data];
  }

  /** indiquer que l'application démarre */
  function start(): void {
    $this->file->serialize($this->initData());
  }

  /** indiquer le début d'une action */
  function action(?string $title, ?int $maxSteps=null): void {
    [$file, $data] = $this->willWrite();
    $file->serialize(self::merge($data, [
      "action" => $title,
      "action_date_start" => new DateTime(),
      "action_max_step" => $maxSteps,
      "action_current_step" => 0,
    ]));
  }

  /** indiquer qu'une étape est franchie dans l'action en cours */
  function step(int $nbSteps=1): void {
    [$file, $data] = $this->willWrite();
    $file->serialize(self::merge($data, [
      "action_date_step" => new DateTime(),
      "action_current_step" => $data["action_current_step"] + $nbSteps,
    ]));
  }

  /** indiquer que l'application s'arrête */
  function stop(): void {
    [$file, $data] = $this->willWrite();
    $file->serialize(self::merge($data, [
      "date_stop" => new DateTime(),
    ]));
  }

  function getLockFile(?string $name=null, ?string $title=null): LockFile {
    $ext = self::LOCK_EXT;
    if ($name !== null) $ext = ".$name$ext";
    $file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT);
    $name = str::join("/", [$this->name, $name]);
    return new LockFile($file, $name, $title);
  }
}
