# Emacs-based bookmark system

autoload colors && colors
zmodload -F zsh/stat b:zstat
zmodload zsh/datetime
zmodload zsh/mapfile

local __bm_bookmark_cache=()
let __bm_last_read_time=-1

function __bm_offset_to_row_col {
    let off="${2}"
    let row=1
    let col=0
    for line in "${(@f)mapfile[${1}]}"; do
        let len="${#line}"
        if (( off > len )); then
            off='off - (len + 1)'
            if (( off <= 0 )); then
                (( len == 0 )) && col=0 || col='len - 1'
                break
            fi
            row+=1
        else
            col='off'
            off=0
            break;
        fi
    done <"${1}"
    if (( off > 0 )); then
        row+=-1
    fi
    printf '%d\0%d' "${row}" "${col}"
}

function __bm_find_user_emacs_dir {
    [[ -f "${HOME}/.emacs.d/var/bookmark-default.el" ]] &&
        { printf '%s' "${HOME}/.emacs.d/var/bookmark-default.el"; return 0 }
    [[ -f "${HOME}/.emacs.d/bookmark-default.el" ]] &&
        { printf '%s' "${HOME}/.emacs.d/bookmark-default.el"; return 0 }
    [[ -f "${HOME}/.config/emacs/var/bookmark-default.el" ]] &&
        { printf '%s' "${HOME}/.config/emacs/var/bookmark-default.el"; return 0 }
    [[ -f "${HOME}/.config/emacs/bookmark-default.el" ]] &&
        { printf '%s' "${HOME}/.config/emacs/bookmark-default.el"; return 0 }
    [[ -f "${XDG_CONFIG_HOME}/emacs/var/bookmark-default.el" ]] &&
        { printf '%s' "${XDG_CONFIG_HOME}/emacs/var/bookmark-default.el"; return 0 }
    [[ -f "${XDG_CONFIG_HOME}/emacs/bookmark-default.el" ]] &&
        { printf '%s' "${XDG_CONFIG_HOME}/emacs/bookmark-default.el"; return 0 }
    printf 'Could not discover Emacs bookmark file! Please set $BM_MODE to
"daemon" or define $BM_BOOKMARK_PATH!\n'
    return 1
}
BM_BOOKMARK_PATH="${BM_BOOKMARK_PATH:-"$(__bm_find_user_emacs_dir)"}"

function __bm_hash_dirs {
    # First empty the hash table
    hash -dr

    # Then add the hash dirs
    for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
        local name="${__bm_bookmark_cache[${i}]://=/}"
        hash -d "${name}=${__bm_bookmark_cache[${i} + 2]}"
    done
}

function __bm_update_bookmark_list {
    __bm_last_read_time="${EPOCHSECONDS}"
    local args
    local script
    case "${BM_MODE}" in
        'daemon')
            script=$(<<'EOF'
(progn
  (require 'server)
  (dolist (entry (server-eval-at
                  "server"
                  '(when (boundp 'bookmark-alist)
                     bookmark-alist)))
    (let ((path (alist-get 'filename (cdr entry) ""))
          (pos (alist-get 'position (cdr entry) 1)))
      (princ (format "%s\0%s\0%s\0%s\0" (car entry) path
                     (expand-file-name path) pos)))))
EOF
                  )
            ;;
        ''|'emacs')
            args="--insert=${BM_BOOKMARK_PATH}"
            script=$(<<'EOF'
(progn
  (dolist (entry (read (current-buffer)))
    (let ((path (alist-get 'filename (cdr entry) ""))
          (pos (alist-get 'position (cdr entry) 1)))
      (princ (format "%s\0%s\0%s\0%s\0" (car entry) path
                     (expand-file-name path) pos)))))
EOF
                  )
            ;;
        *)
        printf 'Unknown value for $BM_MODE: "%s"\n' "${BM_MODE}"
        return 1
        ;;
    esac
    __bm_bookmark_cache=(${(0)"$(command emacs --batch ${args} --eval "${script}")"})
    __bm_hash_dirs
}
function __bm_bookmark_location {
    local parts=(${(s:/:)"${2}"})
    local bm_name="${parts[1]}"
    local rest_arr=(${parts:1})
    local rest="${(j:/:)rest_arr}"
    for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
        if [[ "${bm_name}" = "${__bm_bookmark_cache[${i}]}" ]]; then
            __bm_res=("${__bm_bookmark_cache[${i} + 2]}"
                      "${__bm_bookmark_cache[${i} + 3]}"
                      "${rest}")
            return 0
        fi
    done
    __bm_res=()
    return 1
}
function __bm_list_bookmarks {
    for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
        local name="${__bm_bookmark_cache[${i}]}"
        local pretty_path="${__bm_bookmark_cache[${i} + 1]}"
        local abs_path="${__bm_bookmark_cache[${i} + 2]}"
        local print_color=""
        if [[ -d "${abs_path}" ]]; then
            print_color="${bold_color}${fg[blue]}"
        fi
        printf "${print_color}%s${reset_color} => %s\n" \
               "${name}" "${pretty_path}"
    done
}

function _bookmarks {
    for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
        compadd -q "${__bm_bookmark_cache[${i}]}"
    done
}

alias lsbm="__bm_update_bookmark_list && __bm_list_bookmarks"
function bm {
    __bm_update_bookmark_list || \
        { printf 'Updating bookmark list failed!\n'; return 1 }
    (( ${#} == 0 )) && { __bm_list_bookmarks; return }
    local __bm_res
    __bm_bookmark_location __bm_res "${1}"
    local bm_loc="${__bm_res[1]}"
    local target="${__bm_res[1]}/${__bm_res[3]}"
    if (( ${#__bm_res} != 0 )) && [[ -e "${bm_loc}" ]]; then
        if [[ -d "${target}" ]]; then
            cd "${target}"
            [[ "${BM_CWD_LS}" == 'true' ]] && ls || true
        elif [[ -e "${bm_loc}" ]]; then
            let offset="${__bm_res[2]}"
            local rowcol=(${(0)"$(__bm_offset_to_row_col "${bm_loc}" "${offset}")"})
            ${=EDITOR} "+${rowcol[1]}:${rowcol[2]}" "${bm_loc}"
        else
            printf 'Bookmark exists, but trailing path doesn'"'"'t: "%s"\n' \
                   "${(q)__bm_res[3]}"
            return 1
        fi
    else
        printf 'No such bookmark: "%s"\n' "${(q)1}"
        return 1
    fi
}
function _bm {
    (( "${CURRENT}" == 2 )) || return
    local arg="${(Q)words[${CURRENT}]}"
    if ! [[ "${arg}" == */* ]]; then
        for ((i = 1; i < ${#__bm_bookmark_cache}; i+=4)); do
            compadd -q -S '/' -- "${__bm_bookmark_cache[${i}]}"
        done
    else
        local __bm_res
        __bm_bookmark_location __bm_res "${arg}"
        if [[ -d "${__bm_res[1]}" ]]; then
            local parts=(${(s:/:)__bm_res[3]})
            local bm="${${(s:/:)${arg}}[1]}"
            local subdir="${(j:/:)parts[1,-2]}"
            local search="${parts[${#parts}]}"
            if ((${#parts} > 0)) && [[ "${arg}" == */ ]]; then
                subdir="${subdir}/${search}"
                subdir="${subdir#/}"
                search=""
            fi
            local pre_path="${__bm_res[1]}/${subdir}/"
            local raw_arg="${words[${CURRENT}]}"
            local prefix="${${(s:/:)${raw_arg}}[1]}/${subdir}"
            if ! [[ -z "${subdir}" ]]; then
                prefix+='/'
            fi
            compset -P "${(b)prefix}"
            for file in "${pre_path}"*; do
                compadd -W "${pre_path}" -f "${file:t}"
            done
        fi
    fi
}
compdef _bm bm

function bmadd {
    if [[ "${1}" = '-h' ]]; then
        printf 'usage: bmadd [PATH] [NAME]\n'
        return 0
    fi
    [[ "${1}" = '--' ]] && shift 1
    local name
    local loc
    case "${#}" in
        0)
            loc="${PWD}"
            name="${PWD:t}"
            ;;
        1)
            loc="${1}"
            name="${1:t}"
            ;;
        *)
        loc="${1}"
        name="${2}"
        ;;
    esac
    __bm_update_bookmark_list
    local __bm_res
    if __bm_bookmark_location __bm_res "${name}" >/dev/null; then
        printf 'Bookmark "%s" already exists. Overwrite it? [y/N] ' "${(q)name}"
        read -q
        let ans=${?}
        printf '\n'
        (( ${ans} != 0 )) && return 1
    fi
    local res="$(emacsclient --eval \
"(let* ((loc (pop server-eval-args-left))
        (name (pop server-eval-args-left))
        (res (with-temp-buffer
               (set-visited-file-name loc t nil)
               (bookmark-set name)
               (set-buffer-modified-p nil)))
        (inhibit-message t))
   (bookmark-save)
   res)" "${loc}" "${name}")"
    [[ "${res}" = 'nil' ]] && printf 'Added bookmark "%s"\n' "${(q)name}" \
            || { printf '%s\n' "${res}"; return 1 }
    __bm_update_bookmark_list
}
function _bmadd {
    _arguments ':file:_files' ':name'
}
compdef _bmadd bmadd

function bmrm {
    if [[ "${1}" = '-h' ]]; then
        printf 'usage: bmrm [NAME]\n'
        return 0
    fi
    [[ "${1}" = '--' ]] && shift 1
    if (( ${#} < 1 )); then
        printf 'usage: bmrm [NAME]\n'
        return 1
    fi
    __bm_update_bookmark_list
    local __bm_res
    __bm_bookmark_location __bm_res "${1}" >/dev/null || \
        { printf 'No such bookmark: "%s"\n' "${1}"; return 1 }
    printf 'Really delete "%s"? [y/N] ' "${(q)1}"
    if read -q; then
        printf '\n'
        local res="$(emacsclient --eval \
"(let* ((res (bookmark-delete (pop server-eval-args-left)))
        (inhibit-message t))
   (bookmark-save)
   res)" "${1}")"
        [[ "${res}" = 'nil' ]] && printf 'Deleted bookmark "%s"\n' "${(q)1}" \
                || { printf '%s\n' "${res}"; return 1 }

        __bm_update_bookmark_list
    else
        printf '\n'
        return 1
    fi
}
function _bmrm {
    _arguments ':bookmark:_bookmarks'
}
compdef _bmrm bmrm

function __bm_precmd_hook {
    # Auto reload
    if [[ "${BM_AUTO_RELOAD}" == true ]] &&
           (( ${__bm_last_read_time} < $(zstat +mtime "${BM_BOOKMARK_PATH}") )); then
        __bm_update_bookmark_list
    fi
}
(( ${precmd_functions[(I)__bm_precmd_hook]} )) ||
    precmd_functions+=(__bm_precmd_hook)

__bm_update_bookmark_list