<?php
namespace nur\sery\tools;

use nur\sery\ExitError;
use nur\sery\ext\yaml;
use nur\sery\os\path;
use nur\sery\os\proc\Cmd;
use nur\sery\os\sh;
use nur\sery\output\msg;
use nur\sery\app;
use nur\sery\app\cli\Application;
use nur\sery\app\RunFile;

class BgLauncherApp extends Application {
  const ACTION_INFOS = 0, ACTION_START = 1, ACTION_STOP = 2;
  const ARGS = [
    "purpose" => "lancer un script en tâche de fond",
    "usage" => "ApplicationClass args...",

    "sections" => [
      parent::VERBOSITY_SECTION,
    ],

    ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS,
      "help" => "Afficher des informations sur la tâche",
    ],
    ["-s", "--start", "name" => "action", "value" => self::ACTION_START,
      "help" => "Démarrer la tâche",
    ],
    ["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP,
      "help" => "Arrêter la tâche",
    ],
  ];

  protected int $action = self::ACTION_START;

  protected ?array $args = null;

  static function show_infos(RunFile $runfile, ?int $level=null): void {
    msg::print($runfile->getDesc(), $level);
    msg::print(yaml::with(["data" => $runfile->read()]), ($level ?? 0) - 1);
  }

  function main() {
    $args = $this->args;

    $appClass = $args[0] ?? null;
    if ($appClass === null) {
      self::die("Vous devez spécifier la classe de l'application");
    }
    $appClass = $args[0] = str_replace("/", "\\", $appClass);
    if (!class_exists($appClass)) {
      self::die("$appClass: classe non trouvée");
    }

    $useRunfile = constant("$appClass::USE_RUNFILE");
    if (!$useRunfile) {
      self::die("Cette application ne supporte le lancement en tâche de fond");
    }

    $runfile = app::with($appClass)->getRunfile();
    switch ($this->action) {
    case self::ACTION_START:
      $argc = count($args);
      $appClass::_manage_runfile($argc, $args, $runfile);
      if ($runfile->warnIfLocked()) self::exit(app::EC_LOCKED);
      array_splice($args, 0, 0, [
        PHP_BINARY,
        path::abspath(NULIB_APP_app_launcher),
      ]);
      app::params_putenv();
      self::_start($args, $runfile);
      break;
    case self::ACTION_STOP:
      self::_stop($runfile);
      self::show_infos($runfile, -1);
      break;
    case self::ACTION_INFOS:
      self::show_infos($runfile);
      break;
    }
  }

  public static function _start(array $args, Runfile $runfile): void {
    $pid = pcntl_fork();
    if ($pid == -1) {
      # parent, impossible de forker
      throw new ExitError(app::EC_FORK_PARENT, "Unable to fork");
    } elseif (!$pid) {
      # child, fork ok
      $runfile->wfPrepare($pid);
      $outfile = $runfile->getOutfile() ?? "/tmp/NULIB_APP_app_console.out";
      $exitcode = app::EC_FORK_CHILD;
      try {
        # rediriger STDIN, STDOUT et STDERR
        fclose(fopen($outfile, "wb")); // vider le fichier
        fclose(STDIN); $in = fopen("/dev/null", "rb");
        fclose(STDOUT); $out = fopen($outfile, "ab");
        fclose(STDERR); $err = fopen($outfile, "ab");
        # puis lancer la commande
        $cmd = new Cmd($args);
        $cmd->addSource("/g/init.env");
        $cmd->addRedir("both", $outfile, true);
        $cmd->fork_exec($exitcode, false);
        sh::_waitpid(-$pid, $exitcode);
      } finally {
        $runfile->wfReaped($exitcode);
      }
    }
  }

  public static function _stop(Runfile $runfile): bool {
    $data = $runfile->read();
    $pid = $runfile->_getCid($data);
    msg::action("stop $pid");
    if ($runfile->wfKill($reason)) {
      msg::asuccess();
      return true;
    } else {
      msg::afailure($reason);
      return false;
    }
  }
}
