{ config, pkgs, ... }:

let
  inherit (pkgs) lib;
in rec {
  imports =
    [ ./musnix
      ./bragi/hw.nix
      ./custom/zsh.nix
      ./users.nix
      ./custom/unit-status-mail.nix
      ./custom/trivmix-service.nix
    ];

  boot.loader.grub.enable = true;
  boot.loader.grub.version = 2;

  boot.kernelModules = [ "usblp" ];

  boot.tmpOnTmpfs = true;

  boot.supportedFilesystems = [ "cifs" ];

  networking.hostName = "bragi";
  networking.hostId = "2af11085";
  networking.wireless.enable = true;

  nixpkgs.config.packageOverrides = oldPkgs:
    rec {
      haskellPackages = oldPkgs.haskellPackages.override {
        overrides = self: super: rec {
          trivmix = self.callPackage ./custom/trivmix/trivmix.nix {};
          inherit
            (lib.mapAttrs (name: oldPkgs.haskell.lib.dontCheck) super)
            Glob filelock;
          inherit
            (self.callPackage ./custom/thermoprint { inherit (self) Glob; inherit (pkgs) runCommand makeWrapper; extraPackages = (p: with p; [ persistent-postgresql ]); })
            thermoprint-spec thermoprint-bbcode thermoprint-client thermoprint-server thermoprint-webgui tprint bbcode;
        };
      };

      jack2Full = oldPkgs.jack2Full.override { dbus = null; };

      mpd = oldPkgs.mpd.override { gmeSupport = false; pulseaudioSupport = false; };
      
      inherit (haskellPackages) trivmix thermoprint-server thermoprint-webgui tprint;
    };

  nixpkgs.config.allowUnfree = true;

  environment.systemPackages = with pkgs; [
    git
    mosh
    rsync
    tmux
    nfs-utils
    jack2Full
    trivmix
    zsh
    tprint
    samba
  ];

  # List services that you want to enable:

  services.openssh = {
    enable = true;
  };

  services.fcron = {
    enable = true;
    systab = ''
      %weekly * * nix-collect-garbage --delete-older-than '7d'
    '';
  };

  systemd.automounts = [
    {
      wantedBy = [ "multi-user.target" ];
      where = "/media/odin";
    }
    {
      wantedBy = [ "multi-user.target" ];
      where = "/media/dellingr";
      automountConfig.TimoutIdleSec = "30s";
    }
    {
      wantedBy = [ "multi-user.target" ];
      where = "/media/vali";
      automountConfig.TimoutIdleSec = "5min";
    }
  ];

  systemd.mounts = [
    {
      what = "odin.asgard.yggdrasil:/srv/media";
      where = "/media/odin";
      type = "nfs";
      options = "ro";
    }
    {
      what = "/dev/disk/by-uuid/6436-3432";
      where = "/media/dellingr";
      type = "vfat";
    }
    {
      what = "//VALI/Public";
      where = "/media/vali";
      type = "cifs";
      options = "guest,dir_mode=0777,file_mode=0666,nounix,iocharset=utf8,sec=none";
    }
  ];

  systemd.globalEnvironment = {
    JACK_PROMISCUOUS_SERVER = "1";
  };

  environment.sessionVariables = {
    JACK_PROMISCUOUS_SERVER = "1";
  };

  musnix = {
    enable = true;
    alsaSeq.enable = false;
    kernel = {
      packages = with pkgs; linuxPackages_latest_rt;
      optimize = true;
      realtime = true;
    };
  };

  systemd.services.jack = {
    wantedBy = [ "sound.target" ];
    serviceConfig = {
      Type = "simple";
      ExecStart = "${pkgs.jack2Full}/bin/jackd -d alsa -d 'hw:1' -M -H -r 96000";
      ExecStartPost = "${pkgs.jack2Full}/bin/jack_wait -w -t 5";
      User = "jack";
      Group = "audio";
      UMask = "0000";
      Nice = "-15";
      LimitRTPRIO = "95:95";
      LimitMEMLOCK = "infinity";
    };
  };

  services.trivmix = {
    "mpdmix0" = { connectOut = "outnode0:in"; group = "mpd"; initial = "-35dB"; };
    "mpdmix1" = { connectOut = "outnode1:in"; group = "mpd"; initial = "-35dB"; };

    "passmix0" = { connectOut = "outnode0:in"; connectIn = "system:capture_5"; group = "vali_out"; initial = "-5dB"; };
    "passmix1" = { connectOut = "outnode1:in"; connectIn = "system:capture_6"; group = "vali_out"; initial = "-5dB"; };

    "passmix2" = { connectOut = "system:playback_5"; connectIn = "system:capture_1"; group = "mic_out"; initial = "1"; };
    "passmix3" = { connectOut = "system:playback_6"; connectIn = "system:capture_1"; group = "mic_out"; initial = "1"; };

    "passmix4" = { connectOut = "outnode0:in"; connectIn = "system:capture_7"; group = "hel_out"; initial = "-5dB"; };
    "passmix5" = { connectOut = "outnode1:in"; connectIn = "system:capture_8"; group = "hel_out"; initial = "-5dB"; };

    "outnode0" = { initial = "1"; adjustable = false; };
    "outnode1" = { initial = "1"; adjustable = false; };

    "headphones0" = { connectOut = "system:playback_3"; connectIn = "outnode0:out"; group = "headphones"; initial = "1"; };
    "headphones1" = { connectOut = "system:playback_4"; connectIn = "outnode1:out"; group = "headphones"; initial = "1"; };

    "speakers0" = { connectOut = "system:playback_7"; connectIn = "outnode0:out"; group = "speakers"; initial = "0"; };
    "speakers1" = { connectOut = "system:playback_8"; connectIn = "outnode1:out"; group = "speakers"; initial = "0"; };
  };

  services.mpd = {
    enable = true;
    musicDirectory = "/media/odin/music";
    network.listenAddress = "any"; # Just so the module won't produce a bind_to_adress line
    extraConfig = ''
      bind_to_address "bragi.bragisheimr.yggdrasil"
      bind_to_address "bragi.asgard.yggdrasil"
      bind_to_address "localhost"

      bind_to_address "/var/lib/mpd/socket"
    
      audio_output {
        name "JACK"
        type "jack"
        client_name "mpd"
        destination_ports "mpdmix0:in,mpdmix1:in"
      }
    '';
    user = "mpd";
    group = "audio";
  };
  systemd.services."mpd".requires = [ "mpdmix0.service" "mpdmix1.service" "media-odin.mount" ];
  systemd.services."mpd".serviceConfig = {
      LimitMEMLOCK = "infinity";
      Nice = "-5";
      LimitRTPRIO = lib.mkForce "95:95";
      UMask = "0000";
  };

  users.extraUsers.jack = {
    name = "jack";
    isSystemUser = true;
    group = "audio";
  };

  security.wrappers = { "mount.nfs".source = "${pkgs.nfs-utils}/bin/mount.nfs"; };

  programs.bash.promptInit = ''
    PROMPT_COLOR="1;31m"
    return $UID && PROMPT_COLOR="1;32m"
    case "$TERM" in
      xterm*|rxvt*|kterm|aterm|gnome*) # Others can go here.
        PS1="\n\[\033[$PROMPT_COLOR\][\u@\h:\w]\\$\[\033[0m\] "
        if test "$TERM" = "xterm"; then
          PS1="\[\033]2;\h:\u:\w\007\]$PS1"
        fi
        ;;
      *)
        PS1="[\u@\h:\w]$ "
        ;;
    esac
  '';

  networking.interfaces = {
    "enp1s0" = {
      useDHCP = false;
      ipAddress = "10.141.4.1";
      prefixLength = 24;
    };
  };

  networking.nat = {
    enable = true;
    externalIP = "10.141.1.5";
    externalInterface = "wlp4s0";
    internalIPs = [ "10.141.4.0/24"
                  ];
    internalInterfaces = [ "enp1s0"
                         ];
  };

  networking.firewall = {
    enable = true;
    allowPing = true;
    allowedTCPPorts = [ 22 # SSH
                        80 # HTTP
                        5432 # PostgreSQL
                        6600 # MPD
                        139 445 # SAMBA
                      ];
    allowedUDPPorts = [ 137 138 ]; # SAMBA
    allowedUDPPortRanges = [ { from = 60000; to = 61000; } # mosh
                           ];
    extraCommands = ''
      iptables -t nat -A POSTROUTING -o wlp4s0 -j MASQUERADE
      #iptables -A FORWARD -i wlp4s0 -o enp1s0 -m state --state RELATED,ESTABLISHED -j ACCEPT
      iptables -A FORWARD -i wlp4s0 -o enp1s0 -j ACCEPT
      iptables -A FORWARD -i enp1s0 -o wlp4s0 -j ACCEPT
    '';
  };

  networking.defaultMailServer = {
    directDelivery = true;
    hostName = "ymir.niflheim.yggdrasil";
    useSTARTTLS = true;
    setSendmail = true;
  };

  networking.search = [ "bragisheimr.yggdrasil" "asgard.yggdrasil" ];

  services.dhcpd4 = {
    enable = true;
    interfaces = [ "enp1s0"
                 ];
    extraConfig = ''
      option subnet-mask 255.255.255.0;
      option domain-name-servers 10.141.1.1, 8.8.8.8, 8.8.4.4;
      option domain-name "bragisheimr.yggdrasil";
      option routers 10.141.4.1;
      subnet 10.141.4.0 netmask 255.255.255.0 {
        range 10.141.4.100 10.141.4.254;
      }

      group {
        use-host-decl-names on;

        host vali {
          hardware ethernet e0:cb:4e:f7:10:3d;
          fixed-address vali.bragisheimr.yggdrasil;
        }

        host printer {
          hardware ethernet 30:cd:a7:b0:55:8d;
          fixed-address printer.bragisheimr.yggdrasil;
        }
      }
    '';
  };

  services.samba = {
    enable = true;
  };

  users.extraUsers.root = let
    template = (import users/gkleen.nix);
    in {
        inherit (template) shell;
        openssh.authorizedKeys.keyFiles = template.openssh.authorizedKeys.keyFiles;
      };

  users.extraUsers."thermoprint" = {
    name = "thermoprint";
    group = "lp";
    isSystemUser = true;
    createHome = true;
    home = "/var/lib/thermoprint";
  };

  systemd.services."thermoprint" = {
    environment = {
      THERMOPRINT_CONFIG = ./bragi/thermoprint-server;
      THERMOPRINT_CACHE = ''${users.extraUsers."thermoprint".home}/dyre'';
    };
    requires = [ "postgresql.service" ];
    wantedBy = [ "default.target" ];
    serviceConfig = {
      Type = "simple";
      ExecStart = ''${pkgs.thermoprint-server}/bin/thermoprint-server --force-reconf'';
      User = users.extraUsers."thermoprint".name;
      Group = users.extraUsers."thermoprint".group;
      WorkingDirectory = "~";
    };
  };

  systemd.services."thermoprint-webgui" = {
    wantedBy = [ "default.target" ];
    serviceConfig = {
      Type = "simple";
      ExecStart = ''${pkgs.thermoprint-webgui}/bin/thermoprint-webgui -P 80 -A localhost -F /thermoprint/api/ -a "localhost" -p 8081'';
      User = users.extraUsers."thermoprint".name;
      Group = users.extraUsers."thermoprint".group;
      WorkingDirectory = "~";
    };
  };

  users.extraUsers."bar" = {
    name = "bar";
    group = "nogroup";
    isSystemUser = true;
    createHome = true;
    home = "/var/lib/bar";
  };

  systemd.services."bar" = let
    ghc = pkgs.haskellPackages.ghcWithPackages (p: with p; [yesod persistent-postgresql]);
  in {
    environment = {
      PORT = "8082";
      HOST = "::1";
      TPRINT_BASEURL = "http://localhost:80/thermoprint/api";
      APPROOT = "/bar";
      IP_FROM_HEADER = "true";
    };
    requires = [ "postgresql.service" ];
    wantedBy = [ "default.target" ];
    serviceConfig = {
      Type = "notify";
      ExecStart = ''
        ${pkgs.callPackage ./bragi/bar {}}/bin/bar
      '';
      User = users.extraUsers."bar".name;
      Group = users.extraUsers."bar".group;
      WorkingDirectory = "~";
    };
  };

  services.nginx = {
    enable = true;
    httpConfig = ''
      default_type application/octet-stream;

      log_format main
              '$remote_addr - $remote_user [$time_local] '
              '"$request" $status $bytes_sent '
              '"$http_referer" "$http_user_agent" '
              '"$gzip_ratio"';

      client_header_timeout 10m;
      client_body_timeout 10m;
      send_timeout 10m;

      connection_pool_size 256;
      client_header_buffer_size 1k;
      large_client_header_buffers 4 2k;
      request_pool_size 4k;

      gzip on;
      gzip_min_length 1100;
      gzip_buffers 4 8k;
      gzip_types text/plain;

      output_buffers 1 32k;
      postpone_output 1460;

      sendfile on;
      tcp_nopush on;
      tcp_nodelay on;

      keepalive_timeout 75 20;

      ignore_invalid_headers on;

      access_log stderr;
      error_log stderr;

      server {
        listen *:80;
        server_name _;

        location /thermoprint/api/ {
          proxy_pass http://[::1]:8080/;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
        }

        location /thermoprint/ {
          proxy_pass http://localhost:8081/;
        }

        location /bar/ {
          proxy_pass http://[::1]:8082/;
        }
      }
    '';
  };

  services.postgresql = {
    enable = true;
    enableTCPIP = true;
    authentication = lib.mkForce ''
      local all all peer
      host all all 10.141.0.0/16 md5
    '';
    initialScript = pkgs.writeText "schema.sql" ''
      CREATE USER thermoprint;
      CREATE DATABASE thermoprint WITH OWNER = thermoprint;
      GRANT ALL ON DATABASE thermoprint TO thermoprint;

      CREATE USER bar;
      CREATE DATABASE bar WITH OWNER = bar;
      GRANT ALL ON DATABASE bar TO bar;
    '';
  };

  nix = {
    daemonIONiceLevel = 3;
    daemonNiceLevel = 10;
    gc = {
      automatic = true;
    };
    autoOptimiseStore = true;
  };

  system.autoUpgrade.enable = true;
  system.stateVersion = "16.09";

  systemd.services."nixos-upgrade".path = with pkgs; [ git ];
  systemd.services."nixos-upgrade".preStart = ''
    git -C /etc/nixos pull
    git -C /etc/nixos submodule update
  '';

  systemd.status-mail = {
    recipient = "root@yggdrasil.li";
    onFailure = [ "nixos-upgrade" ];
  };
}