{ pkgs ? import <nixpkgs> {}, ... }:

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

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

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

  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: type: record: {
    "_${type}._${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: types:
        concatMapAttrs (type: records: map (srvRecordPair domain protocol type) records) types)
        srvRecords);
}