{ config, lib, name, ... }:

with builtins;
with lib;

let
  cfg = config;

  portHash = flip pipe [
    (hashString "md5")
    (substring 0 7)
    (hash: (fromTOML "v=0x${hash}").v)
    (flip mod cfg.reservedPorts.amount)
    (add cfg.reservedPorts.start)
  ];
in

{
  options = {
    ipv4 = mkOption {
      type = types.str;
      default = "127.0.0.1";
      description = "The IPv4 address.";
    };
    hostname = mkOption {
      type = types.str;
      description = "The hostname.";
    };

    port = mkOption {
      type = types.int;
      description = "The TCP or UDP port.";
    };
    portStr = mkOption {
      type = types.str;
      description = "The TCP or UDP port, as a string.";
    };
    reservedPorts = {
      amount = mkOption {
        type = types.int;
        default = 10000;
        description = "Amount of ports to reserve at most.";
      };
      start = mkOption {
        type = types.int;
        default = 30000;
        description = "Starting point for reserved ports.";
      };
    };

    protocol = mkOption {
      type = types.str;
      description = "The protocol in URL scheme name format.";
    };
    path = mkOption {
      type = types.nullOr types.path;
      default = null;
      description = "The resource path.";
    };
    url = mkOption {
      type = types.str;
      description = "The URL.";
    };
    tuple = mkOption {
      type = types.str;
      description = "The hostname:port tuple.";
    };
    extra = mkOption {
      type = types.attrs;
      description = "Arbitrary extra data.";
    };
  };
  config = mkIf true {
    hostname = mkDefault cfg.ipv4;
    port = mkDefault (portHash "${cfg.hostname}:${name}");
    portStr = toString cfg.port;
    tuple = "${cfg.hostname}:${cfg.portStr}";
    url = "${cfg.protocol}://${cfg.hostname}:${cfg.portStr}${if cfg.path == null then "" else cfg.path}";
  };
}