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

let

  cfg = config.programs.custom.vim;

  nix-xptemplates = pkgs.writeTextFile {
    name = "nix-xptemplates";
    destination = "/ftplugin/nix/nix.xpt.vim";
    text = # vim
      ''
        XPTemplate priority=personal

        XPT option " tips
        `name^ = mkOption {
          type = with types; `type^;
          description = ${"''"}
              `cursor^
          ${"''"};
        };

        XPT package " tips
        { config, lib, ... }:
        {
          `cursor^
        }

        XPT terranix" tips
        { config, lib, pkgs, ... }:
        with lib;
        let
          cfg = config.`name^;
        in {

          options.`name^ = mkOption {
            default = {};
            type = with types; attrsOf (submodule ({ name, ... }:{
              options = {
                enable = mkEnableOption "`name^.name";
              };
            }));
          };

          config =
            let
              allConfigs = cfg
            in
              mkIf (cfg != {} ){
                `cursor^
              };
        }

        XPT module " tips
        { config, lib, pkgs, ... }:

        with lib;

        let

          cfg = config.`name^;

        in {

          options.`name^ = {
              enable = mkEnableOption "enable `name^";
          };

          config = mkIf cfg.enable {
              `cursor^
          };
        }

        XPT shell " tips
        { pkgs ?  import <nixpkgs> {} }:
        pkgs.mkShell {

          # needed pkgs
          # -----------
          buildInputs = with pkgs; [
              `name^
          ];

          # run this on start
          # -----------------
          shellHook = ${"''"}
            HISTFILE=${"$"}{toString ./.}/.history
          ${"''"};
        }

        XPT fhsUser " tips
        { pkgs ? import <nixpkgs> {} }:
        (pkgs.buildFHSUserEnv {
        name = "fhs-user-env";

        targetPkgs = pkgs: with pkgs; [
          # core stuff
          # ----------
          vim silver-searcher curl coreutils git tig

          # common X dependencies
          # ---------------------
          atk cairo dbus eudev expat fontconfig freetype gdk_pixbuf glib gnome3.GConf gtk2-x11
          mesa_glu nspr nss pango xlibs.libXScrnSaver xlibs.libXcomposite xlibs.libXcursor
          xlibs.libXdamage xlibs.libXfixes xlibs.libXi xlibs.libXrender xlibs.libXtst xorg.libX11
          xorg.libXext xorg.libXinerama xorg.libxcb
          liblo zlib fftw minixml libcxx alsaLib glibc

          # new stuff
          # ---------
          `cursor^

        ];

        # multilib packages
        # -----------------
        # these are packages compiled 32bit and 64bit
        multiPkgs = pkgs: with pkgs; [
        ];

        # environment variables
        # ---------------------
        profile = ${"''"}
          export TERM="xterm"
          ${"''"};

        }).env

            '';
  };

  vim-tv-plugin = with lib;
    ((rtp: rtp // { inherit rtp; }) (pkgs.write "vim-tv" {
      "/syntax/haskell.vim".text = # vim
        ''
          syn region String start=+\[[[:alnum:]]*|+ end=+|]+

          hi link ConId Identifier
          hi link VarId Identifier
          hi link hsDelimiter Delimiter
        '';
      "/syntax/nix.vim".text = # vim
        ''
          "" Quit when a (custom) syntax file was already loaded
          "if exists("b:current_syntax")
          "  finish
          "endif

          "setf nix

          " Ref <nix/src/libexpr/lexer.l>
          syn match NixID    /[a-zA-Z\_][a-zA-Z0-9\_\'\-]*/
          syn match NixINT   /\<[0-9]\+\>/
          syn match NixPATH  /[a-zA-Z0-9\.\_\-\+]*\(\/[a-zA-Z0-9\.\_\-\+]\+\)\+/
          syn match NixHPATH /\~\(\/[a-zA-Z0-9\.\_\-\+]\+\)\+/
          syn match NixSPATH /<[a-zA-Z0-9\.\_\-\+]\+\(\/[a-zA-Z0-9\.\_\-\+]\+\)*>/
          syn match NixURI   /[a-zA-Z][a-zA-Z0-9\+\-\.]*:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']\+/
          syn region NixSTRING
            \ matchgroup=NixSTRING
            \ start='"'
            \ skip='\\"'
            \ end='"'
          syn region NixIND_STRING
            \ matchgroup=NixIND_STRING
            \ start="'''"
            \ skip="'''\('\|[$]\|\\[nrt]\)"
            \ end="'''"

          syn match NixOther /[-!+&<>|():/;=.,?\[\]*@]/

          syn match NixCommentMatch /\(^\|\s\)#.*/
          syn region NixCommentRegion start="/\*" end="\*/"

          hi link NixCode Statement
          hi link NixData Constant
          hi link NixComment Comment

          hi link NixCommentMatch NixComment
          hi link NixCommentRegion NixComment
          hi link NixID NixCode
          hi link NixINT NixData
          hi link NixPATH NixData
          hi link NixHPATH NixData
          hi link NixSPATH NixData
          hi link NixURI NixData
          hi link NixSTRING NixData
          hi link NixIND_STRING NixData

          hi link NixEnter NixCode
          hi link NixOther NixCode
          hi link NixQuote NixData

          syn cluster nix_has_dollar_curly contains=@nix_ind_strings,@nix_strings
          syn cluster nix_ind_strings contains=NixIND_STRING
          syn cluster nix_strings contains=NixSTRING

          ${concatStringsSep "\n" (mapAttrsToList (name:
            { extraStart ? null, lang ? name }:
            let
              startAlts = filter isString [ "/\\* ${name} \\*/" extraStart ];
              sigil = "\\(${concatStringsSep "\\|" startAlts}\\)[ \\t\\r\\n]*";
              # vim
            in ''
              syn include @nix_${lang}_syntax syntax/${lang}.vim
              if exists("b:current_syntax")
                unlet b:current_syntax
              endif

              syn match nix_${lang}_sigil
                \ X${replaceStrings [ "X" ] [ "\\X" ] sigil}\ze\('''\|"\)X
                \ nextgroup=nix_${lang}_region_IND_STRING,nix_${lang}_region_STRING
                \ transparent

              syn region nix_${lang}_region_STRING
                \ matchgroup=NixSTRING
                \ start='"'
                \ skip='\\"'
                \ end='"'
                \ contained
                \ contains=@nix_${lang}_syntax
                \ transparent

              syn region nix_${lang}_region_IND_STRING
                \ matchgroup=NixIND_STRING
                \ start="'''"
                \ skip="'''\('\|[$]\|\\[nrt]\)"
                \ end="'''"
                \ contained
                \ contains=@nix_${lang}_syntax
                \ transparent

              syn cluster nix_ind_strings
                \ add=nix_${lang}_region_IND_STRING

              syn cluster nix_strings
                \ add=nix_${lang}_region_STRING

              " This is required because containedin isn't transitive.
              syn cluster nix_has_dollar_curly
                \ add=@nix_${lang}_syntax
            '') {
              c = { };
              cabal = { };
              diff = { };
              haskell = { };
              python = { };
              lua = { };
              sed.extraStart = ''writeSed[^ \t\r\n]*[ \t\r\n]*"[^"]*"'';
              sh.extraStart = concatStringsSep "\\|" [
                ''
                  write\(A\|Ba\|Da\)sh[^ \t\r\n]*[ \t\r\n]*\("[^"]*"\|[a-z]\+\)''
                "[a-z]*Phase[ \\t\\r\\n]*="
              ];
              yaml = { };
              vim.extraStart = ''
                write[^ \t\r\n]*[ \t\r\n]*"\(\([^"]*\.\)\?vimrc\|[^"]*\.vim\)"'';
              xdefaults = { };
            })}

          " Clear syntax that interferes with nixINSIDE_DOLLAR_CURLY.
          syn clear shVarAssign

          syn region nixINSIDE_DOLLAR_CURLY
            \ matchgroup=NixEnter
            \ start="[$]{"
            \ end="}"
            \ contains=TOP
            \ containedin=@nix_has_dollar_curly
            \ transparent

          syn region nix_inside_curly
            \ matchgroup=NixEnter
            \ start="{"
            \ end="}"
            \ contains=TOP
            \ containedin=nixINSIDE_DOLLAR_CURLY,nix_inside_curly
            \ transparent

          syn match NixQuote /'''\($\|\\.\)/he=s+2
            \ containedin=@nix_ind_strings
            \ contained

          syn match NixQuote /'''\('\|\\.\)/he=s+1
            \ containedin=@nix_ind_strings
            \ contained

          syn match NixQuote /\\./he=s+1
            \ containedin=@nix_strings
            \ contained

          syn sync fromstart

          let b:current_syntax = "nix"

          set isk=@,48-57,_,192-255,-,'
        '';
      "/syntax/sed.vim".text = # vim
        ''
          syn region sedBranch
            \ matchgroup=sedFunction start="T"
            \ matchgroup=sedSemicolon end=";\|$"
            \ contains=sedWhitespace
        '';
    }));

  # active plugins
  # --------------
  extra-runtimepath = with pkgs;
    lib.concatMapStringsSep "," (pkg: "${pkg.rtp}") [
      vimPlugins.Syntastic
      vimPlugins.ack-vim
      vimPlugins.airline
      vimPlugins.vim-nix
      vimPlugins.xptemplate
      vim-tv-plugin
    ];

  # the vimrc
  # ---------
  vimrc = pkgs.writeText "vimrc" ''

    " turn on linenumbers
    " to turn of :set nonumber
    :set number

    " show Trailing Whitespaces
    :set list listchars=tab:»·,trail:¶

    " Map leader is the key for shortcuts
    nnoremap <SPACE> <Nop>
    let mapleader = "\<Space>"

    " move blocks of text in visual mode
    " does not work correctly
    vmap <up> xkP`[V`]
    vmap <down> xp`[V`]

    " search/grep case insensitive
    :set ignorecase

    " tabs should always be 2 spaces
    set et ts=2 sts=2 sw=2

    " installed vim-plugins
    set runtimepath=${extra-runtimepath},$VIMRUNTIME,$HOME/.vim,${nix-xptemplates}

    " syntax highlighting on
    syntax on

    " xptemplates
    " -----------
    " a plugin to insert snippets on demand
    set nocompatible
    filetype plugin on

    " enable cursor cross
    " -------------------
    ":hi CursorLine   cterm=NONE ctermbg=darkred ctermfg=white guibg=darkred guifg=white
    ":hi CursorColumn cterm=NONE ctermbg=darkred ctermfg=white guibg=darkred guifg=white
    :hi CursorLine   cterm=NONE ctermbg=0 guibg=#073642
    :hi CursorColumn cterm=NONE ctermbg=0 guibg=#073642
    set cursorline
    set cursorcolumn

    " save view
    " ---------
    augroup AutoSaveFolds
      autocmd!
      autocmd BufWinLeave * mkview
      autocmd BufWinEnter * silent loadview
    augroup END

    " some language stuff
    " -------------------
    :map <leader>s :setlocal spell spelllang=en

  '';

in {

  # no options
  options.programs.custom.vim.enable = lib.mkEnableOption "vim";

  config = lib.mkIf cfg.enable {
    # create vimrc
    # ------------
    # and load it as config for vim
    environment.variables.VIMINIT = ":so /etc/vimrc";
    environment.etc.vimrc.source = vimrc;

    # set vim to the default editor
    # -----------------------------
    programs.vim.defaultEditor = true;

    # install vim
    # -----------
    environment.systemPackages = [ pkgs.vim ];
  };

}