{ configuration ? import ../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>

# []: display all options
# [<option names>]: display the selected options
, displayOptions ? [
    "hardware.pcmcia.enable"
    "environment.systemPackages"
    "boot.kernelModules"
    "services.udev.packages"
    "jobs"
    "environment.etc"
    "system.activationScripts"
  ]
}:

# This file is used to generate a dot graph which contains all options and
# there dependencies to track problems and their sources.

let

  evalFun = {
    extraArgs ? {}
  }: import ../lib/eval-config.nix {
       modules = [ configuration ];
       inherit extraArgs;
     };

  eval = evalFun {};
  inherit (eval) pkgs;

  reportNewFailures = old: new: with pkgs.lib;
    let
      filterChanges =
        filter ({fst, snd}:
          !(fst.config.success -> snd.config.success)
        );

      keepNames =
        map ({fst, snd}:
          assert fst.name == snd.name; snd.name
        );
     in
       keepNames (
         filterChanges (
           zipLists (collect isOption old) (collect isOption new)
         )
       );


  # Create a list of modules where each module contains only one failling
  # options.
  introspectionModules = with pkgs.lib;
    let
      setIntrospection = opt: rec {
        name = opt.name;
        path = splitString "." opt.name;
        config = setAttrByPath path
          (throw "Usage introspection of '${name}' by forced failure.");
      };
    in
      map setIntrospection (collect isOption eval.options);

  overrideConfig = thrower:
    pkgs.lib.recursiveUpdateUntil (path: old: new:
      path == thrower.path
    ) eval.config thrower.config;


  graph = with pkgs.lib;
    map (thrower: {
      option = thrower.name;
      usedBy = reportNewFailures eval.options (evalFun {
        extraArgs = {
          config = overrideConfig thrower;
        };
      }).options;
    }) introspectionModules;

  graphToDot = graph: with pkgs.lib; ''
    digraph "Option Usages" {
      ${concatMapStrings ({option, usedBy}:
          assert __trace option true;
          if displayOptions == [] || elem option displayOptions then
            concatMapStrings (user: ''
              "${option}" -> "${user}"''
            ) usedBy
          else ""
        ) graph}
    }
  '';

in

pkgs.texFunctions.dot2pdf {
  dotGraph = pkgs.writeTextFile {
    name = "option_usages.dot";
    text = graphToDot graph;
  };
}
