{config, pkgs, lib, ...}:
let
  cfg = config.services.nghttpx;

  # renderHost :: Either ServerOptions Path -> String
  renderHost = server:
    if builtins.isString server
    then "unix://${server}"
    else "${server.host},${builtins.toString server.port}";

  # Filter out submodule parameters whose value is null or false or is
  # the key _module.
  #
  # filterParams :: ParamsSubmodule -> ParamsSubmodule
  filterParams = p:
    lib.filterAttrs
      (n: v: ("_module" != n) && (null != v) && (false != v))
      (lib.optionalAttrs (null != p) p);

  # renderBackend :: BackendSubmodule -> String
  renderBackend = backend:
    let
      host = renderHost backend.server;
      patterns = lib.concatStringsSep ":" backend.patterns;

      # Render a set of backend parameters, this is somewhat
      # complicated because nghttpx backend patterns can be entirely
      # omitted and the params may be given as a mixed collection of
      # 'key=val' pairs or atoms (e.g: 'proto=h2;tls')
      params =
        lib.mapAttrsToList
          (n: v:
            if builtins.isBool v
            then n
            else if builtins.isString v
            then "${n}=${v}"
            else "${n}=${builtins.toString v}")
          (filterParams backend.params);

      # NB: params are delimited by a ";" which is the same delimiter
      # to separate the host;[pattern];[params] sections of a backend
      sections =
        builtins.filter (e: "" != e) ([
          host
          patterns
        ]++params);
      formattedSections = lib.concatStringsSep ";" sections;
    in
      "backend=${formattedSections}";

  # renderFrontend :: FrontendSubmodule -> String
  renderFrontend = frontend:
    let
      host   = renderHost frontend.server;
      params0 =
        lib.mapAttrsToList
          (n: v: if builtins.isBool v then n else v)
          (filterParams frontend.params);

      # NB: nghttpx doesn't accept "tls", you must omit "no-tls" for
      # the default behavior of turning on TLS.
      params1 = lib.remove "tls" params0;

      sections          = [ host] ++ params1;
      formattedSections = lib.concatStringsSep ";" sections;
    in
      "frontend=${formattedSections}";

  configurationFile = pkgs.writeText "nghttpx.conf" ''
    ${lib.optionalString (null != cfg.tls) ("private-key-file="+cfg.tls.key)}
    ${lib.optionalString (null != cfg.tls) ("certificate-file="+cfg.tls.crt)}

    user=nghttpx

    ${lib.concatMapStringsSep "\n" renderFrontend cfg.frontends}
    ${lib.concatMapStringsSep "\n" renderBackend  cfg.backends}

    backlog=${builtins.toString cfg.backlog}
    backend-address-family=${cfg.backend-address-family}

    workers=${builtins.toString cfg.workers}
    rlimit-nofile=${builtins.toString cfg.rlimit-nofile}

    ${lib.optionalString cfg.single-thread "single-thread=yes"}
    ${lib.optionalString cfg.single-process "single-process=yes"}

    ${cfg.extraConfig}
  '';
in
{ imports = [
    ./nghttpx-options.nix
  ];

  config = lib.mkIf cfg.enable {

    users.groups.nghttpx = { };
    users.users.nghttpx = {
      group = config.users.groups.nghttpx.name;
      isSystemUser = true;
    };


    systemd.services = {
      nghttpx = {
        wantedBy = [ "multi-user.target" ];
        after    = [ "network.target" ];
        script   = ''
          ${pkgs.nghttp2}/bin/nghttpx --conf=${configurationFile}
        '';

        serviceConfig = {
          Restart    = "on-failure";
          RestartSec = 60;
        };
      };
    };
  };
}
