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

with lib;

let
  cfg = config.services.trivmix;

  mixerModule = {
    options = {
      connectIn = mkOption {
        type = with types; coercedTo str singleton (listOf str);
        default = [];
        description = "Ports to connect mixer input to";
      };

      connectOut = mkOption {
        type = with types; coercedTo str singleton (listOf str);
        default = [];
        description = "Ports to connect mixer output to";
      };

      onConnect = mkOption {
        type = types.lines;
        default = "";
        description = "Additional shell commands to run after connections are set up";
      };

      adjustable = mkOption {
        type = types.bool;
        default = true;
        description = "Should the volume on this mixer be adjustable?";
      };

      group = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Volumes of mixers that share a group name are synchronised";
      };

      initial = mkOption {
        type = types.str;
        default = "1";
        description = "Initial volume";
      };
    };
  };

  service = name: mixerCfg: with mixerCfg; let
    connectScript = ''
      #!${pkgs.stdenv.shell}

      ${concatMapStringsSep "\n" (port: "jack_connect ${port} $1") connectIn}
      ${concatMapStringsSep "\n" (port: "jack_connect $2 ${port}") connectOut}
      ${onConnect}
    '';

    connect = (connectOut != []) || (connectIn != []) || (onConnect != []);

    mixerDeps = filter (x: any (hasPrefix (x + ":")) connectIn || any (hasPrefix (x + ":")) connectOut) (attrNames cfg);
  in {
    wantedBy = [ "sound.target" ];
    requires = [ "jack.service" ] ++ map (n: n + ".service") mixerDeps;
    path = with pkgs; [ jack2Full ];
    serviceConfig = {
      Type = "notify";
      ExecStart = ''${pkgs.trivmix}/bin/trivmix --client ${name} \
        ${optionalString connect "--run ${connectScript}"} \
        "--level ${initial}" \
        ${optionalString (adjustable && isNull group) "/dev/shm/mix/${name}/level"} \
        ${optionalString (! isNull group) "/dev/shm/mix/${group}/level"}
      '';
      User = "jack";
      Group = "audio";
      Nice = "-10";
      LimitRTPRIO = "95:95";
      LimitMEMLOCK = "infinity";
    };
  };
in {
  options.services.trivmix = mkOption {
    type = types.attrsOf (types.submodule mixerModule);
    default = {};
    description = "Definition of mixers";
  };

  config.systemd.services = mapAttrs service cfg;
}