<?php
namespace nur\sery\wip\app\cli;

use nur\sery\ExitError;
use nur\sery\file\TmpfileWriter;
use nur\sery\os\path;
use nur\sery\os\proc\Cmd;
use nur\sery\output\msg;
use nur\sery\wip\app\app2;
use nur\sery\wip\app\args;
use nur\sery\wip\app\RunFile;

class bg_launcher {
  /**
   * transformer une liste d'argument de la forme
   * - ["myArg" => $value]  devient  ["--my-arg", "$value"]
   * - ["myOpt" => true]    devient  ["--my-opt"]
   * - ["myOpt" => false]   est momis
   * - les valeurs séquentielles sont prises telles quelles
   */
  static function verifix_args(array $args): array {
    $args = args::from_array($args);
    # corriger le chemin de l'application pour qu'il soit absolu et normalisé
    $args[0] = path::abspath($args[0]);
    return $args;
  }

  static function launch(string $appClass, array $args): int {
    $app = app2::get();
    $vendorBindir = $app->getVendorbindir();
    $launch_php = "$vendorBindir/_launch.php";
    if (!file_exists($launch_php)) {
      $launch_php = __DIR__."/../../lib/_launch.php";
    }
    $tmpfile = new TmpfileWriter();
    $tmpfile->keep()->serialize($app->getParams());

    $args = self::verifix_args($args);
    $cmd = new Cmd([
      $launch_php,
      "--internal-use", $tmpfile->getFile(),
      $appClass, "--", ...$args,
    ]);
    $cmd->addRedir("both", "/tmp/nulib_app_launcher-launch.log");
    $cmd->passthru($exitcode);

    # attendre un peu que la commande aie le temps de s'initialiser
    sleep(1);

    $tmpfile->close();
    return $exitcode;
  }

  static function _start(array $args, Runfile $runfile): bool {
    if ($runfile->warnIfLocked()) return false;
    $pid = pcntl_fork();
    if ($pid == -1) {
      # parent, impossible de forker
      throw new ExitError(app2::EC_FORK_PARENT, "Unable to fork");
    } elseif ($pid) {
      # parent, fork ok
      return true;
    } else {
      ## child, fork ok
      # Créer un groupe de process, pour pouvoir tuer tous les enfants en même temps
      $runfile->tm_startPg();
      $logfile = $runfile->getLogfile() ?? "/tmp/NULIB_APP_app_start.log";
      $pid = posix_getpid();
      $exitcode = app2::EC_FORK_CHILD;
      try {
        # puis lancer la commande
        $cmd = new Cmd($args);
        $cmd->addSource("/g/init.env");
        $cmd->addRedir("both", $logfile, true);
        msg::debug("$pid: launching\n".$cmd->getCmd());
        $cmd->fork_exec($exitcode);
        msg::debug("$pid: exitcode=$exitcode");
        return true;
      } finally {
        $runfile->wfStopped($exitcode);
      }
    }
  }

  static function _stop(Runfile $runfile): void {
    $data = $runfile->read();
    $pid = $data["pg_pid"];
    if ($pid === null) {
      msg::warning("$data[name]: groupe de process inconnu");
      return;
    }
    msg::action("kill $pid");
    if (!posix_kill(-$pid, SIGKILL)) {
      switch (posix_get_last_error()) {
      case PCNTL_ESRCH:
        msg::afailure("process inexistant");
        break;
      case PCNTL_EPERM:
        msg::afailure("process non accessible");
        break;
      case PCNTL_EINVAL:
        msg::afailure("signal invalide");
        break;
      }
      return;
    }
    $timeout = 10;
    while ($runfile->tm_isUndead($pid)) {
      sleep(1);
      if (--$timeout == 0) {
        msg::afailure("impossible d'arrêter la tâche");
        return;
      }
    }
    $runfile->wfStopped(app2::EC_REAPABLE);
    msg::asuccess();
  }
}
