{ lib, ... }:

with lib;
let
  join-lines = concatStringsSep "\n";

  dump = obj: builtins.trace obj obj;

  makeSrvRecords = protocol: service: records: let
    service-blah = (dump service);
    record-blah = (dump records);
    in
    join-lines (map (record:
      "_${service}._${protocol} IN SRV ${toString record.priority} ${
        toString record.weight
      } ${toString record.port} ${record.host}.") records);

  makeSrvProtocolRecords = protocol: services:
    join-lines (mapAttrsToList (makeSrvRecords protocol) services);

  srvRecordOpts = with types; {
    options = {
      weight = mkOption {
        type = int;
        description = "Weight relative to other records.";
        default = 1;
      };

      priority = mkOption {
        type = int;
        description = "Priority to give this record.";
        default = 0;
      };

      port = mkOption {
        type = port;
        description = "Port to use when connecting.";
      };

      host = mkOption {
        type = str;
        description = "Host to contact for this service.";
        example = "my-host.my-domain.com.";
      };
    };
  };

  srvRecordPair = domain: protocol: service: record: {
    "_${service}._${protocol}.${domain}" =
      "${toString record.priority} ${toString record.weight} ${
        toString record.port
      } ${record.host}.";
  };

in rec {

  srvRecords = with types; attrsOf (attrsOf (listOf (submodule srvRecordOpts)));

  srvRecordsToBindZone = srvRecords:
    join-lines (mapAttrsToList makeSrvProtocolRecords srvRecords);

  concatMapAttrs = f: attrs:
    concatMap (x: x) (mapAttrsToList (key: val: f key val) attrs);

  srvRecordsToPairs = domain: srvRecords: 
    listToAttrs (concatMapAttrs (protocol: services:
      concatMapAttrs
      (service: records: map (srvRecordPair domain protocol service) records) services)
      srvRecords);
}