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

with lib;

let
  cfg = config.services.namecoind;
  dataDir = "/var/lib/namecoind";
  useSSL = (cfg.rpc.certificate != null) && (cfg.rpc.key != null);
  useRPC = (cfg.rpc.user != null) && (cfg.rpc.password != null);

  listToConf = option: list: concatMapStrings (value: "${option}=${value}\n") list;

  configFile = pkgs.writeText "namecoin.conf" (
    ''
      server=1
      daemon=0
      txindex=1
      txprevcache=1
      walletpath=${cfg.wallet}
      gen=${if cfg.generate then "1" else "0"}
      ${listToConf "addnode" cfg.extraNodes}
      ${listToConf "connect" cfg.trustedNodes}
    ''
    + optionalString useRPC ''
      rpcbind=${cfg.rpc.address}
      rpcport=${toString cfg.rpc.port}
      rpcuser=${cfg.rpc.user}
      rpcpassword=${cfg.rpc.password}
      ${listToConf "rpcallowip" cfg.rpc.allowFrom}
    ''
    + optionalString useSSL ''
      rpcssl=1
      rpcsslcertificatechainfile=${cfg.rpc.certificate}
      rpcsslprivatekeyfile=${cfg.rpc.key}
      rpcsslciphers=TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH
    ''
  );

in

{

  ###### interface

  options = {

    services.namecoind = {

      enable = mkEnableOption "namecoind, Namecoin client";

      wallet = mkOption {
        type = types.path;
        default = "${dataDir}/wallet.dat";
        description = ''
          Wallet file. The ownership of the file has to be
          namecoin:namecoin, and the permissions must be 0640.
        '';
      };

      generate = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to generate (mine) Namecoins.
        '';
      };

      extraNodes = mkOption {
        type = types.listOf types.str;
        default = [ ];
        description = ''
          List of additional peer IP addresses to connect to.
        '';
      };

      trustedNodes = mkOption {
        type = types.listOf types.str;
        default = [ ];
        description = ''
          List of the only peer IP addresses to connect to. If specified
          no other connection will be made.
        '';
      };

      rpc.user = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = ''
          User name for RPC connections.
        '';
      };

      rpc.password = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = ''
          Password for RPC connections.
        '';
      };

      rpc.address = mkOption {
        type = types.str;
        default = "0.0.0.0";
        description = ''
          IP address the RPC server will bind to.
        '';
      };

      rpc.port = mkOption {
        type = types.port;
        default = 8332;
        description = ''
          Port the RPC server will bind to.
        '';
      };

      rpc.certificate = mkOption {
        type = types.nullOr types.path;
        default = null;
        example = "/var/lib/namecoind/server.cert";
        description = ''
          Certificate file for securing RPC connections.
        '';
      };

      rpc.key = mkOption {
        type = types.nullOr types.path;
        default = null;
        example = "/var/lib/namecoind/server.pem";
        description = ''
          Key file for securing RPC connections.
        '';
      };

      rpc.allowFrom = mkOption {
        type = types.listOf types.str;
        default = [ "127.0.0.1" ];
        description = ''
          List of IP address ranges allowed to use the RPC API.
          Wiledcards (*) can be user to specify a range.
        '';
      };

    };

  };

  ###### implementation

  config = mkIf cfg.enable {

    users.users.namecoin = {
      uid = config.ids.uids.namecoin;
      description = "Namecoin daemon user";
      home = dataDir;
      createHome = true;
    };

    users.groups.namecoin = {
      gid = config.ids.gids.namecoin;
    };

    systemd.services.namecoind = {
      description = "Namecoind daemon";
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];

      startLimitIntervalSec = 120;
      startLimitBurst = 5;
      serviceConfig = {
        User = "namecoin";
        Group = "namecoin";
        ExecStart = "${pkgs.namecoind}/bin/namecoind -conf=${configFile} -datadir=${dataDir} -printtoconsole";
        ExecStop = "${pkgs.coreutils}/bin/kill -KILL $MAINPID";
        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
        Nice = "10";
        PrivateTmp = true;
        TimeoutStopSec = "60s";
        TimeoutStartSec = "2s";
        Restart = "always";
      };

      preStart = optionalString (cfg.wallet != "${dataDir}/wallet.dat") ''
        # check wallet file permissions
        if [ "$(stat --printf '%u' ${cfg.wallet})" != "${toString config.ids.uids.namecoin}" \
           -o "$(stat --printf '%g' ${cfg.wallet})" != "${toString config.ids.gids.namecoin}" \
           -o "$(stat --printf '%a' ${cfg.wallet})" != "640" ]; then
           echo "ERROR: bad ownership or rights on ${cfg.wallet}" >&2
           exit 1
        fi
      '';

    };

  };

  meta.maintainers = with lib.maintainers; [ rnhmjoj ];

}
