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

with lib;

let
  cfg = config.services.asterisk;

  asteriskUser = "asterisk";

  varlibdir = "/var/lib/asterisk";
  spooldir = "/var/spool/asterisk";
  logdir = "/var/log/asterisk";

  asteriskEtc = pkgs.stdenv.mkDerivation
  ((mapAttrs' (name: value: nameValuePair
        # Fudge the names to make bash happy
        ((replaceChars ["."] ["_"] name) + "_")
        (value)
      ) cfg.confFiles) //
  {
    confFilesString = concatStringsSep " " (
      attrNames cfg.confFiles
    );

    name = "asterisk.etc";

    # Default asterisk.conf file
    # (Notice that astetcdir will be set to the path of this derivation)
    asteriskConf = ''
      [directories]
      astetcdir => @out@
      astmoddir => ${pkgs.asterisk}/lib/asterisk/modules
      astvarlibdir => /var/lib/asterisk
      astdbdir => /var/lib/asterisk
      astkeydir => /var/lib/asterisk
      astdatadir => /var/lib/asterisk
      astagidir => /var/lib/asterisk/agi-bin
      astspooldir => /var/spool/asterisk
      astrundir => /var/run/asterisk
      astlogdir => /var/log/asterisk
      astsbindir => ${pkgs.asterisk}/sbin
    '';
    extraConf = cfg.extraConfig;

    # Loading all modules by default is considered sensible by the authors of
    # "Asterisk: The Definitive Guide". Secure sites will likely want to
    # specify their own "modules.conf" in the confFiles option.
    modulesConf = ''
      [modules]
      autoload=yes
    '';

    # Use syslog for logging so logs can be viewed with journalctl
    loggerConf = ''
      [general]

      [logfiles]
      syslog.local0 => notice,warning,error
    '';

    buildCommand = ''
      mkdir -p "$out"

      # Create asterisk.conf, pointing astetcdir to the path of this derivation
      echo "$asteriskConf" | sed "s|@out@|$out|g" > "$out"/asterisk.conf
      echo "$extraConf" >> "$out"/asterisk.conf

      echo "$modulesConf" > "$out"/modules.conf

      echo "$loggerConf" > "$out"/logger.conf

      # Config files specified in confFiles option override all other files
      for i in $confFilesString; do
        conf=$(echo "$i"_ | sed 's/\./_/g')
        echo "''${!conf}" > "$out"/"$i"
      done
    '';
  });
in

{
  options = {
    services.asterisk = {
      enable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to enable the Asterisk PBX server.
        '';
      };

      extraConfig = mkOption {
        default = "";
        type = types.lines;
        example = ''
          [options]
          verbose=3
          debug=3
        '';
        description = ''
          Extra configuration options appended to the default
          <literal>asterisk.conf</literal> file.
        '';
      };

      confFiles = mkOption {
        default = {};
        type = types.attrsOf types.str;
        example = literalExample
          ''
            {
              "extensions.conf" = '''
                [tests]
                ; Dial 100 for "hello, world"
                exten => 100,1,Answer()
                same  =>     n,Wait(1)
                same  =>     n,Playback(hello-world)
                same  =>     n,Hangup()

                [softphones]
                include => tests

                [unauthorized]
              ''';
              "sip.conf" = '''
                [general]
                allowguest=no              ; Require authentication
                context=unauthorized       ; Send unauthorized users to /dev/null
                srvlookup=no               ; Don't do DNS lookup
                udpbindaddr=0.0.0.0        ; Listen on all interfaces
                nat=force_rport,comedia    ; Assume device is behind NAT

                [softphone](!)
                type=friend                ; Match on username first, IP second
                context=softphones         ; Send to softphones context in
                                           ; extensions.conf file
                host=dynamic               ; Device will register with asterisk
                disallow=all               ; Manually specify codecs to allow
                allow=g722
                allow=ulaw
                allow=alaw

                [myphone](softphone)
                secret=GhoshevFew          ; Change this password!
              ''';
              "logger.conf" = '''
                [general]

                [logfiles]
                ; Add debug output to log
                syslog.local0 => notice,warning,error,debug
              ''';
            }
        '';
        description = ''
          Sets the content of config files (typically ending with
          <literal>.conf</literal>) in the Asterisk configuration directory.

          Note that if you want to change <literal>asterisk.conf</literal>, it
          is preferable to use the <option>services.asterisk.extraConfig</option>
          option over this option. If <literal>"asterisk.conf"</literal> is
          specified with the <option>confFiles</option> option (not recommended),
          you must be prepared to set your own <literal>astetcdir</literal>
          path.

          See
          <link xlink:href="http://www.asterisk.org/community/documentation"/>
          for more examples of what is possible here.
        '';
      };

      extraArguments = mkOption {
        default = [];
        type = types.listOf types.str;
        example =
          [ "-vvvddd" "-e" "1024" ];
        description = ''
          Additional command line arguments to pass to Asterisk.
        '';
      };
    };
  };

  config = mkIf cfg.enable {
    users.extraUsers = singleton
    { name = asteriskUser;
      uid = config.ids.uids.asterisk;
      description = "Asterisk daemon user";
      home = varlibdir;
    };

    systemd.services.asterisk = {
      description = ''
        Asterisk PBX server
      '';

      wantedBy = [ "multi-user.target" ];

      preStart = ''
        # Copy skeleton directory tree to /var
        for d in '${varlibdir}' '${spooldir}' '${logdir}'; do
          # TODO: Make exceptions for /var directories that likely should be updated
          if [ ! -e "$d" ]; then
            mkdir -p "$d"
            cp --recursive ${pkgs.asterisk}/"$d" "$d"
            chown --recursive ${asteriskUser} "$d"
            find "$d" -type d | xargs chmod 0755
          fi
        done
      '';

      serviceConfig = {
        ExecStart =
          let
            # FIXME: This doesn't account for arguments with spaces
            argString = concatStringsSep " " cfg.extraArguments;
          in
          "${pkgs.asterisk}/bin/asterisk -U ${asteriskUser} -C ${asteriskEtc}/asterisk.conf ${argString} -F";
        Type = "forking";
        PIDFile = "/var/run/asterisk/asterisk.pid";
      };
    };
  };
}