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

  cfg = config.services.pulseaudio;

  hasZeroconf =
    let
      z = cfg.zeroconf;
    in
    z.publish.enable || z.discovery.enable;

  overriddenPackage = cfg.package.override (
    lib.optionalAttrs hasZeroconf { zeroconfSupport = true; }
  );
  binary = "${lib.getBin overriddenPackage}/bin/pulseaudio";
  binaryNoDaemon = "${binary} --daemonize=no";

  # Forces 32bit pulseaudio and alsa-plugins to be built/supported for apps
  # using 32bit alsa on 64bit linux.
  enable32BitAlsaPlugins =
    cfg.support32Bit
    && pkgs.stdenv.hostPlatform.isx86_64
    && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null);

  myConfigFile =
    let
      addModuleIf = cond: mod: lib.optionalString cond "load-module ${mod}";
      allAnon = lib.optional cfg.tcp.anonymousClients.allowAll "auth-anonymous=1";
      ipAnon =
        let
          a = cfg.tcp.anonymousClients.allowedIpRanges;
        in
        lib.optional (a != [ ]) ''auth-ip-acl=${lib.concatStringsSep ";" a}'';
    in
    pkgs.writeTextFile {
      name = "default.pa";
      text = ''
        .include ${cfg.configFile}
        ${addModuleIf cfg.zeroconf.publish.enable "module-zeroconf-publish"}
        ${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
        ${addModuleIf cfg.tcp.enable (
          lib.concatStringsSep " " ([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon)
        )}
        ${addModuleIf config.services.jack.jackd.enable "module-jack-sink"}
        ${addModuleIf config.services.jack.jackd.enable "module-jack-source"}
        ${cfg.extraConfig}
      '';
    };

  ids = config.ids;

  uid = ids.uids.pulseaudio;
  gid = ids.gids.pulseaudio;

  stateDir = "/run/pulse";

  # Create pulse/client.conf even if PulseAudio is disabled so
  # that we can disable the autospawn feature in programs that
  # are built with PulseAudio support (like KDE).
  clientConf = pkgs.writeText "client.conf" ''
    autospawn=no
    ${cfg.extraClientConf}
  '';

  # Write an /etc/asound.conf that causes all ALSA applications to
  # be re-routed to the PulseAudio server through ALSA's Pulse
  # plugin.
  alsaConf = ''
    pcm_type.pulse {
      libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
      ${lib.optionalString enable32BitAlsaPlugins "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
    }
    pcm.!default {
      type pulse
      hint.description "Default Audio Device (via PulseAudio)"
    }
    ctl_type.pulse {
      libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
      ${lib.optionalString enable32BitAlsaPlugins "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
    }
    ctl.!default {
      type pulse
    }
  '';

in
{
  imports = [
    (lib.mkRenamedOptionModule [ "hardware" "pulseaudio" ] [ "services" "pulseaudio" ])
  ];

  options = {

    services.pulseaudio = {
      enable = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Whether to enable the PulseAudio sound server.
        '';
      };

      systemWide = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          If false, a PulseAudio server is launched automatically for
          each user that tries to use the sound system. The server runs
          with user privileges. If true, one system-wide PulseAudio
          server is launched on boot, running as the user "pulse", and
          only users in the "pulse-access" group will have access to the server.
          Please read the PulseAudio documentation for more details.

          Don't enable this option unless you know what you are doing.
        '';
      };

      support32Bit = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Whether to include the 32-bit pulseaudio libraries in the system or not.
          This is only useful on 64-bit systems and currently limited to x86_64-linux.
        '';
      };

      configFile = lib.mkOption {
        type = lib.types.nullOr lib.types.path;
        description = ''
          The path to the default configuration options the PulseAudio server
          should use. By default, the "default.pa" configuration
          from the PulseAudio distribution is used.
        '';
      };

      extraConfig = lib.mkOption {
        type = lib.types.lines;
        default = "";
        description = ''
          Literal string to append to `configFile`
          and the config file generated by the pulseaudio module.
        '';
      };

      extraClientConf = lib.mkOption {
        type = lib.types.lines;
        default = "";
        description = ''
          Extra configuration appended to pulse/client.conf file.
        '';
      };

      package = lib.mkOption {
        type = lib.types.package;
        default = if config.services.jack.jackd.enable then pkgs.pulseaudioFull else pkgs.pulseaudio;
        defaultText = lib.literalExpression "pkgs.pulseaudio";
        example = lib.literalExpression "pkgs.pulseaudioFull";
        description = ''
          The PulseAudio derivation to use.  This can be used to enable
          features (such as JACK support, Bluetooth) via the
          `pulseaudioFull` package.
        '';
      };

      extraModules = lib.mkOption {
        type = lib.types.listOf lib.types.package;
        default = [ ];
        example = lib.literalExpression "[ pkgs.pulseaudio-modules-bt ]";
        description = ''
          Extra pulseaudio modules to use. This is intended for out-of-tree
          pulseaudio modules like extra bluetooth codecs.

          Extra modules take precedence over built-in pulseaudio modules.
        '';
      };

      daemon = {
        logLevel = lib.mkOption {
          type = lib.types.str;
          default = "notice";
          description = ''
            The log level that the system-wide pulseaudio daemon should use,
            if activated.
          '';
        };

        config = lib.mkOption {
          type = lib.types.attrsOf lib.types.unspecified;
          default = { };
          description = "Config of the pulse daemon. See `man pulse-daemon.conf`.";
          example = lib.literalExpression ''{ realtime-scheduling = "yes"; }'';
        };
      };

      zeroconf = {
        discovery.enable = lib.mkEnableOption "discovery of pulseaudio sinks in the local network";
        publish.enable = lib.mkEnableOption "publishing the pulseaudio sink in the local network";
      };

      # TODO: enable by default?
      tcp = {
        enable = lib.mkEnableOption "tcp streaming support";

        anonymousClients = {
          allowAll = lib.mkEnableOption "all anonymous clients to stream to the server";
          allowedIpRanges = lib.mkOption {
            type = lib.types.listOf lib.types.str;
            default = [ ];
            example = lib.literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
            description = ''
              A list of IP subnets that are allowed to stream to the server.
            '';
          };
        };
      };

    };

  };

  config = lib.mkIf cfg.enable (
    lib.mkMerge [
      {
        environment.etc."pulse/client.conf".source = clientConf;

        environment.systemPackages = [ overriddenPackage ];

        environment.etc = {
          "alsa/conf.d/99-pulseaudio.conf".text = alsaConf;

          "pulse/daemon.conf".source = pkgs.writeText "daemon.conf" (
            lib.generators.toKeyValue { } cfg.daemon.config
          );

          "openal/alsoft.conf".source = pkgs.writeText "alsoft.conf" "drivers=pulse";

          "libao.conf".source = pkgs.writeText "libao.conf" "default_driver=pulse";
        };

        services.pulseaudio.configFile = lib.mkDefault "${lib.getBin overriddenPackage}/etc/pulse/default.pa";

        # Disable flat volumes to enable relative ones
        services.pulseaudio.daemon.config.flat-volumes = lib.mkDefault "no";

        # Upstream defaults to speex-float-1 which results in audible artifacts
        services.pulseaudio.daemon.config.resample-method = lib.mkDefault "speex-float-5";

        # Allow PulseAudio to get realtime priority using rtkit.
        security.rtkit.enable = true;

        systemd.packages = [ overriddenPackage ];

        # PulseAudio is packaged with udev rules to handle various audio device quirks
        services.udev.packages = [ overriddenPackage ];
      }

      (lib.mkIf (cfg.extraModules != [ ]) {
        services.pulseaudio.daemon.config.dl-search-path =
          let
            overriddenModules = builtins.map (
              drv: drv.override { pulseaudio = overriddenPackage; }
            ) cfg.extraModules;
            modulePaths =
              builtins.map (drv: "${drv}/lib/pulseaudio/modules")
                # User-provided extra modules take precedence
                (overriddenModules ++ [ overriddenPackage ]);
          in
          lib.concatStringsSep ":" modulePaths;
      })

      (lib.mkIf hasZeroconf {
        services.avahi.enable = true;
      })
      (lib.mkIf cfg.zeroconf.publish.enable {
        services.avahi.publish.enable = true;
        services.avahi.publish.userServices = true;
      })

      (lib.mkIf (!cfg.systemWide) {
        environment.etc = {
          "pulse/default.pa".source = myConfigFile;
        };
        systemd.user = {
          services.pulseaudio =
            {
              restartIfChanged = true;
              serviceConfig = {
                RestartSec = "500ms";
                PassEnvironment = "DISPLAY";
              };
            }
            // lib.optionalAttrs config.services.jack.jackd.enable {
              environment.JACK_PROMISCUOUS_SERVER = "jackaudio";
            };
          sockets.pulseaudio = {
            wantedBy = [ "sockets.target" ];
          };
        };
      })

      (lib.mkIf cfg.systemWide {
        users.users.pulse = {
          # For some reason, PulseAudio wants UID == GID.
          uid =
            assert uid == gid;
            uid;
          group = "pulse";
          extraGroups = [ "audio" ];
          description = "PulseAudio system service user";
          home = stateDir;
          homeMode = "755";
          createHome = true;
          isSystemUser = true;
        };

        users.groups.pulse.gid = gid;
        users.groups.pulse-access = { };

        systemd.services.pulseaudio = {
          description = "PulseAudio System-Wide Server";
          wantedBy = [ "sound.target" ];
          before = [ "sound.target" ];
          environment.PULSE_RUNTIME_PATH = stateDir;
          serviceConfig = {
            Type = "notify";
            ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}";
            Restart = "on-failure";
            RestartSec = "500ms";
          };
        };

        environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";
      })
    ]
  );

}
