struct
(** Encoding of the state of a target:

  • `run_bookkeeping` keeps the information for the `Long_running` plugin.
  • `log` is a time stamped optional log message
Every state point to its previous state through a `'a hitory`.

We use the subtyping of polymorphic variants to encode the state-machine; a given state can come only from certain previous states, those are enforced with the type-parameter of the `history` value.

*)


  type run_bookkeeping = 
    { plugin_name: string; run_parameters: string } [@@deriving yojson]
  type log = {
    (* time: Time.t; *)
    time: float;
    message: string option;
  } [@@deriving yojson]
  type 'a history = {
    log: log;
    previous_state: 'a;
  } [@@deriving yojson]
  type id = string
      [@@deriving yojson]
  type passive = [ `Passive of log ] [@@deriving yojson]
  type active = [
    | `Active of (passive history * [ `User | `Dependency of id ])
  ] [@@deriving yojson]
  type evaluating_condition = [
    | active
    | `Tried_to_eval_condition of evaluating_condition history
  ] [@@deriving yojson]
  type already_done = [
    | `Already_done of evaluating_condition history
  ] [@@deriving yojson]
  type building = [
    | `Building of evaluating_condition history
    | `Still_building of building history
  ] [@@deriving yojson]
  type dependency_failure = [
    | `Dependencies_failed of (building history * id list)
  ] [@@deriving yojson]
  type starting = [
    | `Starting of building history
    | `Tried_to_start of (starting history * run_bookkeeping)
  ] [@@deriving yojson]
  (* let starting_of_yojson yj : ([< starting ], _) Result.t = starting_of_yojson yj *)
  type failed_to_start = [
    | `Failed_to_eval_condition of evaluating_condition history
    | `Failed_to_start of (starting history * run_bookkeeping)
  ] [@@deriving yojson]
  type running = [
    | `Started_running of (starting history * run_bookkeeping)
    | `Still_running  of (running history * run_bookkeeping)
    | `Still_running_despite_recoverable_error of
        (string * running history * run_bookkeeping)
  ] [@@deriving yojson]
(*
Successful run is the success of the process, we still have to verify
that the potential condition has been ensured.
*)

  type successful_run = [
    | `Successfully_did_nothing of starting history
    | `Ran_successfully of (running history * run_bookkeeping)
    | `Tried_to_reeval_condition of (string * successful_run history)
  ] [@@deriving yojson]
  type process_failure_reason = [
    (* | Did_not_ensure_condition of string *)
    | `Long_running_failure of string
  ] [@@deriving yojson]
  type failed_run = [
    | `Failed_running of
        (running history * process_failure_reason * run_bookkeeping)
  ] [@@deriving yojson]
  type verified_run = [
    | `Verified_success of successful_run history
  ] [@@deriving yojson]
  type failed_to_verify_success = [
    | `Did_not_ensure_condition of successful_run history
  ] [@@deriving yojson]
  type killable_state = [
    | passive
    | evaluating_condition
    | building
    | starting
    | running
  ] [@@deriving yojson]
  type killing = [
    | `Killing of killable_state history
    | `Tried_to_kill of killing history
  ] [@@deriving yojson]
  type killed = [
    | `Killed of killing history
  ] [@@deriving yojson]
  type failed_to_kill = [
    | `Failed_to_kill of killing history
  ] [@@deriving yojson]
  type finishing_state = [
    | failed_run
    | verified_run
    | already_done
    | dependency_failure
    | failed_to_start
    | killed
    | failed_to_kill
    | failed_to_verify_success
  ] [@@deriving yojson]
  type finished = [
    | `Finished of finishing_state history
  ] [@@deriving yojson]
  type t = [
    | killing
    | killed
    | killable_state
    | successful_run
    | finishing_state
    | finished
  ] [@@deriving yojson]

  let of_yojson yj : (t, _) Result.t = of_yojson yj


  let make_log ?message () = 
    {time = Time.now (); message}
  let to_history ?log previous_state =
    {log = make_log ?message:log (); previous_state}

  let rec simplify (t: t) =
    match t with
    | `Building _
    | `Tried_to_start _
    | `Started_running _
    | `Starting _
    | `Still_building _
    | `Still_running _
    | `Still_running_despite_recoverable_error _
    | `Ran_successfully _
    | `Successfully_did_nothing _
    | `Tried_to_eval_condition _
    | `Tried_to_reeval_condition _
    | `Active _ -> `In_progress
    | `Verified_success _
    | `Already_done _ -> `Successful
    | `Dependencies_failed _
    | `Failed_running _
    | `Failed_to_kill _
    | `Failed_to_start _
    | `Failed_to_eval_condition _
    | `Killing _
    | `Tried_to_kill _
    | `Did_not_ensure_condition _
    | `Killed _ -> `Failed
    | `Finished s ->
      simplify (s.previous_state :> t)
    | `Passive _ -> `Activable

  let rec passive_time (t: t) =
    let continue history =
      passive_time (history.previous_state :> t)
    in
    match t with
    | `Building history -> continue history
    | `Tried_to_start (history, _) -> continue history
    | `Started_running (history, _) -> continue history
    | `Starting history -> continue history
    | `Still_building history -> continue history
    | `Still_running (history, _) -> continue history
    | `Still_running_despite_recoverable_error (_, history, _) -> continue history
    | `Ran_successfully (history, _) -> continue history
    | `Successfully_did_nothing history -> continue history
    | `Active (history, _) -> continue history
    | `Tried_to_eval_condition history -> continue history
    | `Tried_to_reeval_condition (_, history) -> continue history
    | `Verified_success history -> continue history
    | `Already_done history -> continue history
    | `Dependencies_failed (history, _) -> continue history
    | `Failed_running (history, _, _) -> continue history
    | `Failed_to_kill history -> continue history
    | `Failed_to_eval_condition history -> continue history
    | `Failed_to_start (history, _) -> continue history
    | `Killing history -> continue history
    | `Tried_to_kill history -> continue history
    | `Did_not_ensure_condition history -> continue history
    | `Killed history -> continue history
    | `Finished history -> continue history
    | `Passive log -> log.time

  let finished_time = function
  | `Finished {log; _} -> Some log.time
  | _ -> None

  let name (t: t) =
    match t with
    | `Building _ -> "Building"
    | `Tried_to_start _ -> "Tried_to_start"
    | `Started_running _ -> "Started_running"
    | `Starting _ -> "Starting"
    | `Still_building _ -> "Still_building"
    | `Still_running _ -> "Still_running"
    | `Still_running_despite_recoverable_error _ ->
      "Still_running_despite_recoverable_error"
    | `Ran_successfully _ -> "Ran_successfully"
    | `Successfully_did_nothing _ -> "Successfully_did_nothing"
    | `Active _ -> "Active"
    | `Tried_to_eval_condition _ -> "Tried_to_eval_condition"
    | `Tried_to_reeval_condition _ -> "Tried_to_reeval_condition"
    | `Verified_success _ -> "Verified_success"
    | `Already_done _ -> "Already_done"
    | `Dependencies_failed _ -> "Dependencies_failed"
    | `Failed_running _ -> "Failed_running"
    | `Failed_to_kill _ -> "Failed_to_kill"
    | `Failed_to_eval_condition _ -> "Failed_to_eval_condition"
    | `Failed_to_start _ -> "Failed_to_start"
    | `Killing _ -> "Killing"
    | `Tried_to_kill _ -> "Tried_to_kill"
    | `Did_not_ensure_condition _ -> "Did_not_ensure_condition"
    | `Killed _ -> "Killed"
    | `Finished _ -> "Finished"
    | `Passive _ -> "Passive"

  let rec latest_run_bookkeeping (t: t) =
    let continue history =
      latest_run_bookkeeping (history.previous_state :> t) in
    match t with
    | `Building history -> None
    | `Tried_to_start (hist, book) -> (Some book)
    | `Started_running (hist, book) -> (Some book)
    | `Starting history -> (None)
    | `Still_building history -> (None)
    | `Still_running (hist, book) -> (Some book)
    | `Still_running_despite_recoverable_error (_, hist, book) -> (Some book)
    | `Ran_successfully (hist, book) -> (Some book)
    | `Successfully_did_nothing history -> (None)
    | `Tried_to_eval_condition _ -> (None)
    | `Tried_to_reeval_condition (_, history) -> continue history
    | `Active (history, _) -> (None)
    | `Verified_success history -> continue history
    | `Already_done history -> None
    | `Dependencies_failed (history, _) -> (None)
    | `Failed_running (hist, _, book) -> (Some book)
    | `Failed_to_kill history -> continue history
    | `Failed_to_eval_condition history -> continue history
    | `Failed_to_start (hist, book) -> (Some book)
    | `Killing history -> continue history
    | `Tried_to_kill history -> continue history
    | `Did_not_ensure_condition history -> continue history
    | `Killed history -> continue history
    | `Finished history -> continue history
    | `Passive log -> (None)

  let contents (t: t) =
    let some h = Some (h :> t history) in
    match t with
    | `Building history -> (some history, None)
    | `Tried_to_start (hist, book) -> (some hist, Some book)
    | `Started_running (hist, book) -> (some hist, Some book)
    | `Starting history -> (some history, None)
    | `Still_building history -> (some history, None)
    | `Still_running (hist, book) -> (some hist, Some book)
    | `Still_running_despite_recoverable_error (_, hist, book) ->
      (some hist, Some book)
    | `Ran_successfully (hist, book) -> (some hist, Some book)
    | `Successfully_did_nothing history -> (some history, None)
    | `Active (history, _) -> (some history, None)
    | `Tried_to_eval_condition history -> (some history, None)
    | `Tried_to_reeval_condition (_, history) -> (some history, None)
    | `Verified_success history -> (some history, None)
    | `Already_done history -> (some history, None)
    | `Dependencies_failed (history, _) -> (some history, None)
    | `Failed_running (hist, _, book) -> (some hist, Some book)
    | `Failed_to_kill history -> (some history, None)
    | `Failed_to_eval_condition history -> (some history, None)
    | `Failed_to_start (hist, book) -> (some hist, Some book)
    | `Killing history -> (some history, None)
    | `Tried_to_kill history -> (some history, None)
    | `Did_not_ensure_condition history -> (some history, None)
    | `Killed history -> (some history, None)
    | `Finished history -> (some history, None)
    | `Passive log -> (NoneNone)

  let summary t =
    let rec count_start_attempts : starting history -> int = fun h ->
      match h.previous_state with 
      | `Starting _ -> 1
      | `Tried_to_start (hh, _) -> 1 + (count_start_attempts hh)
    in
    let rec count_kill_attempts : killing history -> int = fun h ->
      match h.previous_state with 
      | `Killing _ -> 1
      | `Tried_to_kill hh -> 1 + (count_kill_attempts hh)
    in
    let plural_of_int ?(y=false) n =
      match y, n with
      | true, 1 ->  "y"
      | true, _ -> "ies"
      | _, 1 ->  ""
      | _, _ -> "s" in
    let rec dive (t: t) =
      let continue history = dive (history.previous_state :> t) in
      match t with
      | `Building history -> continue history
      | `Tried_to_start (history, book) ->
        let attempts = count_start_attempts history in
        fmt " %d start-attempt%s" attempts (plural_of_int attempts)
        :: continue history
      | `Started_running (history, book) -> continue history
      | `Starting history -> continue history
      | `Still_building history -> continue history
      | `Still_running (history, book) -> continue history
      | `Still_running_despite_recoverable_error (error, history, book) ->
        fmt "non-fatal error %S (check-running)" error :: continue history
      | `Ran_successfully (history, book) -> continue history
      | `Successfully_did_nothing history -> continue history
      | `Active (history, _) -> continue history
      | `Tried_to_eval_condition history -> continue history
      | `Tried_to_reeval_condition (error, history) ->
        fmt "non-fatal error %S (eval-condition)" error :: continue history
      | `Verified_success history -> continue history
      | `Already_done history ->
        "already-done" :: continue history
      | `Dependencies_failed (history, deps) ->
        let nb_deps = (List.length deps) in
        fmt "%d depependenc%s failed" nb_deps (plural_of_int ~y:true nb_deps)
        :: continue history
      | `Failed_running (history, reason, book) ->
        fmt "Reason: %S" (match reason with | `Long_running_failure s -> s)
        :: continue history
      | `Failed_to_kill history -> continue history
      | `Failed_to_eval_condition history -> continue history
      | `Failed_to_start (history, book) ->
        continue history
      | `Killing history ->
        fmt "killed from %s" (name (history.previous_state :> t))
        :: continue history
      | `Tried_to_kill history ->
        fmt "%d killing-attempts" (count_kill_attempts history)
        :: continue history
      | `Did_not_ensure_condition history ->
        "Did_not_ensure_condition" :: continue history
      | `Killed history ->
        "killed" :: continue history
      | `Finished history -> continue history
      | `Passive log -> []
    in
    let history_opt, bookkeeping_opt = contents t in
    let time, message =
      Option.map history_opt ~f:(fun history ->
        let { log = {time; message}; previous_state } = history in
        (time, message))
      |> function
      | None -> passive_time t, None
      | Some (time, m) -> time, m in
    (`Time time, `Log message, `Info (dive t))

  let rec to_flat_list (t : t) =
    let make_item ?bookkeeping ~history name = 
        let { log; previous_state } = history in
        let bookkeeping_msg =
          Option.map bookkeeping ~f:(fun { plugin_name; run_parameters } ->
              fmt "[%s] Run-parameters: %d bytes" plugin_name
                (String.length run_parameters)) in
        (log.time, name, log.message, bookkeeping_msg)
        :: to_flat_list (previous_state :> t)
    in
    let name = name t in
    let history_opt, bookkeeping = contents t in
    match t with
    | `Passive log -> (* passive ! *)
      (log.time, name, log.message, None) :: []
    | other ->  
      let history =
        Option.value_exn history_opt ~msg:"non-passive got None history" in
      make_item ~history ?bookkeeping name

  let log ?depth t =
    to_flat_list t
    |> fun l ->
    begin match depth with
    | Some d -> List.take l d
    | None -> l
    end
    |> List.map ~f:(fun (time, name, msgopt, bookopt) ->
        Log.(s "* " % Time.log time % s ": " % s name
             % (match msgopt with None -> empty | Some m -> n % indent (s m))
             % (match bookopt with None -> empty | Some m -> n % indent (s m))))
    |> Log.(separate n)

  module Is = struct
    let building = function `Building _ -> true | _ -> false
    let tried_to_start = function `Tried_to_start _ -> true | _ -> false
    let started_running = function `Started_running _ -> true | _ -> false
    let starting = function `Starting _ -> true | _ -> false
    let still_building = function `Still_building _ -> true | _ -> false
    let still_running = function `Still_running _ -> true | _ -> false
    let ran_successfully = function `Ran_successfully _ -> true | _ -> false
    let successfully_did_nothing = function `Successfully_did_nothing _ -> true | _ -> false
    let active = function `Active _ -> true | _ -> false
    let tried_to_eval_condition = function `Tried_to_eval_condition _ -> true | _ -> false
    let verified_success = function `Verified_success _ -> true | _ -> false
    let already_done = function `Already_done _ -> true | _ -> false
    let dependencies_failed = function `Dependencies_failed _ -> true | _ -> false
    let failed_running = function `Failed_running _ -> true | _ -> false
    let failed_to_kill = function `Failed_to_kill _ -> true | _ -> false
    let failed_to_start = function `Failed_to_start _ -> true | _ -> false
    let failed_to_eval_condition = function `Failed_to_eval_condition _ -> true | _ -> false
    let killing = function `Killing _ -> true | _ -> false
    let tried_to_kill = function `Tried_to_kill _ -> true | _ -> false
    let did_not_ensure_condition = function `Did_not_ensure_condition _ -> true | _ -> false
    let killed = function `Killed _ -> true | _ -> false
    let finished = function `Finished _ -> true | _ -> false
    let passive = function `Passive _ -> true | _ -> false
  
    let killable = function
    | #killable_state -> true
    | _ -> false

    let finished_because_dependencies_died =
      function
      | `Finished {previous_state = (`Dependencies_failed _); _ } -> true
      | other -> false

  end

  module Count = struct
    module Latest = struct
      let make_counter ~continue t =
        let rec count v (t: t) =
          match continue t with
          | Some previous_state ->
            count (v + 1) (previous_state :> t)
          | _ -> v
        in
        count 0 t
      let tried_to_eval_condition (t: t) =
        make_counter ~continue:(function
          | `Tried_to_eval_condition { log; previous_state } -> Some previous_state
          | _ -> None) t
      let tried_to_reeval_condition (t: t) =
        make_counter ~continue:(function
          | `Tried_to_reeval_condition (_, { log; previous_state }) ->
            Some previous_state
          | _ -> None) t
      let tried_to_kill (t: t) =
        make_counter ~continue:(function
          | `Tried_to_kill { log; previous_state } -> Some previous_state
          | _ -> None) t
      let tried_to_start (t: t) =
        make_counter ~continue:(function
          | `Tried_to_start ({ log; previous_state }, _) -> Some previous_state
          | _ -> None) t
    end
    let consecutive_recent_attempts t =
      let (+-+) = max in
      let open Latest in
      tried_to_start t
      +-+ tried_to_kill t
      +-+ tried_to_eval_condition t
      +-+ tried_to_reeval_condition t
    (* let rec count v (t: t) = *)
    (*   match t with *)
    (*   | `Tried_to_eval_condition { log; previous_state } -> *)
    (*     count (v + 1) (previous_state :> t) *)
    (*   | _ -> v *)
    (* in *)
    (* count 0 t *)
  end

end