#!/usr/bin/bash
# Documentation & Code: https://github.com/hedgedoc/cli

### Bash environment setup
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
# https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
set -o errexit
set -o errtrace
set -o nounset
set -o pipefail
IFS=$'\n'
# uncomment to enable printing debug backtrackes on errors:
# trap 'backtrace' ERR

### Load config
SCRIPTNAME="$(basename "$0")"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"

HEDGEDOC_SERVER="${HEDGEDOC_SERVER:-http://127.0.0.1:3000}"

OLD_CODIMD_CONFIG_DIR="${HEDGEDOC_CONFIG_DIR:-$XDG_CONFIG_HOME/codimd}"
HEDGEDOC_CONFIG_DIR="${HEDGEDOC_CONFIG_DIR:-$XDG_CONFIG_HOME/$SCRIPTNAME}"
[ -e "$OLD_CODIMD_CONFIG_DIR" ] && [ ! -e "$HEDGEDOC_CONFIG_DIR" ] && mv "$OLD_CODIMD_CONFIG_DIR" "$HEDGEDOC_CONFIG_DIR"

HEDGEDOC_COOKIES_FILE="${HEDGEDOC_COOKIES_FILE:-$HEDGEDOC_CONFIG_DIR/key.conf}"

# Auto-create XDG compliant config dir (https://www.ctrl.blog/entry/xdg-basedir-scripting)
mkdir -p "$HEDGEDOC_CONFIG_DIR"
chmod 700 "$HEDGEDOC_CONFIG_DIR"
# Remove any trailing slashes from server URL
[[ "$HEDGEDOC_SERVER" == */ ]] && HEDGEDOC_SERVER="${HEDGEDOC_SERVER::-1}"

help_str="
Usage:
    $SCRIPTNAME <command> [args...]

    \$ $SCRIPTNAME login --email
    \$ $SCRIPTNAME import ~/Desktop/test.txt
    \$ $SCRIPTNAME export --md somenoteidhere
    \$ $SCRIPTNAME history

Commands:
    import <path> [note_id]
        Import the contents of a given file as a new HedgeDoc note on the server.
        Takes an optional note_id, if not given it will generate a random one.
        Returns the private note ID $HEDGEDOC_SERVER/<private_note_id>

    publish <note_id>
        Publish a note as view-only html (hosted on server at /s/<note_name>).
        Returns the public note ID $HEDGEDOC_SERVER/s/<public_note_id>

    export --md|--html|--pdf|--slides <note_id> [output_path]
        Export the contents of a given note on the server to a given local path.

    delete <note_id>
        Delete the given note from the server.


    login --email|--ldap [username] [password]
        Authenticate the CLI with the server.
        (If not passed as args, user & passsword will be asked for via stdin)
        Stores session key in \$HEDGEDOC_COOKIES_FILE=$HEDGEDOC_COOKIES_FILE/

    logout
        Deauthenticate the CLI from the server and delete \$HEDGEDOC_COOKIES_FILE.

    profile
        View the current authenticated user details.

    history [--json]
        View the current logged in user's list of accessed notes.

Config:
    Configuration is set via environment variables (e.g. in ~/.bashrc or shell).

    \$HEDGEDOC_SERVER       (defaults to http://127.0.0.1:3000)
    \$HEDGEDOC_CONFIG_DIR   (defaults to $XDG_CONFIG_HOME/$SCRIPTNAME)
    \$HEDGEDOC_COOKIES_FILE (defaults to \$HEDGEDOC_CONFIG_DIR/key.conf)

Usage examples:

    \$ export HEDGEDOC_SERVER='$HEDGEDOC_SERVER'
    \$ export HEDGEDOC_COOKIES_FILE=$HEDGEDOC_COOKIES_FILE

    \$ $SCRIPTNAME import ~/Desktop/example_note.md
    qhmNmwmxSmK1H2oJmkKBQQ
    \$ $SCRIPTNAME import ~/notes/favorite_movies.txt "favorite-movies"
    favorite-movies
    \$ open \"$HEDGEDOC_SERVER/qhmNmwmxSmK1H2oJmkKBQQ\"

    \$ $SCRIPTNAME publish qhmNmwmxSmK1H2oJmkKBQQ
    S1ok9no3f
    \$ $SCRIPTNAME publish favorite-movies
    favorite-movies
    \$ open \"$HEDGEDOC_SERVER\/s/qhmNmwmxSmK1H2oJmkKBQQ\"

    \$ $SCRIPTNAME export --pdf qhmNmwmxSmK1H2oJmkKBQQ example_note.pdf
    \$ $SCRIPTNAME export --slides favorite-movies favorite-movies.zip

    \$ $SCRIPTNAME delete qhmNmwmxSmK1H2oJmkKBQQ
    \$ $SCRIPTNAME delete favorite-movies

    \$ $SCRIPTNAME login --email email@example.net p4sW0rD
    \$ $SCRIPTNAME login --ldap ldap-user
    \$ $SCRIPTNAME login --email
    \$ $SCRIPTNAME logout
    \$ $SCRIPTNAME profile
    \$ $SCRIPTNAME history
    \$ $SCRIPTNAME history --json
"


### Helper Functions

function backtrace () {
    local deptn=${#FUNCNAME[@]}

    for ((i=1; i<deptn; i++)); do
        local func="${FUNCNAME[$i]}"
        local line="${BASH_LINENO[$((i-1))]}"
        local src="${BASH_SOURCE[$((i-1))]}"
        printf '%*s' $i '' # indent
        echo "at: $func(), $src, line $line"
    done
}

function read_input() {
    echo -n "$1" >&2
    read -r user_response
    echo "$user_response"
}
function read_password() {
    echo -n "$1" >&2
    read -rs secure_user_response
    echo "$secure_user_response"
}


### Main Commands

function publish_note() {
    local note_id="$1"
    # Note: authentication not required on servers that allow guests to publish notes
    curl \
        --location \
        --silent \
        --write-out "%{url_effective}" \
        --output /dev/null \
        --cookie "$HEDGEDOC_COOKIES_FILE" \
        "$HEDGEDOC_SERVER/$note_id/publish" \
    | awk -F '/' '{print $5}'
}

function import_note() {
    local input_path="$1" note_id="${2:-}" post_url="new"
    [[ "$note_id" ]] && post_url="new/$note_id"

    # Note: authentication not required on servers that allow guests to create notes
    response="$(
        curl \
            --request POST \
            --silent \
            --write-out "\n%{http_code}" \
            --cookie "$HEDGEDOC_COOKIES_FILE" \
            --header 'Content-Type: text/markdown' \
            --data-binary "@$input_path" \
            "${HEDGEDOC_SERVER}/${post_url}"
    )"
    http_code=$(tail -n1 <<< "$response")
    note_id=$(sed '$ d' <<< "$response" | awk '{print $4}' | awk -F '/' '{print $4}')
    if [[ "$http_code" != 302 ]]; then
        echo "Failed with a $http_code status." >&2
        echo "Hint: You may need to run '$SCRIPTNAME login' to authenticate first." >&2
        return 2
    fi
    echo "${HEDGEDOC_SERVER}/$note_id"
}

function export_note() {
    local export_type="${1#--}" note_id="${2:-}" output_path="${3:-}"

    if [[ ! "$note_id" ]]; then
        echo "Error: You must specify an export type and note id to export." >&2
        echo "" >&2
        echo "Usage: $SCRIPTNAME export --pdf|--md|--html|--slides <note_id> [output_path]" >&2
        echo "For usage exmaples, see: $SCRIPTNAME help" >&2
        return 2
    fi

    # TODO: make these exports more easily available via REST API endpoints on server
    case "$export_type" in
        pdf)
            output_path="${output_path:-$note_id.pdf}"
            # TODO: figure out why wget doesn't work with curl-generated cookies.txt
            # wget --load-cookies "$HEDGEDOC_COOKIES_FILE" -O "$output_path" "$HEDGEDOC_SERVER/$note_id/pdf"
            curl \
                --silent \
                --cookie "$HEDGEDOC_COOKIES_FILE" \
                --output "$output_path" \
                "${HEDGEDOC_SERVER}/${note_id}/pdf" \
            && echo "$output_path"
            ;;
        md)
            output_path="${output_path:-$note_id.md}"
            # TODO: figure out why wget doesn't work with curl-generated cookies.txt
            # wget --load-cookies "$HEDGEDOC_COOKIES_FILE" -O "$output_path" "$HEDGEDOC_SERVER/$note_id/download"
            curl \
                --silent \
                --cookie "$HEDGEDOC_COOKIES_FILE" \
                --output "$output_path" \
                "${HEDGEDOC_SERVER}/${note_id}/download" \
            && echo "$output_path"
            ;;
        html)
            echo "Warning: HedgeDoc CLI will publish the note as part of the html exporting process."
            # hack: unfortunately there is no way to get a note's raw html
            #       directly. but if we publish it first, we can archive the
            #       published page as a flat html file with wget.
            output_path="${output_path:-$note_id.html}"
            public_id="$(publish_note "$note_id")"
            wget \
                --convert-links \
                --load-cookies "$HEDGEDOC_COOKIES_FILE" \
                --output-document "$output_path" \
                "$HEDGEDOC_SERVER/s/$public_id" \
            && echo "$output_path"
            ;;
        slides)
            output_path="${output_path:-$note_id.zip}"
            # hack: there is no way to get slide output as zipfile directly.
            #       we dump it to folder of html files first then zip it.

            # check for any existing conflicting file/dir that wget downloads
            local temp_folder_name="$(
                echo "$HEDGEDOC_SERVER" \
                | awk -F '/' '{print $3}' \
                | awk -F ':' '{print $1}'
            )" # e.g. HEDGEDOC_SERVER=https://127.0.0.1:3000 -> dir is ./127.0.0.1
            if [[ -d "$temp_folder_name" ]] || [[ -f "$temp_folder_name" ]]; then
                echo "Error: Conflicting temp file $temp_folder_name exists." >&2
                echo "Hint: You must move or delete the conflicting file." >&2
                return 2
            fi
            wget \
                --recursive \
                --convert-links \
                --load-cookies "$HEDGEDOC_COOKIES_FILE" \
                "${HEDGEDOC_SERVER}/${note_id}/slide" \
            && zip -r "$output_path" "$temp_folder_name" \
            && rm -Rf "$temp_folder_name" \
            && echo "$output_path"
            ;;
        *)
            echo "Error: unrecognized export type '$export_type'." >&2
            echo "Hint: For usage and examples, see: $SCRIPTNAME help"
            return 2
            ;;
    esac
}

function backup_all_notes() {
    output_path="${1:-archive.zip}"

    curl \
        --silent \
        --cookie "$HEDGEDOC_COOKIES_FILE" \
        --output "$output_path" \
        "${HEDGEDOC_SERVER}/me/export" \
    && echo "$output_path"
}

function user_login() {
    local method="${1#--}" username="${2:-}" password="${3:-}" username_arg=""
    case "$method" in
        email) local username_arg="email" login_url="login";;
        ldap) local username_arg="username" login_url="auth/ldap";;
        *)
            echo "Error: Unrecognized login method '--$method'."
            echo "Usage: $SCRIPTNAME login --email|--ldap [username] [password]"
            echo "Hint: For usage and examples, see: $SCRIPTNAME help"
            return 2
            ;;
    esac

    [[ ! "$username" ]] && username="$(read_input "Please enter your $username_arg: ")"
    [[ ! "$password" ]] && password="$(read_password "Please enter your password: ")"
    echo "" >&2

    curl \
        --request POST \
        --silent \
        --cookie-jar "$HEDGEDOC_COOKIES_FILE" \
        --data-urlencode "$username_arg=$username" \
        --data-urlencode "password=$password" \
        "${HEDGEDOC_SERVER}/${login_url}" > /dev/null

    if is_authenticated; then
        echo "Logged in to $HEDGEDOC_SERVER as $username using $method auth."
        return 0
    else
        echo "Failed to login to $HEDGEDOC_SERVER as $username using $method auth."
        return 1
    fi
}

function is_authenticated() {
    curl \
        --silent \
        --cookie "$HEDGEDOC_COOKIES_FILE" \
        "${HEDGEDOC_SERVER}/me" \
    | jq -r '.status' \
    | grep -q "ok"
}

function assert_is_authenticated() {
    if ! is_authenticated; then
        echo "Error: Need to authenticate." >&2
        echo "Hint: $SCRIPTNAME login --email|--ldap [username] [password]" >&2
        echo "      For usage examples, see: $SCRIPTNAME help"
        return 1
    fi
}

function user_logout() {
    curl \
        --silent \
        --cookie "$HEDGEDOC_COOKIES_FILE" \
        --cookie-jar "$HEDGEDOC_COOKIES_FILE" \
        "${HEDGEDOC_SERVER}/logout"\ > /dev/null || true

    rm "$HEDGEDOC_COOKIES_FILE" || true
}

function user_profile() {
    assert_is_authenticated

    local response="$(
        curl \
            --silent \
            --cookie "$HEDGEDOC_COOKIES_FILE" \
            "${HEDGEDOC_SERVER}/me"
    )"

    if [[ "$(echo "$response" | jq -r '.status')" != "ok" ]]; then
        echo "Error: Got unexpected response from server." >&2
        echo "$response" >&2
        return 1
    else
        echo "You are logged in to a HedgeDoc server."
        echo ""
        echo "HEDGEDOC_SERVER=$HEDGEDOC_SERVER"
        echo "HEDGEDOC_COOKIES_FILE=$HEDGEDOC_COOKIES_FILE"
        echo ""
        echo "USER_NAME=$(echo "$response" | jq -r '.name')"
        echo "USER_ID=$(echo "$response" | jq -r '.id')"
        echo "USER_PHOTO=$(echo "$response" | jq -r '.photo')"
    fi
}

function user_history() {
    assert_is_authenticated

    curl \
        --silent \
        --cookie "$HEDGEDOC_COOKIES_FILE" \
        "${HEDGEDOC_SERVER}/history" \
    | ([[ ${1:-} = --json ]] && cat || jq -r '["ID", "", "", "Name"], (.history[] | [.id, .text]) | @tsv')
}

function delete_note() {
    local note_id="${1:-}"
    if [[ ! "$note_id" ]]; then
        echo "Error: You must specify a note id to delete." >&2
        echo "Usage: $SCRIPTNAME delete <note_id>" >&2
        return 2
    fi

    assert_is_authenticated
    curl \
        --request DELETE \
        --silent \
        --cookie "$HEDGEDOC_COOKIES_FILE" \
        "${HEDGEDOC_SERVER}/history/${note_id}"
}

function main() {
    local subcommand="$1"; shift

    case "$subcommand" in
        help)
            echo "$help_str"
            return 0
            ;;
        import|import-as)
            import_note "${1:-}" "${2:-}"
            ;;
        publish)
            publish_note "${1:-}"
            ;;
        export)
            export_note "${1:-}" "${2:-}" "${3:-}"
            ;;
        backup)
            backup_all_notes "${1:-}"
            ;;
        delete)
            delete_note "${1:-}"
            ;;
        login)
            user_login "${1:-}" "${2:-}" "${3:-}"
            ;;
        logout)
            user_logout
            ;;
        profile)
            user_profile
            ;;
        history)
            user_history "${1:-}"
            ;;
        *)
            echo "Error: unrecognized command '$subcommand'." >&2
            echo "Hint: For usage and examples, see: $SCRIPTNAME help"
            return 2
            ;;
    esac
}

# Do the usage function of no Command line arguments are given.
if [[ $# -eq 0 ]]; then
  set -- "help"
fi

# Allow importing funcs without running main by using `source ./bin/hedgedoc --import`
if [[ "${1:-}" != "--import" ]]; then
    main "$@" || exit $?
fi

