{ config, lib, pkgs, ... }:

with lib;

let

  cfg = config.services.prosody;

  sslOpts = { ... }: {

    options = {

      # TODO: require attribute
      key = mkOption {
        type = types.str;
        description = "Path to the key file";
      };

      # TODO: require attribute
      cert = mkOption {
        type = types.str;
        description = "Path to the certificate file";
      };
    };
  };

  moduleOpts = {

    roster = mkOption {
      default = true;
      description = "Allow users to have a roster";
    };

    saslauth = mkOption {
      default = true;
      description = "Authentication for clients and servers. Recommended if you want to log in.";
    };

    tls = mkOption {
      default = true;
      description = "Add support for secure TLS on c2s/s2s connections";
    };

    dialback = mkOption {
      default = true;
      description = "s2s dialback support";
    };

    disco = mkOption {
      default = true;
      description = "Service discovery";
    };

    legacyauth = mkOption {
      default = true;
      description = "Legacy authentication. Only used by some old clients and bots";
    };

    version = mkOption {
      default = true;
      description = "Replies to server version requests";
    };

    uptime = mkOption {
      default = true;
      description = "Report how long server has been running";
    };

    time = mkOption {
      default = true;
      description = "Let others know the time here on this server";
    };

    ping = mkOption {
      default = true;
      description = "Replies to XMPP pings with pongs";
    };

    console = mkOption {
      default = false;
      description = "telnet to port 5582";
    };

    bosh = mkOption {
      default = false;
      description = "Enable BOSH clients, aka 'Jabber over HTTP'";
    };

    httpserver = mkOption {
      default = false;
      description = "Serve static files from a directory over HTTP";
    };

  };

  createSSLOptsStr = o:
    if o ? key && o ? cert then
      ''ssl = { key = "${o.key}"; certificate = "${o.cert}"; };''
    else "";

  vHostOpts = { ... }: {

    options = {

      # TODO: require attribute
      domain = mkOption {
        type = types.str;
        description = "Domain name";
      };

      enabled = mkOption {
        default = false;
        description = "Whether to enable the virtual host";
      };

      ssl = mkOption {
        description = "Paths to SSL files";
        default = null;
        options = [ sslOpts ];
      };

      extraConfig = mkOption {
        default = '''';
        description = "Additional virtual host specific configuration";
      };

    };

  };

in

{

  ###### interface

  options = {

    services.prosody = {

      enable = mkOption {
        default = false;
        description = "Whether to enable the prosody server";
      };

      allowRegistration = mkOption {
        default = false;
        description = "Allow account creation";
      };

      modules = moduleOpts;

      extraModules = mkOption {
        description = "Enable custom modules";
        default = [];
      };

      virtualHosts = mkOption {

        description = "Define the virtual hosts";

        type = types.loaOf types.optionSet;

        example = {
          myhost = {
            domain = "my-xmpp-example-host.org";
            enabled = true;
          };
        };

        default = {
          localhost = {
            domain = "localhost";
            enabled = true;
          };
        };

        options = [ vHostOpts ];
      };

      ssl = mkOption {
        description = "Paths to SSL files";
        default = null;
        options = [ sslOpts ];
      };

      admins = mkOption {
        description = "List of administrators of the current host";
        example = [ "admin1@example.com" "admin2@example.com" ];
        default = [];
      };

      extraConfig = mkOption {
        default = '''';
        description = "Additional prosody configuration";
      };

    };
  };


  ###### implementation

  config = mkIf cfg.enable {

    environment.systemPackages = [ pkgs.prosody ];

    environment.etc."prosody/prosody.cfg.lua".text = ''

      pidfile = "/var/lib/prosody/prosody.pid"


      log = "*syslog"

      data_path = "/var/lib/prosody"

      allow_registration = ${ if cfg.allowRegistration then "true" else "false" };

      ${ optionalString cfg.modules.console "console_enabled = true;" }

      ${ optionalString  (cfg.ssl != null) (createSSLOptsStr cfg.ssl) }

      admins = { ${lib.concatStringsSep ", " (map (n: "\"${n}\"") cfg.admins) } };

      modules_enabled = {

        ${ lib.concatStringsSep "\n\ \ " (lib.mapAttrsToList
          (name: val: optionalString val ''"${name}";'')
        cfg.modules) }

        ${ optionalString cfg.allowRegistration "\"register\"\;" }

        ${ lib.concatStringsSep "\n" (map (x: "\"${x}\";") cfg.extraModules)}

        "posix";
      };

      ${ cfg.extraConfig }

      ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
        VirtualHost "${v.domain}"
          enabled = ${if v.enabled then "true" else "false"};
          ${ optionalString (v.ssl != null) (createSSLOptsStr v.ssl) }
          ${ v.extraConfig }
        '') cfg.virtualHosts) }
    '';

    users.extraUsers.prosody = {
      uid = config.ids.uids.prosody;
      description = "Prosody user";
      createHome = true;
      group = "prosody";
      home = "/var/lib/prosody";
    };

    users.extraGroups.prosody = {
      gid = config.ids.gids.prosody;
    };

    systemd.services.prosody = {

      description = "Prosody XMPP server";
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        User = "prosody";
        PIDFile = "/var/lib/prosody/prosody.pid";
        ExecStart = "${pkgs.prosody}/bin/prosodyctl start";
      };

    };

  };

}
