{ config, lib, pkgs, ... }:
let
  inherit (lib) mkEnableOption mkIf mkOption mkMerge literalExpression;
  inherit (lib) mapAttrsToList filterAttrs unique recursiveUpdate types;

  mkValueStringArmagetron = with lib; v:
    if isInt v then toString v
    else if isFloat v then toString v
    else if isString v then v
    else if true == v then "1"
    else if false == v then "0"
    else if null == v then ""
    else throw "unsupported type: ${builtins.typeOf v}: ${(lib.generators.toPretty {} v)}";

  settingsFormat = pkgs.formats.keyValue {
    mkKeyValue = lib.generators.mkKeyValueDefault
      {
        mkValueString = mkValueStringArmagetron;
      } " ";
    listsAsDuplicateKeys = true;
  };

  cfg = config.services.armagetronad;
  enabledServers = lib.filterAttrs (n: v: v.enable) cfg.servers;
  nameToId = serverName: "armagetronad-${serverName}";
  getStateDirectory = serverName: "armagetronad/${serverName}";
  getServerRoot = serverName: "/var/lib/${getStateDirectory serverName}";
in
{
  options = {
    services.armagetronad = {
      servers = mkOption {
        description = "Armagetron server definitions.";
        default = { };
        type = types.attrsOf (types.submodule {
          options = {
            enable = mkEnableOption "armagetronad";

            package = lib.mkPackageOption pkgs "armagetronad-dedicated" {
              example = ''
                pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated
              '';
              extraDescription = ''
                Ensure that you use a derivation which contains the path `bin/armagetronad-dedicated`.
              '';
            };

            host = mkOption {
              type = types.str;
              default = "0.0.0.0";
              description = "Host to listen on. Used for SERVER_IP.";
            };

            port = mkOption {
              type = types.port;
              default = 4534;
              description = "Port to listen on. Used for SERVER_PORT.";
            };

            dns = mkOption {
              type = types.nullOr types.str;
              default = null;
              description = "DNS address to use for this server. Optional.";
            };

            openFirewall = mkOption {
              type = types.bool;
              default = true;
              description = "Set to true to open the configured UDP port for Armagetron Advanced.";
            };

            name = mkOption {
              type = types.str;
              description = "The name of this server.";
            };

            settings = mkOption {
              type = settingsFormat.type;
              default = { };
              description = ''
                Armagetron Advanced server rules configuration. Refer to:
                <https://wiki.armagetronad.org/index.php?title=Console_Commands>
                or `armagetronad-dedicated --doc` for a list.

                This attrset is used to populate `settings_custom.cfg`; see:
                <https://wiki.armagetronad.org/index.php/Configuration_Files>
              '';
              example = literalExpression ''
                {
                  CYCLE_RUBBER = 40;
                }
              '';
            };

            roundSettings = mkOption {
              type = settingsFormat.type;
              default = { };
              description = ''
                Armagetron Advanced server per-round configuration. Refer to:
                <https://wiki.armagetronad.org/index.php?title=Console_Commands>
                or `armagetronad-dedicated --doc` for a list.

                This attrset is used to populate `everytime.cfg`; see:
                <https://wiki.armagetronad.org/index.php/Configuration_Files>
              '';
              example = literalExpression ''
                {
                  SAY = [
                    "Hosted on NixOS"
                    "https://nixos.org"
                    "iD Tech High Rubber rul3z!! Happy New Year 2008!!1"
                  ];
                }
              '';
            };
          };
        });
      };
    };
  };

  config = mkIf (enabledServers != { }) {
    systemd.tmpfiles.settings = mkMerge (mapAttrsToList
      (serverName: serverCfg:
        let
          serverId = nameToId serverName;
          serverRoot = getServerRoot serverName;
          serverInfo = (
            {
              SERVER_IP = serverCfg.host;
              SERVER_PORT = serverCfg.port;
              SERVER_NAME = serverCfg.name;
            } // (lib.optionalAttrs (serverCfg.dns != null) { SERVER_DNS = serverCfg.dns; })
          );
          customSettings = serverCfg.settings;
          everytimeSettings = serverCfg.roundSettings;

          serverInfoCfg = settingsFormat.generate "server_info.${serverName}.cfg" serverInfo;
          customSettingsCfg = settingsFormat.generate "settings_custom.${serverName}.cfg" customSettings;
          everytimeSettingsCfg = settingsFormat.generate "everytime.${serverName}.cfg" everytimeSettings;
        in
        {
          "10-armagetronad-${serverId}" = {
            "${serverRoot}/data" = {
              d = {
                group = serverId;
                user = serverId;
                mode = "0750";
              };
            };
            "${serverRoot}/settings" = {
              d = {
                group = serverId;
                user = serverId;
                mode = "0750";
              };
            };
            "${serverRoot}/var" = {
              d = {
                group = serverId;
                user = serverId;
                mode = "0750";
              };
            };
            "${serverRoot}/resource" = {
              d = {
                group = serverId;
                user = serverId;
                mode = "0750";
              };
            };
            "${serverRoot}/input" = {
              "f+" = {
                group = serverId;
                user = serverId;
                mode = "0640";
              };
            };
            "${serverRoot}/settings/server_info.cfg" = {
              "L+" = {
                argument = "${serverInfoCfg}";
              };
            };
            "${serverRoot}/settings/settings_custom.cfg" = {
              "L+" = {
                argument = "${customSettingsCfg}";
              };
            };
            "${serverRoot}/settings/everytime.cfg" = {
              "L+" = {
                argument = "${everytimeSettingsCfg}";
              };
            };
          };
        }
      )
      enabledServers
    );

    systemd.services = mkMerge (mapAttrsToList
      (serverName: serverCfg:
        let
          serverId = nameToId serverName;
        in
        {
          "armagetronad-${serverName}" = {
            description = "Armagetron Advanced Dedicated Server for ${serverName}";
            wants = [ "basic.target" ];
            after = [ "basic.target" "network.target" "multi-user.target" ];
            wantedBy = [ "multi-user.target" ];
            serviceConfig =
              let
                serverRoot = getServerRoot serverName;
              in
              {
                Type = "simple";
                StateDirectory = getStateDirectory serverName;
                ExecStart = "${lib.getExe serverCfg.package} --daemon --input ${serverRoot}/input --userdatadir ${serverRoot}/data --userconfigdir ${serverRoot}/settings --vardir ${serverRoot}/var --autoresourcedir ${serverRoot}/resource";
                Restart = "on-failure";
                CapabilityBoundingSet = "";
                LockPersonality = true;
                NoNewPrivileges = true;
                PrivateDevices = true;
                PrivateTmp = true;
                PrivateUsers = true;
                ProtectClock = true;
                ProtectControlGroups = true;
                ProtectHome = true;
                ProtectHostname = true;
                ProtectKernelLogs = true;
                ProtectKernelModules = true;
                ProtectKernelTunables = true;
                ProtectProc = "invisible";
                ProtectSystem = "strict";
                RestrictNamespaces = true;
                RestrictSUIDSGID = true;
                User = serverId;
                Group = serverId;
              };
          };
        })
      enabledServers
    );

    networking.firewall.allowedUDPPorts =
      unique (mapAttrsToList (serverName: serverCfg: serverCfg.port) (filterAttrs (serverName: serverCfg: serverCfg.openFirewall) enabledServers));

    users.users = mkMerge (mapAttrsToList
      (serverName: serverCfg:
        {
          ${nameToId serverName} = {
            group = nameToId serverName;
            description = "Armagetron Advanced dedicated user for server ${serverName}";
            isSystemUser = true;
          };
        })
      enabledServers
    );

    users.groups = mkMerge (mapAttrsToList
      (serverName: serverCfg:
        {
          ${nameToId serverName} = { };
        })
      enabledServers
    );
  };
}
