(*
 * Copyright (c) 2013-2020 Thomas Gazagnaire <thomas@gazagnaire.org>
 * Copyright (c) 2013-2020 Anil Madhavapeddy <anil@recoil.org>
 * Copyright (c) 2015-2020 Gabriel Radanne <drupyog@zoho.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *)

(** Signature for functoria devices. A [device] is a module implementation which
    contains a runtime state which can be set either at configuration time (by
    the application builder) or at runtime, using command-line arguments. *)

type ('a, 'b) t
(** The type for devices whose runtime state is of type ['a] and having extra
    data-dependencies of type ['b]. *)

val module_type : ('a, 'b) t -> 'a Type.t
(** [module_type t] is [t]'s module type. *)

val module_name : ('a, 'b) t -> string
(** [module_name t] is [t]'s module name. *)

val packages : ('a, 'b) t -> Package.t list Key.value
(** [packages t] is the list of OPAM packages that are needed by [t].*)

val local_libs : ('a, 'b) t -> string list
(** [local_libs t] is the list of local libraries that are needed by [t]. *)

val install : ('a, 'b) t -> Info.t -> Install.t Key.value
(** [install t i] is the list of files installed by [t], using the build
    information [i]. *)

val extra_deps : ('a, 'b) t -> 'b list
(** [extra_deps t] is the list of dependencies that be initialized before
    running the code generated by [connect t]. *)

val id : ('a, 'b) t -> int
(** [id t] is [t]'s unique identifier. Freshly generated for each call to {!v}.
*)

val pp : 'b Fmt.t -> ('a, 'b) t Fmt.t
(** [pp pp_dep] is the pretty-printer for devices, using [pp_dep] to
    pretty-print the extra data-dependencies. *)

val equal : ('a, 'b) t -> ('c, 'd) t -> bool
(** [equal] is the equality function for devices. *)

val witness : ('a, _) t -> ('b, _) t -> ('a, 'b) Typeid.witness
(** [witness a b] provides an equality witness. *)

val hash : ('a, 'b) t -> int
(** [hash t] is [t]'s hash. *)

(** {1 Resources} *)

val files : ('a, 'b) t -> Info.t -> Fpath.Set.t
(** [files t info s] is the list of files generated configure-time. *)

val keys : ('a, 'b) t -> Key.t list
(** [keys t] is the list of keys which can be used to configure [t]. *)

val runtime_args : ('a, 'b) t -> Runtime_arg.t list
(** [runtime_args t] is the list of command-line arguments which can be used to
    configure [t] at runtime. *)

(** {1 Code Generation} *)

type 'a code = private {
  pos : (string * int * int * int) option;
  code : string;
}
(** The type for fragments of code of type ['a]. *)

val code :
  pos:string * int * int * int ->
  ('a, Format.formatter, unit, 'b code) format4 ->
  'a
(** Generate localised code. *)

val connect : ('a, 'b) t -> Info.t -> string -> string list -> 'a code
(** [connect t info impl_name args] is the code to execute in order to create a
    new state (usually calling [<module_name t>.connect]) with the arguments
    [args], in the context of the project information [info]. The freshly
    created state will be made available in [var_name t] *)

val start : ?pos:string * int * int * int -> string -> string list -> 'a code
(** [start ?pos impl_name args] is the code [<impl_name>.start <args>]. *)

val nice_name : _ t -> string
(** [nice_name d] provides a identifier unique to [d] which is a valid OCaml
    identifier. *)

(** {1 Actions} *)
val dune : ('a, 'b) t -> Info.t -> Dune.stanza list
(** [dune t info] are the dune stanza which needs to be generated to build the
    application. *)

(** {1 Configuration} *)

val configure : ('a, 'b) t -> Info.t -> unit Action.t
(** [configure t info] is configure hook for [t] the device and the files it
    generates. During the configure phase, you cannot rely on [packages t] being
    installed.

    To run code during the [build] phase, generate a [dune] fragment instead. *)

(** {1 Constructors} *)

val v :
  ?packages:Package.t list ->
  ?packages_v:Package.t list Key.value ->
  ?local_libs:string list ->
  ?install:(Info.t -> Install.t) ->
  ?install_v:(Info.t -> Install.t Key.value) ->
  ?keys:Key.t list ->
  ?runtime_args:Runtime_arg.t list ->
  ?extra_deps:'b list ->
  ?connect:(Info.t -> string -> string list -> 'a code) ->
  ?dune:(Info.t -> Dune.stanza list) ->
  ?configure:(Info.t -> unit Action.t) ->
  ?files:(Info.t -> Fpath.t list) ->
  string ->
  'a Type.t ->
  ('a, 'b) t

val extend :
  ?packages:Package.t list ->
  ?packages_v:Package.t list Key.value ->
  ?dune:(Info.t -> Dune.stanza list) ->
  ?pre_configure:(Info.t -> unit Action.t) ->
  ?post_configure:(Info.t -> unit Action.t) ->
  ?files:(Info.t -> Fpath.t list) ->
  ('a, 'b) t ->
  ('a, 'b) t

(** {1 Device graphs} *)

module Graph : sig
  type ('a, 'i) device

  (** A graph of devices, annotated with their arguments, dependencies, and a
      unique identifier.

      Warning: this is truly a DAG: sharing {b must} be preserved. Manual walks
      are discouraged, please use {!fold} instead. *)
  type t =
    | D : { dev : (_, _) device; args : t list; deps : t list; id : int } -> t

  val fold : (t -> 'a -> 'a) -> t -> 'a -> 'a
  (** [fold f g z] applies [f] on each device in topological order. *)

  val var_name : t -> string
  (** [var_name t] returns the name identifying [t] which is a valid OCaml
      variable identifier. *)

  val impl_name : t -> string
  (** [impl_name t] returns the name identifying [t]'s module implementation. *)
end
with type ('a, 'i) device := ('a, 'i) t
