# Copyright Tom Hubrecht, (2023)
#
#  Tom Hubrecht <tom@hubrecht.ovh>
#
#  This software is governed by the CeCILL  license under French law and
#  abiding by the rules of distribution of free software.  You can  use,
#  modify and/ or redistribute the software under the terms of the CeCILL
#  license as circulated by CEA, CNRS and INRIA at the following URL
#  "http://www.cecill.info".
#
#  As a counterpart to the access to the source code and  rights to copy,
#  modify and redistribute granted by the license, users are provided only
#  with a limited warranty  and the software's author,  the holder of the
#  economic rights,  and the successive licensors  have only  limited
#  liability.
#
#  In this respect, the user's attention is drawn to the risks associated
#  with loading,  using,  modifying and/or developing or reproducing the
#  software by the user in light of its specific status of free software,
#  that may mean  that it is complicated to manipulate,  and  that  also
#  therefore means  that it is reserved for developers  and  experienced
#  professionals having in-depth computer knowledge. Users are therefore
#  encouraged to load and test the software's suitability as regards their
#  requirements in conditions enabling the security of their systems and/or
#  data to be ensured and,  more generally, to use and operate it in the
#  same conditions as regards security.
#
#  The fact that you are presently reading this means that you have had
#  knowledge of the CeCILL license and that you accept its terms.

let
  # Reimplement optional functions
  _optional =
    default: b: value:
    if b then value else default;
in

rec {
  inherit (import ./nixpkgs.nix)
    flip
    hasPrefix
    recursiveUpdate
    splitString
    unique
    ;

  /*
    Fuses a list of attribute sets into a single attribute set.

    Type: [attrs] -> attrs

     Example:
       x = [ { a = 1; } { b = 2; } ]
       fuseAttrs x
       => { a = 1; b = 2; }
  */
  fuseAttrs = builtins.foldl' (attrs: x: attrs // x) { };

  fuseValueAttrs = attrs: fuseAttrs (builtins.attrValues attrs);

  /*
    Applies a function to `attrsList` before fusing the resulting list
    of attribute sets.

    Type: ('a -> attrs) -> ['a] -> attrs

    Example:
     x = [ "to" "ta" "ti" ]
     f = s: { ${s} = s + s; }
     mapFuse f x
     => { to = "toto"; ta = "tata"; ti = "titi"; }
  */
  mapFuse =
    # 'a -> attrs
    f:
    # ['a]
    attrsList:
    fuseAttrs (builtins.map f attrsList);

  /*
    Equivalent of lib.singleton but for an attribute set.

    Type: str -> 'a -> attrs

     Example:
       singleAttr "a" 1
       => { a = 1; }
  */
  singleAttr = name: value: { ${name} = value; };

  # Enables a list of modules.
  enableAttrs' =
    enable:
    mapFuse (m: {
      ${m}.${enable} = true;
    });

  enableModules = enableAttrs' "enable";

  /*
    Create an attribute set from a list of values, mapping those
    values through the function `f`.

    Example:
     mapSingleFuse (x: "val-${x}") [ "a" "b" ]
     => { a = "val-a"; b = "val-b" }
  */
  mapSingleFuse = f: mapFuse (x: singleAttr x (f x));

  /*
    Creates a relative path as a string

    Type: path -> str -> path

     Example:
       mkRel /home/test/ "file.txt"
       => "/home/test/file.txt"
  */
  mkRel = path: file: path + "/${file}";

  setDefault =
    default:
    mapFuse (name: {
      ${name} = default;
    });

  mkBaseSecrets =
    root:
    mapFuse (secret: {
      ${secret}.file = mkRel root secret;
    });

  getSecrets = dir: builtins.attrNames (import (mkRel dir "secrets.nix"));

  subAttr = attrs: name: attrs.${name};

  subAttrs = attrs: builtins.map (subAttr attrs);

  optionalList = _optional [ ];

  optionalAttrs = _optional { };

  optionalString = _optional "";
  /*
    Same as fuseAttrs but using `lib.recursiveUpdate` to merge attribute
    sets together.

    Type: [attrs] -> attrs
  */
  recursiveFuse = builtins.foldl' recursiveUpdate { };

  mkImport =
    root: file:
    let
      path = mkRel root file;
    in
    path + (optionalString (!(builtins.pathExists path)) ".nix");

  mkImports = root: builtins.map (mkImport root);

  /*
    Creates a confugiration by merging enabled modules,
    services and extraConfig.

    Example:
     mkConfig {
       enabledModules = [ "ht-defaults" ];
       enabledServices = [ "toto" ];
       extraConfig = { services.nginx.enable = true; };
       root = ./.;
     }
     =>
     {
       imports = [ ./toto ];
       ht-defaults.enable = true;
       services.nginx.enable = true;
     }
  */
  mkConfig =
    {
      # List of modules to enable with `enableModules`
      enabledModules,
      # List of services to import
      enabledServices,
      # Extra configuration, defaults to `{ }`
      extraConfig ? { },
      # Path relative to which the enabled services will be imported
      root,
    }:
    recursiveFuse [
      (enableModules enabledModules)

      { imports = mkImports root ([ "_hardware-configuration" ] ++ enabledServices); }

      extraConfig
    ];
}