{ options, config, lib, ... }:

with lib;
with types;
let
  virtualHostsModule = elemAt options.services.nginx.virtualHosts.type.functor.wrapped.getSubModules 0;
  listenersModule = virtualHostsModule.submodule.options.listen.type.functor.wrapped;
  listenerOption = mkOption {
    type = listOf listenersModule;
    default = [];
  };

  nconfig = config.services.nginx;

  withSSL = enabled: listener:
    listener // {
      ssl = enabled;
      port = if enabled then 443 else 80;
    };
in {
  options.services.nginx = {
    localListeners = listenerOption;
    privateListeners = listenerOption;
    publicListeners = listenerOption;

    virtualHosts = mkOption {
      type = attrsOf (submodule ({ config, ... }: {
        options = {
          listenAll = mkEnableOption "listen on all interfaces";
          listenLocal = mkEnableOption "listen on the local interface";
          listenPrivate = mkEnableOption "listen privately";
          listenPublic = mkEnableOption "listen publicly";
        };

        config =
          let
            onlySSL = config.onlySSL || config.enableSSL;
            hasSSL = onlySSL || config.addSSL || config.forceSSL;
            createFor = listeners:
              (optionals hasSSL (map (withSSL true) listeners))
              ++ (optionals (!onlySSL) (map (withSSL false) listeners));
          in mkMerge [
            (mkIf config.listenAll {
              listenLocal = true;
              listenPublic = true;
              listenPrivate = true;
            })

            {
              listen =
                (optionals config.listenLocal (createFor nconfig.localListeners))
                ++ (optionals config.listenPrivate (createFor nconfig.privateListeners))
                ++ (optionals config.listenPublic (createFor nconfig.publicListeners));
            }
          ];
      }));
    };
  };
}