#!/bin/bash

## Copyright Ian Jauslin 2015-2026
## 
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
## 
##     http://www.apache.org/licenses/LICENSE-2.0
## 
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.

# directory containing the engine files
enginedir=/home/ian/Programs/BBlog2/src/engines

# version
version=2.3.1

function print_config {
  echo "engine: $engine"
  echo "db: $db"
  echo "conf: $conf"
  echo "aux: $aux"
  echo "format: $format"
  echo "outbib: $outbib"
  echo "order: $order" 
  echo "aux_cmd: $aux_cmd"
  echo "ref_map: $ref_map"
}

# defaults
function set_defaults {
  engine="sqlite"
  db=""
  conf=""
  aux='*.aux'
  format='\bibitem[%citeref%]{%token%}%n%%author% - {\it %title%}, %journal%, %year%.%n%'
  outbib=''
  order="alphabetical"
  aux_cmd='\\citation{'
  ref_map=''
}

# read cli args
function read_cli {

  flag=""
  for arg in "$@"; do
    case "$arg" in
      "-e" )
	flag="engine" ;;
      "-d" )
	flag="db" ;;
      "-c" )
	flag="conf" ;;
      "-a" )
	flag="aux" ;;
      "-f" )
	flag="format" ;;
      "-b" )
	flag="outbib" ;;
      "-o" )
	flag="order" ;;
      "-A" )
	flag="aux_cmd" ;;
      "-F" )
	flag="filter" ;;
      "-r" )
	flag="ref_map" ;;
      # version
      "-v" )
	echo "$version"; exit ;;
      * )
	if [ -n "$flag" ]; then
	  # treat filters differently
	  if [ "$flag" != 'filter' ]; then
	    eval "$flag='$arg'"
	    # flag the variable as set
	    eval "set_$flag=1"
	  else
	    field="${arg%%:*}"
	    value="${arg#*:}"
	    eval "filter_$field='$value'"
	    eval "set_filter_$field=1"
	  fi
	  flag=""
	fi
    esac
  done
}

# read config file
function read_config {
  extra_entries_index=0

  if [ -n "$conf" ]; then
    [ -f "$conf" ] || { echo "error: $conf: no such file" 1>&2 ; exit -1 ; }
    while read -r confline; do
      flag=""
      field="${confline%%:*}"
      value="${confline#*:}"
      # remove spaces
      field=$(echo "$field" | sed -r 's/ *$//')
      value=$(echo "$value" | sed -r 's/^ *//')
      case "$field" in
	"engine" )
	  flag="engine" ;;
	"database" )
	  flag="db" ;;
	"aux" )
	  flag="aux" ;;
	"format" )
	  flag="format" ;;
	"out_file" )
	  flag="outbib" ;;
	"order" )
	  flag="order" ;;
	"aux_cmd" )
	  flag="aux_cmd" ;;
	"ref_map" )
	  flag="ref_map" ;;
	"filter" )
	  # set flag to the specific filter
	  flag="filter_${value%%:*}"
	  # reset value
	  value="${value#*:}" ;;
	"extra" )
	  extra[$extra_entries_index]="$value"
	  extra_entries_index=$((extra_entries_index+1)) ;;
      esac

      if [ -n "$flag" ]; then
	# check it was not set on the command line
	eval "[ -n \"\$set_$flag\" -a \"\$set_$flag\" = 1 ] || $flag='$value'"
      fi
    done <"$conf"

    # sort extra entries
    if [ "$extra_entries_index" -gt 0 -a "$order" = "alphabetical" ]; then
      IFS=$'\n' extra=($(echo "${extra[*]}" | sort -k 1,1 -t :))
    fi
  fi
}

# check the configuration
function check_config {
  # check whether the engine exists
  [ -e "$enginedir/$engine.sh" ] || { echo "error: engine $engine not found in $enginedir" 1>&2; exit -1 ; }

  # check the database and aux file
  for file in "$db" "$aux"; do
    [[ "$file" =~ '*' || -e "$file" ]] || { echo "error: $file: no such file" 1>&2 ; exit -1 ; }
  done

  # check the $order variable
  [ "$order" != "alphabetical" -a "$order" != "appearance" ] && { echo "error: order should be one of 'alphabetical' or 'appearance' (read $order)" 1>&2 ; exit -1 ; }

  # check for ref_map
  [ -n "$ref_map" ] && [ ! -e "$ref_map" ] && { echo "error: $ref_map: no such file" 1>&2 ; exit -1 ; }
}

function replace_format {
  out="$1"

  # replace newlines
  out="${out//\%n\%/\\n}"

  # if the ref of the entry has been mapped, fetch the old citeref
  if [ -n "$ref_map" ]; then
    citeref=$(inverse_map_citeref "$citeref")
  fi

  # first search for conditionals
  # while '|' is still in $out
  while : ; do
    # find next '|'
    tmp=${out#*|}
    # save text before '|'
    pre=${out%%|*}
    # check whether it was found
    [ "$tmp" = "$out" ] && break
    # find next '|'
    command=${tmp%%|*}
    # save text after '|'
    post=${tmp#*|}
    # check whether it was found
    [ "$command" = "$tmp" ] && { echo "error: '|'s do not match in '$1'" 1>&2 ; exit -1 ; }

    # extract field
    field="${command%%\?*}"
    repl="${command##*\?}"

    # check whether field is empty
    val=$(eval "echo \$$field")
    if [ -n "$val" ]; then
      out="$pre$repl$post"
    else
      out="$pre$post"
    fi
  done

  # replace '%'
  # while '%' is still in $out
  while : ; do
    # find next '%'
    tmp=${out#*%}
    # check whether it was found
    [ "$tmp" = "$out" ] && break
    # find next '%'
    command=${tmp%%%*}
    # check whether it was found
    [ "$command" = "$tmp" ] && { echo "error: '%'s do not match in '$1'" 1>&2 ; exit -1 ; }

    # the field entry may be followed by a filter
    field="${command%:*}"
    # filter
    filter="${command##*:}"

    # apply filter
    if [ -n "$filter" ]; then
      # replace '%' inside filter text
      sed_cmd=$(replace_format "$(eval echo \$filter_$filter)")
      replacement=$(eval "echo \$$field" | sed -r "$sed_cmd" )
    else
      replacement=$(eval "echo \$$field")
    fi
  
    # escape '%' in replacement
    replacement="${replacement//\%/::iansays:percent::}"


    out="${out//\%$command\%/$replacement}"
  done

  # finish replacing newlines
  out="${out//\\n/%}"
  out=$(echo "$out" | tr "%" "\n")

  # un-escape '%' in replacement
  out="${out//::iansays:percent::/\%}"

  echo "$out"
}

function escape_string {
  str="$1"
  str="${str//\\/\\\\}"
  str="${str//\`/\\\`}"
  str="${str//\"/\\\"}"
  str="${str//\$/\\\$}"
  echo "$str"
}

# replace a citeref according to a ref_map
function map_citeref {
  # check whether a ref_map was specified
  [ -z "$ref_map" ] && { echo "$1" ; return 0 ; }

  oldref="$1"
  newref=$(echo "$oldref" | sed -r -f "$ref_map")
  echo "$newref"
}

# find the pre-image of a citeref through the ref_map
function inverse_map_citeref {
  # check whether a ref_map was specified
  [ -z "$ref_map" ] && { echo "$1" ; return 0 ; }

  newref="$1"

  foundref=0
  # sift through aux file
  grep -h "$aux_cmd" $aux | while read -r refs; do
    eval "refs=\${refs#$aux_cmd}"
    refs="${ref%\}}"
    # can be a comma separated list
    for ref in $(echo -n "$refs" | tr ',' '\n'); do
      # replace the ref via the ref_map
      possibleref=$(map_citeref "$ref")
      # check whether the ref is the right one
      if [ "$possibleref" = "$newref" ]; then
	echo "$ref"
	foundref=1
	return 1
      fi
    done
  done && echo "$foundref$newref"
}

# format entries
function format_BBlog_entries {
  # keep track of tokens to avoid repetitions
  token_list=""

  has_there_been_a_nonempty_line_so_far=0

  while read -r line; do
    # if line has 0 size, then new entry
    if [ -z "$line" ]; then
      # skip if it is the first line
      if [ $has_there_been_a_nonempty_line_so_far = 1 ]; then
	# check whether an extra entry precedes it
	# alphabetical order
	if [ "$order" = "alphabetical" ]; then
	  old_token="$token"
	  old_citeref="$citeref"
	  while [[ ${#extra} -gt 0 && "${extra[0]%%:*}" < "$prauth$year" ]]; do
	    reftokval="${extra[0]#*:}"
	    citeref="${reftokval%%:*}"
	    tokval="${reftokval#*:}"
	    token="${tokval%%:*}"
	    value="${tokval#*:}"
	    # check whether token is unique
	    token=$(replace_repeated_token "$token" "$token_list")
	    # add token to list
	    token_list="$token_list;$token"

	    replace_format "$value"

	    IFS=$'\n' extra=($(echo "${extra[*]:1}"))
	  done
	  token="$old_token"
	  citeref="$old_citeref"

	  # check whether token is unique
	  token=$(replace_repeated_token "$token" "$token_list")
	  # add token to list
	  token_list="$token_list;$token"

	  replace_format "$format"
	  
	# appearance order
	else
	  foundit=0
	  if [ ${#extra} -gt 0 ]; then
	    for entry in "${extra[@]}"; do
	      ref="${entry#*:}"
	      ref="${ref%%:*}"
	      if [ "$ref" = "$citeref" ]; then
		old_token="$token"
		old_citeref="$citeref"

		citeref="$ref"
		token="${entry#*:*:}"
		value="${token#*:}"
		token="${token%%:*}"
		# check whether token is unique
		token=$(replace_repeated_token "$token" "$token_list")
		# add token to list
		token_list="$token_list;$token"

		replace_format "$value"

		token="$old_token"
		citeref="$old_citeref"
		foundit=1
		break
	      fi
	    done
	  fi

	  if [ $foundit = 0 ]; then
	    # check whether token is unique
	    token=$(replace_repeated_token "$token" "$token_list")
	    # add token to list
	    token_list="$token_list;$token"

	    replace_format "$format"
	  fi
	fi
      fi
      has_there_been_a_nonempty_line_so_far=0
    else
      eval "$line"
      has_there_been_a_nonempty_line_so_far=1
    fi
  done

  # add remaining extra entries (only for alphabetical order)
  if [ "$order" = "alphabetical" ]; then
    while [ ${#extra} -gt 0 ]; do
      reftokval="${extra[0]#*:}"
      citeref="${reftokval%%:*}"
      tokval="${reftokval#*:}"
      token="${tokval%%:*}"
      value="${tokval#*:}"
      # check whether token is unique
      token=$(replace_repeated_token "$token" "$token_list")
      # add token to list
      token_list="$token_list;$token"

      replace_format "$value"
      IFS=$'\n' extra=($(echo "${extra[*]:1}"))
    done
  fi
}

# replace a token that already exists
function replace_repeated_token {
  token="$1"
  list="$2"

  out="$token"
  i=0
  while $(echo "$token_list;" | grep -q ";$out;"); do
    i=$((i+1))
    out="$token"$(alpha_number $i)
  done

  echo "$out"
}

# convert an integer to base 26
function alpha_number {
  num="$1"
  rem="$num"
  out=""
  letters=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
  while [ "$rem" -gt 0 ]; do
    digit=$((rem-26*(rem/26)))
    out="${letters[$digit]}$out"
    rem=$(((rem-digit)/26))
  done

  echo "$out"
}


set_defaults
read_cli "$@"
read_config
check_config
source "$enginedir/$engine.sh"

# check whether to order by alphabetical order or appearance
[ "$order" = "alphabetical" ] && fetch_cmd=fetch_BBlog_entries_alpha || fetch_cmd=fetch_BBlog_entries_appearance

# fetch entries
if [ -n "$outbib" ]; then
  "$fetch_cmd" | format_BBlog_entries > "$outbib"
else
  "$fetch_cmd" | format_BBlog_entries
fi

