#!/usr/bin/env bash

# TickTick enables you to put JSON in bash scripts
# https://github.com/kristopolous/TickTick

ARGV=$@

__tick_error() {
  echo "TICKTICK PARSING ERROR: "$1
}

# Parts of this work are derived from https://github.com/dominictarr/JSON.sh
# See LICENSE for more info. {{{
__tick_json_tokenize() {
  local ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
  local CHAR='[^[:cntrl:]"\\]'
  local STRING="\"$CHAR*($ESCAPE$CHAR*)*\""
  local VARIABLE="\\\$[A-Za-z0-9_]*"
  local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?'
  local KEYWORD='null|false|true'
  local SPACE='[[:space:]]+'
  egrep -ao "$STRING|$VARIABLE|$NUMBER|$KEYWORD|$SPACE|." --color=never | egrep -v "^$SPACE$"  # eat whitespace
}

__tick_json_parse_array() {
  local index=0
  local ary=''

  read -r Token

  case "$Token" in
    ']') ;;
    *)
      while :
      do
        __tick_json_parse_value "$1" "`printf "%012d" $index`"

        (( index++ ))
        ary+="$Value" 

        read -r Token
        case "$Token" in
          ']') break ;;
          ',') ary+=_ ;;
        esac
        read -r Token
      done
      ;;
  esac
}

__tick_json_parse_object() {
  local key
  local obj=''
  read -r Token

  case "$Token" in
    '}') ;;
    *)
      while :
      do
        # The key, it should be valid
        case "$Token" in
          '"'*'"'|\$[A-Za-z0-9_]*) key=$Token ;;
          # If we get here then we aren't on a valid key
          *) 
            __tick_error "Object without a Key"
            break
            ;;
        esac

        # A colon
        read -r Token

        # The value
        read -r Token
        __tick_json_parse_value "$1" "$key"
        obj+="$key:$Value"        

        read -r Token
        case "$Token" in
          '}') break ;;
          ',') obj+=_ ;;
        esac
        read -r Token
      done
    ;;
  esac
}

__tick_json_parse_value() {
  local jpath="${1:+$1_}$2"
  local prej=${jpath//\"/}

  [ "$prej" ] && prej="_$prej"
  [ "$prej" ] && prej=${prej/-/__hyphen__}

  case "$Token" in
    '{') __tick_json_parse_object "$jpath" ;;
    '[') __tick_json_parse_array  "$jpath" ;;

    *) 
      Value=$Token 
      Path="$Prefix$prej"
      Path=${Path/#_/}
      echo __tick_data_${Path// /}=$Value 
      ;;
  esac
}

__tick_json_parse() {
  read -r Token
  __tick_json_parse_value
  read -r Token
}
# }}} End of code from github

# Since the JSON parser is just json parser, and we have a runtime
# and assignments built on to this, along with javascript like referencing
# there is a two-pass system, just because it was easier to code.
#
# This one separates out the valid JSON from the runtime library support
# and little fo' language that this is coded in.
__tick_fun_tokenize_expression() {
  CHAR='[A-Za-z_$\\]'
  FUNCTION="(push|pop|shift|items|delete|length)[[:space:]]*\("
  NUMBER='[0-9]*'
  STRING="$CHAR*($CHAR*)*"
  PAREN="[()]"
  QUOTE="[\"\']"
  SPACE='[[:space:]]+'
  egrep -ao "$FUNCTION|$STRING|$QUOTE|$PAREN|$NUMBER|$SPACE|." --color=never |\
    sed "s/^/S/g;s/$/E/g" # Make sure spaces are respected
}

__tick_fun_parse_expression() {
  while read -r token; do
    token=${token/#S/}
    token=${token/%E/}

    if [ $done ]; then
      suffix+="$token"
    else
      case "$token" in
        #
        # The ( makes sure that you can do key.push = 1, not that you would, but
        # avoiding having reserved words lowers the barrier to entry.  Try doing
        # say function debugger() {} in javascript and then run it in firefox. That's
        # a fun one.
        #
        # So, it's probably better to be as lenient as possible when dealing with
        # syntax like this.
        #
        'push('|'pop('|'shift('|'items('|'delete('|'length(') function=$token ;;
        ')') 
          function=${function/%(/}

          #
          # Since bash only returns integers, we have to have a significant hack in order
          # to return a string and then do something to the object. Basically, everything
          # gets slammed inline.
          #
          # Q: Why don't you just reserve a global and then have the subfunction assign to it?
          #
          # A: Because the assignment has to happen prior to the function running. There's a number
          #    of syntax tricks where you can basically emulate "pointers", but then the coder would
          #    have to know about this "pointer" idea and then deal with their variables a different
          #    way.
          #
          # ---------
          #
          # Q: Why don't you just do stuff in a sub-shell and then make sure you emit things in 
          #    something like a ( ) or a ` ` block?
          #
          # A: Because environments get copied into the subshell and then you'd be modifying the
          #    copy, not the original data.  After the subshell ended, the original environment
          #    would stay, unmodified.
          #
          # ---------
          # 
          # Q: Why don't you use the file system and do some magic with subthreads or something?
          #
          # A: Really? This should have side-effects? In programming there is something called
          #    the principle of least astonishment. In a way, the implementation below somewhat
          #    breaks that principle.  However, using a file system or doing something really 
          #    funky like that, would violate that principle far more.
          #
          # ---------
          #
          # But really, I sincerely hate the current solution. If you have a better idea, please
          # please, open a dialog with me.
          #
          case $function in
            items) echo '${!__tick_data_'"$Prefix"'*}' ;;
            delete) echo 'unset __tick_data_'${Prefix/%_/} ;;
            pop) echo '"$( __tick_runtime_last ${!__tick_data_'"$Prefix"'*} )"; __tick_runtime_pop ${!__tick_data_'"$Prefix"'*}' ;;
            shift) echo '`__tick_runtime_first ${!__tick_data_'"$Prefix"'*}`; __tick_runtime_shift ${!__tick_data_'"$Prefix"'*}' ;;
            length) echo '`__tick_runtime_length ${!__tick_data_'"$Prefix"'*}`' ;;
            *) echo "__tick_runtime_$function \"$arguments\" __tick_data_$Prefix "'${!__tick_data_'"$Prefix"'*}'
          esac
          unset function

          return
          ;;

        [0-9]*) Prefix+=`printf "%012d" $token` ;;
        '['|.) Prefix+=_ ;;
        '"'|"'"|']') ;;
        =) done=1 ;;
        # Only respect a space if its in the args.
        ' ') [ -n "$function" ] && arguments+="$token" ;;
        *) [ -n "$function" ] && arguments+="$token" || Prefix+="$token" ;;
      esac
    fi
  done

  if [ "$suffix" ]; then
    echo "$suffix" | __tick_json_tokenize | __tick_json_parse
  else
    Prefix=${Prefix/-/__hyphen__}
    echo '${__tick_data_'$Prefix'}'
  fi
}

__tick_fun_parse_tickcount_reset() {
  # If the tick count is 1 then the backtick we encountered was a 
  # shell code escape. These ticks need to be preserved for the script.
  if (( ticks == 1 )); then
    code+='`'
  fi

  # This resets the backtick counter so that `some shell code` doesn't
  # trip up the tokenizer
  ticks=0
}

# The purpose of this function is to separate out the Bash code from the
# special "tick tick" code.  We do this by hijacking the IFS and reading
# in a single character at a time
__tick_fun_parse() {
  IFS=

  # code oscillates between being bash or tick tick blocks.
  code=''

  # By using -n, we are given that a newline will be an empty token. We
  # can certainly test for that.
  while read -r -n 1 token; do
    case "$token" in
      '`') 

        # To make sure that we find two sequential backticks, we reset the counter
        # if it's not a backtick.
        if (( ++ticks == 2 )); then

          # Whether we are in the stanza or not, is controlled by a different
          # variable
          if (( tickFlag == 1 )); then
            tickFlag=0
            [ "$code" ] && echo -n "`echo $code | __tick_fun_tokenize_expression | __tick_fun_parse_expression`"
          else
            tickFlag=1
            echo -n "$code"
          fi

          # If we have gotten this deep, then we are toggling between backtick
          # and bash. So se should unset the code.
          unset code
        fi
        ;;

      '') 
        __tick_fun_parse_tickcount_reset

        # this is a newline. If we are in ticktick, then we want to consume
        # them for the parser later on. If we are in bash, then we want to
        # preserve them.  We do this by emitting our buffer and then clearing
        # it
        if (( tickFlag == 0 )); then
          echo "$code"
          unset code
        fi

        ;;

      *) 
        __tick_fun_parse_tickcount_reset
        
        # This is a buffer of the current code, either bash or backtick
        code+="$token"
        ;;
    esac 
  done
}

__tick_fun_tokenize() {
  # This makes sure that when we rerun the code that we are
  # interpreting, we don't try to interpret it again.
  export __tick_var_tokenized=1

  # Using bash's caller function, which is for debugging, we
  # can find out the name of the program that called us. We 
  # then cat the calling program and push it through our parser
  local code=$(cat `caller 1 | cut -d ' ' -f 3` | __tick_fun_parse)

  # Before the execution we search to see if we emitted any parsing errors
  hasError=`echo "$code" | grep "TICKTICK PARSING ERROR" | wc -l`

  if [ $__tick_var_debug ]; then
    printf "%s\n" "$code"
    exit 0
  fi

  # If there are no errors, then we go ahead
  if (( hasError == 0 )); then
    # Take the output and then execute it

    bash -c "$code" -- $ARGV
  else
    echo "Parsing Error Detected, see below"

    # printf observes the new lines
    printf "%s\n" "$code"
    echo "Parsing stopped here."
  fi

  exit
}

## Runtime {
__tick_runtime_length() { echo $#; }
__tick_runtime_first() { echo ${!1}; }
__tick_runtime_last() { eval 'echo $'${!#}; }
__tick_runtime_pop() { eval unset ${!#}; }

__tick_runtime_shift() {
  local left=
  local right=

  for (( i = 1; i <= $# + 1; i++ )) ; do
    if [ "$left" ]; then
      eval "$left=\$$right"
    fi
    left=$right
    right=${!i}
  done
  eval unset $left
}
__tick_runtime_push() {
  local value="${1/\'/\\\'}"
  local base=$2
  local lastarg=${!#}

  let nextval=${lastarg/$base/}+1
  nextval=`printf "%012d" $nextval`

  eval $base$nextval=\'$value\'
}

tickParse() {
  eval `echo "$1" | __tick_json_tokenize | __tick_json_parse | tr '\n' ';'`
}
## } End of Runtime


[ $__tick_var_tokenized ] || __tick_fun_tokenize