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

let
  inherit (lib) concatStrings singleton;

  tinc = cfg.tincPackage;
  hostsType = builtins.typeOf cfg.hosts;
  hosts =
    if hostsType == "package" then
      # use package as is
      cfg.hosts
    else if hostsType == "path" then
      # use path to generate a package
      pkgs.stdenv.mkDerivation {
        name = "custom-retiolum-hosts";
        src = cfg.hosts;
        installPhase = ''
          mkdir $out
          find . -name .git -prune -o -type f -print0 | xargs -0 cp --target-directory $out
        '';
      }
    else
      abort "The option `services.retiolum.hosts' must be set to a package or a path"
    ;
  iproute = cfg.iproutePackage;

  retiolumExtraHosts = import (pkgs.runCommand "retiolum-etc-hosts"
    { }
    ''
      generate() {
        (cd ${hosts}
          printf \'\'
          for i in `ls`; do
            names=$(hostnames $i)
            for j in `sed -En 's|^ *Aliases *= *(.+)|\1|p' $i`; do
              names="$names $(hostnames $j)"
            done
            sed -En '
              s|^ *Subnet *= *([^ /]*)(/[0-9]*)? *$|\1  '"$names"'|p
            ' $i
          done | sort
          printf \'\'
        )
      }

      case ${cfg.generateEtcHosts} in
        short)
          hostnames() { echo "$1"; }
          generate
          ;;
        long)
          hostnames() { echo "$1.${cfg.network}"; }
          generate
          ;;
        both)
          hostnames() { echo "$1.${cfg.network} $1"; }
          generate
          ;;
        *)
          echo '""'
          ;;
      esac > $out
    '');


  confDir = pkgs.runCommand "retiolum" {
    # TODO text
    executable = true;
    preferLocalBuild = true;
  } ''
    set -euf

    mkdir -p $out

    ln -s ${hosts} $out/hosts

    cat > $out/tinc.conf <<EOF
    Name = ${cfg.name}
    Device = /dev/net/tun
    Interface = ${cfg.network}
    ${concatStrings (map (c : "ConnectTo = " + c + "\n") cfg.connectTo)}
    PrivateKeyFile = ${cfg.privateKeyFile}
    EOF

    # source: krebscode/painload/retiolum/scripts/tinc_setup/tinc-up
    cat > $out/tinc-up <<EOF
    host=$out/hosts/${cfg.name}
    ${iproute}/sbin/ip link set \$INTERFACE up

    addr4=\$(sed -n 's|^ *Subnet *= *\(10[.][^ ]*\) *$|\1|p' \$host)
    if [ -n "\$addr4" ];then
        ${iproute}/sbin/ip -4 addr add \$addr4 dev \$INTERFACE
        ${iproute}/sbin/ip -4 route add 10.243.0.0/16 dev \$INTERFACE
    fi
    addr6=\$(sed -n 's|^ *Subnet *= *\(42[:][^ ]*\) *$|\1|p' \$host)
    ${iproute}/sbin/ip -6 addr add \$addr6 dev \$INTERFACE
    ${iproute}/sbin/ip -6 route add 42::/16 dev \$INTERFACE
    EOF

    chmod +x $out/tinc-up
  '';


  user = cfg.network + "-tinc";

in

{
  environment.systemPackages = [ tinc hosts iproute ];

  networking.extraHosts = retiolumExtraHosts;

  systemd.services.retiolum = {
    description = "Tinc daemon for Retiolum";
    after = [ "network.target" ];
    wantedBy = [ "multi-user.target" ];
    path = [ tinc iproute ];
    serviceConfig = {
      # TODO we cannot chroot (-R) b/c we use symlinks to hosts
      #      and the private key.
      ExecStart = "${tinc}/sbin/tincd -c ${confDir} -d 0 -U ${user} -D";
      SyslogIdentifier = "retiolum-tincd";
    };
    restartIfChanged = true;
  };

  users.extraUsers = singleton {
    name = user;
    uid = 2961822815; # bin/genid retiolum-tinc
  };
}