#! /usr/bin/bash
#
# rfc
#
# Author: Baptiste Fontaine
# License: MIT
# Version: 1.0.1 (2024/10/28)
#
# URL: https://github.com/bfontaine/rfc
#

__rfc() {

    local VERSION='1.0.1'
    local PAGER=${PAGER:-less}
    local fetch_cmd=${CURL:-curl}
    local rfc_dir
    local default_rfc_dir="$HOME/.cache/RFCs"
    local code

    # Error codes
    local NOT_FOUND=1
    local UNRECOGNIZED_CMD=2
    local NETWORK_ERROR=3
    local NO_CURL_WGET=4

    # URLs
    local REPO_HOME='https://github.com/bfontaine/rfc'
    local ISSUES_URL='https://github.com/bfontaine/rfc/issues'

    local RFCS_TGZ_BASEURL='https://www.rfc-editor.org/in-notes/tar/'
    local RFCS_BASEURL='https://www.rfc-editor.org/rfc/'
    local DRAFTS_BASEURL='https://www.ietf.org/id/'

    if [ -n "$RFC_DIR" ]; then
      rfc_dir="$RFC_DIR"
    elif [ -n "$XDG_CACHE_HOME" ]; then
      rfc_dir="$XDG_CACHE_HOME/RFCs"
    else
      rfc_dir="$default_rfc_dir"
    fi

    # $rfc_dir must be absolute
    [ "${rfc_dir:0:1}" != "/" ] && rfc_dir="$PWD/$rfc_dir"

    print_rfc() {
        $PAGER "$rfc_dir/$1"
    }

    fetch_url() {
        case $fetch_cmd in
            curl)
                curl -fs "$1" >| "$2";
                case "$?" in
                    6|7) return $NETWORK_ERROR ;;
                    22)  return $NOT_FOUND ;;
                    *)   return 0 ;;
                esac ;;

            wget)
                wget -cq "$1" -O "$2";
                case "$?" in
                    4) return $NETWORK_ERROR ;;
                    8) return $NOT_FOUND ;;
                    *) return 0 ;;
                esac ;;
        esac
    }

    get_ftp_url() {
        local tmp
        tmp=$(mktemp)
        # $1 = pattern of the file we're looking for
        local pattern="$1"
        local fetch_exit_code=

        fetch_url "$RFCS_TGZ_BASEURL" "$tmp"
        fetch_exit_code=$?

        if [ "$fetch_exit_code" -ne 0 ]; then
          return $fetch_exit_code
        fi

        # Lines have the following format:
        #   <li><a href="RFCs0001-0500.tar.gz"> RFCs0001-0500.tar.gz</a></li>
        # We want this __^^^^^^^^^^^^^^^^^^^^
        < "$tmp" grep "$pattern" | cut -d'"' -f2
    }

    get_rfc() {
        fetch_url "$RFCS_BASEURL/rfc$1.txt" "$rfc_dir/$1"
        return $?
    }

    get_draft() {
        fetch_url "$DRAFTS_BASEURL/$1.txt" "$rfc_dir/$1"
        return $?
    }

    migrate_rfc_dir() {
        local previous_rfc_dir="$HOME/.RFCs"
        if [ -f "$previous_rfc_dir/_404s" ] && [ "$rfc_dir" != "$previous_rfc_dir" ]; then
          # Migrate _404s
          cat "$previous_rfc_dir/_404s" >> "$rfc_dir/_404s"
          rm "$previous_rfc_dir/_404s"
          # Migrate the RFC files
          mv -f "$previous_rfc_dir"/* "$rfc_dir/"

          echo "In 1.0.0 the RFC cache directory moved from $previous_rfc_dir to $rfc_dir." >&2
          echo "All files were migrated; you can now run \`rmdir '$previous_rfc_dir'\`" >&2
        fi
    }

    init_rfc_dir() {
        mkdir -p "$rfc_dir"
        touch "$rfc_dir"/_404s

        if [ -z "$RFC_TEST" ]; then
          migrate_rfc_dir
        fi
    }

    print_not_found_error() {
        if [[ "$1" == draft* ]]; then
            echo "There's no such draft."
        else
            echo "There's no such RFC."
        fi
    }

    ## Subcommands ##

    # rfc list
    list() {
        \ls -1 "$rfc_dir" | grep '^[0-9]\{1,\}$' | sort -n | sed 's/^/RFC /'
    }

    # rfc search <pattern>
    search() {
        if [ -z "$1" ]; then
            echo 'Usage: rfc search "<pattern>"'
            return $UNRECOGNIZED_CMD;
        fi
        grep -RIs --exclude _404s $@ "$rfc_dir" | sed "s%$rfc_dir/%RFC %"
        return 0
    }

    # rfc sync [week|month|all]
    # shellcheck disable=SC2164
    sync_rfcs() {
        local name=RFC-all.tar.gz
        case "$1" in
            month|30|30days) name=$(get_ftp_url "30daysTo.*gz") ;;
            week|7|7days)    name=$(get_ftp_url "7daysTo.*gz") ;;
            all) ;;
            *)
              # Support 'sync' without any argument as an alias to 'sync all'
              if [ ! -z "$1" ]; then
                echo "Unrecognized 'sync' target: $1"
                return $UNRECOGNIZED_CMD
              fi ;;
        esac
        # shellcheck disable=SC2181
        if [ $? -ne 0 ]; then
          return $?
        fi

        # This shouldn't happen.
        if [ -z "$name" ]; then
          echo "I couldn't find the requested archive." >&2
          echo "Please report this: $ISSUES_URL" >&2
          return $NOT_FOUND
        fi

        mkdir -p "$rfc_dir/_tmp"
        if fetch_url "$RFCS_TGZ_BASEURL$name" "$rfc_dir/_tmp/$name"; then
          :
        else
          exit $NETWORK_ERROR
        fi
        cd "$rfc_dir/_tmp"
        tar -xzf "$name"
        rm -f "$name"
        [ -d in-notes ] && cd in-notes
        for f in rfc*.txt; do
            echo "$f" | grep -q '^rfc[0-9]\{1,\}\.txt$'
            if [ "$?" -eq "0" ]; then
                n=${f##rfc};
                n=${n%%.txt};
                mv -f "$f" "$rfc_dir/$n"
            fi
        done
        rm -rf "$rfc_dir/_tmp"
    }

    # rfc help
    print_usage() {
        # shellcheck disable=SC2016
        echo 'Usage:
    rfc --version             # display the version number and exit
    rfc --help                # display this text and exit

    rfc <number>              # display the RFC <number>
    rfc search [OPTS] X       # Search for X in local RFCs using grep with OPTS
                                passed through
    rfc list                  # List locally available RFCs
    rfc sync [week|month|all] # batch download RFCs. `week` and `month`
                                respectively download only RFCs that were
                                added/updated during the last week/month.
                                `all` (default) downloads all RFCs. It might
                                take some time, be patient.
    rfc clear                 # clear the cache

The default cache directory is '"$default_rfc_dir"'.
The current one is '"$rfc_dir"'.'
    }

    # rfc version
    print_version() {
        echo "rfc v$VERSION - $REPO_HOME"
    }

    ## /commands ##

    if [ $# -eq 0 ]; then
        print_usage
        return 0
    fi

    # Prefix everything with --debug to enable the debugging output on STDERR.
    if [ "$1" == "--debug" ]; then
      shift ;
      echo "Enabling debug mode." >&2 ;
      set -x ;
    fi

    if which curl > /dev/null 2>&1; then
        if which wget > /dev/null 2>&1; then
          fetch_cmd='wget'
        else
            echo "Error: You need Wget or cURL!"
            return $NO_CURL_WGET;
        fi
    fi

    init_rfc_dir

    case "$1" in

        -v|--version|version)
            print_version
            return 0;;

        -h|--help|-help|help)
            print_usage
            return 0;;

        -*)
            echo "Unrecognized option: $1"
            print_usage
            return $UNRECOGNIZED_CMD;;

        clear|clean|clr)
            rm -rf "$rfc_dir"
            return 0;;

        ls|list)
            list
            return 0;;

        search|find)
            shift;
            search "$@"
            return $?;;

        sync|download)
            shift;
            sync_rfcs "$@"
            return $?;;

        _*)
            print_usage
            return 1;;

        *)
        if grep -q "^$1\$" "$rfc_dir/_404s" 2> /dev/null; then
            print_not_found_error "$1"
            return $NOT_FOUND

        elif [ ! -f "$rfc_dir/$1" ]; then

            if [[ "$1" == draft* ]]; then
                get_draft "$1"
            else
                get_rfc "$1"
            fi

            code=$?

            if [ $code -eq $NETWORK_ERROR ]; then
                echo "Unable to connect to the network."
                [ ! -s "$rfc_dir/$1" ] && rm -f "$rfc_dir/$1"
                return $NETWORK_ERROR
            fi

            if [ $code -eq $NOT_FOUND ]; then
                echo "$1" >> "$rfc_dir/_404s"
                print_not_found_error "$1"

                [ ! -s "$rfc_dir/$1" ] && rm -f "$rfc_dir/$1"

                return $NOT_FOUND
            fi

        fi

        print_rfc "$@" ;;
    esac

}

__rfc "$@"

