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

with lib;

let

  cfg = config.programs.offlineimap;

  accounts = filter (a: a.offlineimap.enable)
    (attrValues config.accounts.email.accounts);

  toIni = generators.toINI {
    mkKeyValue = key: value:
      let
        value' = if isBool value then
          (if value then "yes" else "no")
        else
          toString value;
      in "${key} = ${value'}";
  };

  # Generates a script to fetch only a specific account.
  #
  # Note, these scripts are not actually created and installed at the
  # moment. It will need some thinking on whether this is a good idea
  # and whether other modules should have some similar functionality.
  #
  # Perhaps have a single tool `email` that wraps the command?
  # Something like
  #
  #     $ email <account name> <program name> <program args>
  genOfflineImapScript = account:
    with account;
    pkgs.writeShellScriptBin "offlineimap-${name}" ''
      exec ${pkgs.offlineimap}/bin/offlineimap -a${account.name} "$@"
    '';

  accountStr = account:
    with account;
    let
      postSyncHook = optionalAttrs (offlineimap.postSyncHookCommand != "") {
        postsynchook = pkgs.writeShellScriptBin "postsynchook"
          offlineimap.postSyncHookCommand + "/bin/postsynchook";
      };

      localType =
        if account.flavor == "gmail.com" then "GmailMaildir" else "Maildir";

      remoteType = if account.flavor == "gmail.com" then "Gmail" else "IMAP";

      remoteHost =
        optionalAttrs (imap.host != null) { remotehost = imap.host; };

      remotePort =
        optionalAttrs ((imap.port or null) != null) { remoteport = imap.port; };

      ssl = if imap.tls.enable then {
        ssl = true;
        sslcacertfile = toString imap.tls.certificatesFile;
        starttls = imap.tls.useStartTls;
      } else {
        ssl = false;
      };

      remotePassEval =
        let arglist = concatMapStringsSep "," (x: "'${x}'") passwordCommand;
        in optionalAttrs (passwordCommand != null) {
          remotepasseval = ''get_pass("${name}", [${arglist}]).strip("\n")'';
        };
    in toIni {
      "Account ${name}" = {
        localrepository = "${name}-local";
        remoterepository = "${name}-remote";
      } // postSyncHook // offlineimap.extraConfig.account;

      "Repository ${name}-local" = {
        type = localType;
        localfolders = maildir.absPath;
      } // offlineimap.extraConfig.local;

      "Repository ${name}-remote" = {
        type = remoteType;
        remoteuser = userName;
      } // remoteHost // remotePort // remotePassEval // ssl
        // offlineimap.extraConfig.remote;
    };

  extraConfigType = with types; attrsOf (either (either str int) bool);

in {
  options = {
    programs.offlineimap = {
      enable = mkEnableOption "OfflineIMAP";

      pythonFile = mkOption {
        type = types.lines;
        default = ''
          import subprocess

          def get_pass(service, cmd):
              return subprocess.check_output(cmd, )
        '';
        description = ''
          Python code that can then be used in other parts of the
          configuration.
        '';
      };

      extraConfig.general = mkOption {
        type = extraConfigType;
        default = { };
        example = {
          maxage = 30;
          ui = "blinkenlights";
        };
        description = ''
          Extra configuration options added to the
          <option>general</option> section.
        '';
      };

      extraConfig.default = mkOption {
        type = extraConfigType;
        default = { };
        example = { gmailtrashfolder = "[Gmail]/Papierkorb"; };
        description = ''
          Extra configuration options added to the
          <option>DEFAULT</option> section.
        '';
      };

      extraConfig.mbnames = mkOption {
        type = extraConfigType;
        default = { };
        example = literalExample ''
          {
            filename = "~/.config/mutt/mailboxes";
            header = "'mailboxes '";
            peritem = "'+%(accountname)s/%(foldername)s'";
            sep = "' '";
            footer = "'\\n'";
          }
        '';
        description = ''
          Extra configuration options added to the
          <code>mbnames</code> section.
        '';
      };
    };

    accounts.email.accounts = mkOption {
      type = with types;
        attrsOf (submodule (import ./offlineimap-accounts.nix));
    };
  };

  config = mkIf cfg.enable {
    home.packages = [ pkgs.offlineimap ];

    xdg.configFile."offlineimap/get_settings.py".text = cfg.pythonFile;
    xdg.configFile."offlineimap/get_settings.pyc".source = "${
        pkgs.runCommandLocal "get_settings-compile" {
          nativeBuildInputs = [ pkgs.python2 ];
          pythonFile = cfg.pythonFile;
          passAsFile = [ "pythonFile" ];
        } ''
          mkdir -p $out/bin
          cp $pythonFilePath $out/bin/get_settings.py
          python2 -m py_compile $out/bin/get_settings.py
        ''
      }/bin/get_settings.pyc";

    xdg.configFile."offlineimap/config".text = ''
      # Generated by Home Manager.
      # See https://github.com/OfflineIMAP/offlineimap/blob/master/offlineimap.conf
      # for an exhaustive list of options.
    '' + toIni ({
      general = {
        accounts = concatMapStringsSep "," (a: a.name) accounts;
        pythonfile = "${config.xdg.configHome}/offlineimap/get_settings.py";
        metadata = "${config.xdg.dataHome}/offlineimap";
      } // cfg.extraConfig.general;
    } // optionalAttrs (cfg.extraConfig.mbnames != { }) {
      mbnames = { enabled = true; } // cfg.extraConfig.mbnames;
    } // optionalAttrs (cfg.extraConfig.default != { }) {
      DEFAULT = cfg.extraConfig.default;
    }) + "\n" + concatStringsSep "\n" (map accountStr accounts);
  };
}