;; Global configuration:

;; don't do the startup stuff
(setq initial-scratch-message "")
(setq inhibit-startup-message t)
(setq inhibit-startup-screen t)

;; don't show the menu bar
(menu-bar-mode 0)
(if (display-graphic-p)
    (progn
      (scroll-bar-mode 0)
      (tool-bar-mode 0)))

;; set up sizing in a reasonable way
(setq column-number-mode t)
(setq default-tab-width 4)
(setq-default indent-tabs-mode nil)
(set-fill-column 120)

;; miscellaneous global config
(setq vc-follow-symlinks t)
(setq vc-handled-backends nil)
(setq require-final-newline t)

;; change backups to be less inline
(make-directory "~/.emacs-autosave" t)
(make-directory "~/.emacs-backups" t)
(setq auto-save-file-name-transforms '((".*" "~/.emacs-autosave/" t)))
(setq backup-directory-alist '(("." . "~/.emacs-backups")))

;; some disables
(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)


;; *elpa and package setup
(require 'package)
(package-initialize)
(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives
             '("gelpa" . "http://gelpa.gdritter.com/") t)

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
(if (getenv "FORCE_REFRESH")
    (package-refresh-contents))
(require 'use-package)


;; Text editing: Markdown, org, et cetera

;; associate markdown with more stuff
(use-package markdown-mode
  :ensure t
  :init
  (progn
    (add-to-list 'auto-mode-alist '("\\.text\\'" . markdown-mode))
    (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
    (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
    (add-hook 'markdown-mode-hook 'visual-line-mode)))

(add-hook 'rst-mode-hook 'visual-line-mode)

;; also pandoc specifically
(use-package pandoc-mode
  :ensure t
  :config (add-hook 'markdown-mode-hook 'pandoc-mode))

;; Evil, just in case:
(use-package evil :ensure t)

;; olivetti mode (for editing single prose files)
(use-package olivetti
  :ensure t)

;; Org-mode
(use-package org
  :ensure t
  :init (add-hook 'org-mode-hook 'visual-line-mode))

;; Letting us easily move in outlines
(defun gdritter/add-meta-bindings ()
  (let ((map outline-minor-mode-map))
    (define-key map (kbd "M-<up>") 'outline-previous-visible-heading)
    (define-key map (kbd "M-<down>") 'outline-next-visible-heading)))

(add-hook 'outline-minor-mode-hook 'gdritter/add-meta-bindings)

;; TeLML
(use-package telml-mode
  :ensure t
  :init
  (add-hook 'telml-mode-hook 'visual-line-mode))

;; display Github-like emoji if we're in a text-editing mode of some kind
(defun gdritter/enable-emoji-hook ()
  (if (member major-mode '(markdown-mode pandoc-mode text-mode telml-mode))
      (emojify-mode)))

(use-package emojify
  :ensure t
  :init (add-hook 'after-change-major-mode-hook
                  'gdritter/enable-emoji-hook))

(use-package whitespace
  :ensure t
  :init
  (progn
    (setq whitespace-style '(face empty tabs trailing))
    (global-whitespace-mode t)))

(use-package undo-tree :ensure t)


;; Special logic for handling Pandoc front-matter

;; Pandoc can begin with YAML metadata, and sometimes a pandoc file
;; will indicate which format it should be using. I'm not going to
;; write a full YAML parser in elisp! But usually, parsing simple
;; key-value pairs should be sufficient.
(defun gdritter/pandoc-parse-basic-meta-block ()
  (let ((here (point)))
    (goto-char 0)
    (let ((rs
           (if (re-search-forward "^---\n" nil t)
               (let ((block-start (point)))
                 (if (re-search-forward "^\\(---\\|...\\)\n" nil t)
                     (progn (goto-char block-start)
                            (gdritter/pandoc-parse-kv-section))
                   (progn (message "no metadata block found")
                          nil))))))
      (goto-char here)
      rs)))

;; This tries to parse all the simple key: value forms it can
(defun gdritter/pandoc-parse-kv-section ()
  (if (looking-at "^\\(---\\|...\\)\n")
      '()
    (let ((rs (re-search-forward "^\\([A-Za-z0-9_]+\\):[ ]*\\(.*\\)\n" nil t)))
      (if rs
          (cons (cons (match-string 1)
                      (match-string 2))
                (gdritter/pandoc-parse-kv-section))
        (forward-line 1)))))

;; This will try to dispatch which mode to open based on the
;; metadata block. If the metadata block doesn't contain a format
;; key, then it'll default to Markdown with Pandoc extensions.
(defun gdritter/pandoc-choose-mode ()
  (interactive)
  (let* ((meta (gdritter/pandoc-parse-basic-meta-block))
         (format (assoc-string "format" meta))
         (fmt (if format (cdr format) nil)))
    (cond ((equal fmt "org") (org-mode))
          ((equal fmt "latex") (latex-mode))
          ((equal fmt "rst") (rst-mode))
          ((equal fmt "html") (web-mode))
          (t (markdown-mode)))))

(add-to-list 'auto-mode-alist '("\\.page\\'" . gdritter/pandoc-choose-mode))
(add-to-list 'auto-mode-alist '("\\.pandoc\\'" . gdritter/pandoc-choose-mode))


;; magit!
(use-package magit
  :ensure t
  :defer t
  :init (progn
          (setq magit-last-seen-setup-instructions "1.4.0")))


;; LSP and dev environment stuff
(use-package eglot
  :ensure t
  :config
  (progn
    (add-hook 'c++-mode-hook 'eglot-ensure)
    (add-hook 'rust-mode-hook 'eglot-ensure)
    (add-hook 'python-mode-hook 'eglot-ensure)
    (add-hook 'go-mode-hook 'eglot-ensure)
    (add-hook 'ruby-mode-hook 'eglot-ensure)
    (add-hook 'before-save-hook 'eglot-format)
    (add-to-list
     'eglot-server-programs
     '(ruby-mode . ("rass" "sorbet_rubocop")))))

(use-package breadcrumb :ensure t)
(breadcrumb-mode)

(use-package yasnippet
  :ensure t
  :config (yas-global-mode 1))

(use-package dirvish
  :ensure t
  :config (dirvish-override-dired-mode))


;; for C and C++
(defconst gdritter/cc-style
  '("cc-mode" (c-offsets-alist . ((innamespace . [0])))))
(c-add-style "gdritter/cc-style" gdritter/cc-style)
(setq c-default-style "gdritter/cc-style" c-basic-offset 4)
(c-set-offset 'substatement-open 0)



;; for web (HTML, JavaScript, TypeScript CSS)
(use-package web-mode
  :ensure t
  :init (progn
          (mapcar (lambda (extension)
                    (add-to-list 'auto-mode-alist
                                 `(,extension . web-mode)))
                  '("\\.html?\\'"
                    "\\.tpl\\.php\\'"
                    "\\.[agj]sp\\'"
                    "\\.as[cp]x\\'"
                    "\\.erb\\'"
                    "\\.mustache\\'"
                    "\\.djhtml\\'"))
          (setq web-mode-markup-indent-offset 2)
          (setq web-mode-css-indent-offset 2)
          (setq web-mode-code-indent-offset 2)
          (setq web-mode-attr-indent-offset 2)))

(use-package rjsx-mode :ensure t)
(setq js2-basic-offset 2)

(use-package typescript-mode
  :ensure t
  :init (add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-mode)))
(setq-default typescript-indent-level 2)


;; for Ruby
(use-package ruby-mode :ensure t)
(add-to-list 'auto-mode-alist '("\\.rbi\\'" . ruby-mode))

(setq ruby-deep-indent-param nil)
(setq ruby-insert-encoding-magic-comment nil)


;; for Go
(use-package go-mode :ensure t)
(setq gofmt-command "goimports")
(add-hook 'before-save-hook 'gofmt-before-save)


;; for Rust
(use-package rust-mode
  :ensure t
  :init (setq rust-format-on-save t))

(use-package company
  :ensure t)

(use-package cargo
  :ensure t
  :init (add-hook 'rust-mode-hook #'cargo-minor-mode))

(use-package flycheck-rust
  :ensure t
  :init (add-hook 'flycheck-mode-hook #'flycheck-rust-setup))


;; for Haskell
(use-package haskell-mode
  :ensure t
  :init
  (progn
    (setq haskell-mode-hook 'turn-on-haskell-simple-indent)
    (add-to-list 'Info-default-directory-list "/usr/lib/emacs/haskell-mode/")
    (setq haskell-mode-hook '(turn-on-haskell-indentation))
    (add-hook 'haskell-mode-hook 'outline-minor-mode)
    (setq outline-regexp "-- \\*+")))

(use-package dante
  :ensure t
  :commands 'dante-mode
  :init
  (add-hook 'haskell-mode-hook 'dante-mode)
  (add-hook 'haskell-mode-hook 'flycheck-mode))


;; A slew of miscellaneous lesser-used modes

;; for redo syntax highlighting
(add-to-list 'auto-mode-alist '("\\.do\\'" . sh-mode))
;; for void linux template file highlighting
(add-to-list 'auto-mode-alist '("\\template\\'" . sh-mode))

;; for build-ish formats
(use-package dockerfile-mode :ensure t :defer t)
(use-package meson-mode :ensure t :defer t)
(use-package bazel
  :ensure t
  :init (add-to-list 'auto-mode-alist '("\\.sky\\'" . bazel-mode)))

;; for miscellaneous programming languages
(use-package lua-mode :ensure t :defer t :init (setq lua-indent-level 2))
(use-package fennel-mode :ensure t)
(use-package scala-mode :ensure t)
(use-package glsl-mode :ensure t :defer t)
(use-package ponylang-mode :ensure t :defer t)
(use-package tuareg :ensure t :defer t)

(use-package dash :ensure t)
(use-package dash-functional :ensure t)


;; various data format modes
(use-package toml-mode
  :ensure t)
(use-package yaml-mode
  :ensure t)
(use-package adnot-mode
  :ensure t)
(use-package electric-boogaloo-mode
  :ensure t)


;; for completions
(use-package ivy
  :ensure t
  :init (progn
          (setq ivy-height-alist
                (cons
                 (cons 't (lambda (_) (/ (frame-height) 2)))
                 '()))
          (setq ivy-use-virtual-buffers t)))

(use-package counsel
  :ensure t
  :init (progn
          (global-set-key (kbd "C-x C-f") #'counsel-find-file)
          (global-set-key (kbd "C-x C-b") #'ivy-switch-buffer)
          (with-eval-after-load 'counsel
            (define-key counsel-find-file-map (kbd "C-l") #'counsel-up-directory))))


;; fonts, colors, and ligatures
(defvar *gdritter-ligature-list*
  '(
    ;; equality operators
    "==" "===" "!=" "!=="
    ;; comparison operators
    "<=" ">=" "<=>"
    ;; scope
    "::" ":::"
    ;; right arrows
    "->" "-->" "=>" "==>"
    ;; left arrows
    "<-" "<--" "<=" "<=="
    ;; various operators
    "++" "--" "&&" "||" "&&=" "||=" "=~" ":="
    ;; shift operators
    "<<" "<<<" ">>" ">>>"
    ;; comment syntaxes
    "//" "///" "/*" "*/"
    ;; some haskell nonsense
    "<>" ">>=" "=<<" ">=>" "<=<" "=>" "=<="
    "<$" "<$>" "$>" "<+" "<+>" "+>" "<*" "<*>" "*>"
    "<|>"
    ))

(when (display-graphic-p)
  ;; color theming
  (use-package color-theme-modern
    :ensure t
    :init
    (progn
      (use-package zenburn-theme :ensure t)
      (use-package solarized-theme :ensure t)
      (use-package color-theme-sanityinc-tomorrow :ensure t)
      (let ((theme (getenv "THEME")))
        (cond
         ((string= theme "solarized-dark")
          (load-theme 'solarized-dark t))
         ((string= theme "solarized-light")
          (load-theme 'solarized-light t))
         ((string= theme "tomorrow")
          (load-theme 'sanityinc-tomorrow-night t))
         (t (load-theme 'zenburn t))))))
  ;; ligatures
  (use-package ligature
    :ensure t
    :init
    (progn
      (ligature-set-ligatures 't *gdritter-ligature-list*)
      (global-ligature-mode 't))))




;; spacing fixes!
(defun fix-spacing ()
  (let ((here (point)))
    (interactive)
    (untabify (point-min) (point-max))
    (replace-regexp "\s*$" "")
    (goto-char here)
    (princ "Fixing all spacing...")))

(setq gdritter/spacing-modes
      '(scala-mode
        c++-mode
        asm-mode
        haskell-mode
        haskell-cabal-mode
        emacs-lisp-mode
        lisp-mode
        scheme-mode
        d-mode
        erlang-mode
        tuareg-mode))

(defun fix-spacing-hook ()
  (when (member major-mode gdritter/spacing-modes)
    (fix-spacing)))

(defun gdritter/change-whitespace-mode-hook ()
  (if (member major-mode '(c-mode c++-mode go-mode org-mode))
      (progn (setq whitespace-style '(face))
             (global-whitespace-mode t))))

(add-hook 'before-save-hook 'fix-spacing-hook)

(add-hook 'after-change-major-mode-hook
          'gdritter/change-whitespace-mode-hook)


;; some environment variable directives
(if (getenv "EVIL") (evil-mode))

;; Any machine-specific setup can go in an external
;; file. If it exists, we should load it:
(if (file-exists-p "~/.local.el")
    (load "~/.local.el"))


;; The custom-set-variables stuff

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(package-selected-packages nil))
(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:family "Fira Code" :foundry "unknown" :slant normal :weight normal :height 120 :width normal))))
 '(lsp-ui-doc-background ((t (:background "black"))))
 '(lsp-ui-sideline-current-symbol ((t (:foreground "white" :weight ultra-bold :height 0.99)))))
