#!/bin/bash

# GLOBAL VARS

debug=1
testing=0

action='DUMP'
mode='RUNCMD'

retro_running=0
title_found=0
game_ready=0
game_listed=0
identified=0

credits=0
last_credits=0
coins_count=0
start_count=0
scanning=0
scan_level=1

coin_key="KEY_C"
start_key="KEY_S"

system='mame-libretro'
game=''
game_title=''
coin_addr=''
credits_str=''
title_str='GAMEINFO:'
loaded_arr=('SET_GEOMETRY' 'hiscore memory file')

declare -A data=()
declare -a regions=()

#___________________________________________________
#
# MAIN

Main() {
  debug "Main():\n"
  debug "MODE:\t$mode\n\tACTION:\t$action\n\n"

  if [[ "$action" == DUMP ]]; then
    if [[ "$mode" == RUNCMD ]]; then
      cafca_dump
    elif [[ "$mode" == LAUNCH ]]; then
      # launch retroarch
      sleep 1
    fi

  elif [[ "$action" == SCAN ]]; then
    if [[ "$mode" == RUNCMD ]]; then
      cafca_scan
    elif [[ "$mode" == LAUNCH ]]; then
      if [ "${#args[@]}" -gt 2 ]; then
        game=$(echo "$4" | tr '[:upper:]' '[:lower:]')
      elif [ "${#args[@]}" -gt 3 ]; then
        system=$(echo "$3" | tr '[:upper:]' '[:lower:]')
        game=$(echo "$4" | tr '[:upper:]' '[:lower:]')
      fi
      #cafca_scan
    fi
  fi
}

#___________________________________________________
#
# FUNCTIONS


cafca_scan() {
  retro_running=1
  loaded_str="${loaded_arr[1]}"

  serialSend "GAME_LOADED 1"
  sleep 1

  while (( ! $game_ready )); do
    game_ready=$(grep -Ec -m1 "$loaded_str" $ra_log)
  done

  serialSend "GAME_READY 1"

  watch_credits


  # EXPECT:
  /usr/bin/expect -c '
  	log_user 1
    exp_internal 0

    set timeout -1
    set log_path "/home/pi/CAFCA/log/expect.log"
    set busy_flag "/home/pi/CAFCA/data/scan_flag"
    set coin_file "/home/pi/CAFCA/data/tmp/cafca_coins_cnt"
    set pid [exec pidof retroarch]
    set ready 0
    set scanned 0
    set matched 0
    set matches 999
    set match_limit 5
    set credits 0
    set last_credits 0

    exec echo -e "\n" > "$log_path"
    log_file "$log_path"
    spawn scanmem -p $pid
    proc startup {} {
      exec echo 0 > "$::busy_flag"
      expect {
        "Please" {
          exec sleep 1
          send "option region_scan_level 3\r"
          exec sleep 1
          send "reset\r"
          exec sleep 1
          exp_continue
        }
        "*suitable*" {
          set ::ready 1
          exec sleep 1
        }
      }
      while {$::credits == $::last_credits} {
        set ::credits [exec cat "$::coin_file"]
      }
    }
    proc put_coin {} {
      set ::credits [exec cat "$::coin_file"]
      if {$::credits != $::last_credits} {
        set ::last_credits $::credits
        exec echo 1 > "$::busy_flag"
        exec sleep 1
        send "$::credits\r"
        exec sleep 1
        expect {
          "*currently*" {
            exec echo 0 > "$::busy_flag"
            #set ::matches [lindex [split [lindex [split $expect_out(0,string) "\n"] end ] ">" ] 0]
            set ::matches [lindex [split [lindex [split $expect_out(buffer) "\n"] end-1 ] " " ] end-1 ]
            send_user "\n\nmatches: $::matches\n"
            if {$::matches == "other"} {
              send_user "MATCH IS 1!\n"
              set ::matches 1
            }
            #if {$::matches <= $::match_limit} {
            # send "list\r"
            #}
            #exp_continue
          }
          #"*identified*" {
          #  exec sleep 1
	  #  send "list\r"
          #  #    exp_continue
          #}

          #"*matches*" {
          #  exec echo 0 > "$::busy_flag"
          #  exec sleep 1
            #send "\r"
            #exec sleep 1
            #set ::matches [lindex [split [lindex [split $expect_out(0,string) "\n"] end ] ">" ] 0]
            #set ::matches [lindex [split $expect_out(0,string) "\n"] end ]
            #send_user "\n\nmatches: $::matches\n\n"
            #if {$::matches < $::match_limit} {
            #  extract_match
            #}
          #}
        }
      }
    }
    proc extract_match {} {
      send_user "\n\nMATCH LIMIT REACHED\n\n"
      send "list\r"
    }
    startup
    while {$matches > $match_limit} {
      put_coin
    }
    exec sleep 1
    send_user "\nBREAK!\n"
    send "list\r"
    expect {
      "*0x*" {
        send_user "LISTING!\n"
        exp_continue
      }
    }
    #send "exit\r"
    #close
    #expect eof
  '
  #___________________________________________________

  while (( $retro_running )); do
    if [[ $credits != $last_credits ]]; then
      calc_credits
      last_credits=$credits
      debug "credits: $credits"
      #serialSend "CREDITS $credits"
    fi
  done

}

cafca_dump() {
  retro_running=1

  serialSend "GAME_LOADED 1"

  if [[ "$mode" == RUNCMD ]]; then
    system=$(awk 'NR==1' $rc_info); debug "SYSTEM: $system"
    game=$(awk -F'/' 'NR==3 {print $NF}' $rc_info | cut -d. -f1); debug "GAME: $game"
  fi

  game_name=$(echo "$game" | tr '[:lower:]' '[:upper:]')
  serialSend "GAME_NAME $game_name"

  #---------------------------------------------------

  game_table="$data_dir/$system/game_table"
  game_listed=$(cat "$game_table" | grep -wc "$game")

  [[ $game_listed -gt 0 ]] && get_gamedata

  #------------------------------------------------

  limit=20
  repeats=0
  last_len=0

  while (( ! $game_ready )); do
    log_len=$(wc -l $ra_log | cut -d' ' -f1)

    if [[ $repeats -lt $limit ]]; then
      [[ $log_len == $last_len ]] && repeats=$(( repeats + 1 ))
      last_len=$log_len
    else
      game_ready=1
      break
    fi
    (( ! $title_found )) && check_title
    sleep 0.5
  done

  serialSend "GAME_READY 1"

  debug "\n\nGame Ready!"
  debug "\n\nlast log: $(awk 'END{print}' $ra_log)"

  #------------------------------------------------

  watch_credits

  while (( ! $credits )); do calc_credits; done

  debug "\n\ncoin inserted!\n"
  serialSend "CREDITS $credits"
  serialSend "VFD 6 COIN INSERTED!"

  [[ $game_listed -gt 0 ]] && getAddr

  #------------------------------------------------

  if [[ $identified -gt 0 ]]; then
    serialSend "LCD 0 GAME DATA FOUND:"
    serialSend "LCD 1 $coin_addr: $credits_str"
  fi

  while (( $retro_running )); do
    retro_running=$(ps -ef | grep -v grep | grep -c -m1 retroarch)

    if [[ $identified -gt 0 ]]; then
      credits=$(sudo scanmem -p `pidof retroarch` -c"dump $coin_addr 1;exit" 2>&1 | awk 'NR==15' | awk -F' ' '{print $2}')
    else
      calc_credits 1
    fi

    if [[ $credits != $last_credits ]]; then
      last_credits=$credits
      debug "credits: $credits"
      serialSend "CREDITS $credits"
    fi

    sleep 1
  done

}

getAddr() {
  local scan_args='option region_scan_level 3;reset;lregions;exit'

  sudo scanmem -p `pidof retroarch` -c"$scan_args" 2>&1 |& grep --line-buffered -P -A1000 '\[ 0\]' |& sed s'/[][,]//g' | sudo tee "$file_regionlist" &>/dev/null && sudo sed -i '/> exit/d' "$file_regionlist"

  if [[ $scan_level > 1 ]]; then
    for i in {1..3}; do
      get_regions "${data[NAME]}" $i
      [[ $identified -gt 0 ]] && break
    done
  elif [[ $scan_level == 1 ]]; then
    get_regions "${data[NAME]}" $scan_level
  fi

  if [[ $identified -gt 0 ]]; then
    last_credits="$credits_str"
    debug "IDENTIFIED! $coin_addr: $credits_str"
  fi

}

get_regions() {
  local region_list=$(cat "$file_regionlist")
  local region_count=$(cat "$file_regionlist" | wc -l)
  local reg_num=${data[NUM]}
  local range=5

  [[ ${#} -lt 2 ]] && iter=1 || iter=${2}
  [[ ${1} == code ]] && iter=$(( iter - 1 ))

  debug "get_regions()\n\targs (${#}): ${@}\n"
  debug "regions: $region_count\n"
  debug "iter: $iter\n\n"

  if [[ $iter == 0 ]]; then
    reg_start=$reg_num
    reg_end=$(( reg_start + 1 ))

  elif [[ $iter == 1 ]]; then
    reg_range=$(calc_range $reg_num $range)
    reg_start=$(echo "$reg_range" | cut -d' ' -f1)
    reg_end=$(echo "$reg_range" | cut -d' ' -f2); reg_end=$(( reg_end+1 ))

  elif [[ $iter -gt 1 ]]; then
    reg_start=1
    reg_end=$region_count
  fi

  for (( i=$reg_start; i<$reg_end; i++)); do
    line=$(awk -v ln="$(( i + 1 ))" 'NR==ln' "$file_regionlist" | awk '{$1=$1; print}')
    name=$(echo "$line" | awk '{print $5}')
    [[ $name == ${data[NAME]} ]] && regions+=("0x$(echo $line | awk '{print $2}')")
  done

  (( ! $testing )) && dump_addr
}


dump_addr() {
  for (( i=0; i<${#regions[@]}; i++)); do
    BASE="${regions[$i]}"
    OFFS="${data[OFFS]}"
    ADDR="";printf -v ADDR "0x%X\n" $(( BASE + OFFS)) &>/dev/null; ADDR=$(echo "$ADDR" | tr '[A-Z]' '[a-z]')
    VAL=$(sudo scanmem -p `pidof retroarch` -c"dump $ADDR 1;exit" 2>&1 | awk 'NR==15' | awk -F' ' '{print $2}')

    calc_credits 1

    if [[ "$VAL" == "$credits_str" ]]; then
      LASTVAL="$credits_str"
      printf -v credits_str '%02d' "$(( credits+1 ))" &>/dev/null
      sudo scanmem -p `pidof retroarch` -c"write i16 $ADDR $credits_str;exit" &>/dev/null
      sleep 0.5

      VAL=$(sudo scanmem -p `pidof retroarch` -c"dump $ADDR 1;exit" 2>&1 | awk 'NR==15' | awk -F' ' '{print $2}')

      calc_credits 1
      sleep 0.5
      sudo scanmem -p `pidof retroarch` -c"write i16 $ADDR $credits_str;exit" &>/dev/null

      if [[ ! $VAL == $LASTVAL ]]; then
        debug "NEW WAL: $VAL"
        coin_addr="$ADDR"
        identified=1 && break
      fi
    fi
  done
}

get_gamedata() {
  local table_data=$(cat "$game_table" | grep -w "$game" | awk '{$1=$1;print}')

  data[GAME]=$(echo "$table_data" | awk '{print $1}')
  data[OFFS]=$(echo "$table_data" | awk '{print $2}')
  data[NUM]=$(echo "$table_data" | awk '{print $3}')
  data[NAME]=$(echo "$table_data" | awk '{print $4}')
  data[TYPE]=$(echo "$table_data" | awk -F'\[' '{print substr($2,0,length($2))}')

  debug "\n\nget_gamedata():\n"
  debug "DATA:\n"

  for i in "${!data[@]}"; do
    debug "$i:\t\t${data[$i]}"
  done
}

calc_range() {
  base=$1
  int=$2
  sum=$(( base - int ))

  if [[ $sum -le 0 ]]; then
    start=1; end=$(( int+int ))
  else
    start=$sum; end=$(( base+int ))
  fi

  echo "$start $end"

}

calc_credits() {
  coins_count=$(cat "$file_coins_count")
  start_count=$(cat "$file_start_count")
  credits=$(( coins_count - start_count ))
  [[ $# > 0 ]] && printf -v credits_str '%02d' "$credits" &>/dev/null
}


watch_credits() {
  read_inputs &
}

read_inputs() {
  local coins_cnt=0
  local start_cnt=0

  local limit_start=20
  local t_start=$limit_start
  local last_start=$(date +%s)

  while read -r line; do
    pressed=$(echo "$line" | grep -E "$coin_key|$start_key" | \
      awk -v C="$coin_key" -v S="$start_key" '{
        if ($2 == C) print "COIN"
        if ($2 == S) print "START"
      }')

    coins_cnt=$(cat "$file_coins_count")
    start_cnt=$(cat "$file_start_count")

    if [[ $pressed == COIN ]]; then
      echo "$(( coins_cnt + 1 ))" | sudo tee $file_coins_count &>/dev/null
    elif [[ $pressed == START ]]; then
      if [[ $coins_cnt -gt 0 ]]; then
        if [[ $start_cnt -gt 0 ]]; then
          t_start=$(( `date +%s` - $last_start ))
          if [[ $t_start -ge $limit_start ]]; then
            echo "$(( start_cnt + 1 ))" | sudo tee $file_start_count &>/dev/null
            t_start=0
            last_start=$(date +%s)
          fi
        else
          echo "$(( start_cnt + 1 ))" | sudo tee $file_start_count &>/dev/null
          t_start=0
          last_start=$(date +%s)
        fi
      fi
    fi

    if [[ "$action" == SCAN ]]; then
      scanning=$(cat "$data_dir/scan_flag")

      while (( $scanning )); do
        scanning=$(cat "$data_dir/scan_flag")
      done
    fi
  done< <(thd --dump "$teensy_kbd" 2>&1 |& grep --line-buffered -E "$coin_key|$start_key" | awk -W interactive '{$1=$1;print}' | grep --line-buffered -Ew "EV_KEY $coin_key 1|EV_KEY $start_key 1" 2>&1)
}


check_title() {
  game_title=$(grep -wc "$title_str" $ra_log)
  if [[ $game_title -gt 0 ]]; then
    #game_title=$(grep -w "$title_str" $ra_log | cut -d' ' -f2-)
    #game_title=$(grep -w -A1 "$title_str" $ra_log | awk -F':' '{printf "%s ", $0} END {print ""}')
    game_title=$(grep -w -A1 "$title_str" $ra_log | awk -F':' '{if(NR==1){str=$2}else{str="("$0")"}; printf "%s ", str} END {print ""}')
    debug "GAME TITLE: $game_title"
    serialSend "GAME_TITLE $game_title"
    title_found=1
  fi
}

wipe() {
  file="$@"
  [[ ! -f "$file" ]] && sudo touch "$file" &>/dev/null \
  || echo -e "\n" | sudo tee "$file" &>/dev/null
  sudo chmod 775 "$file" &>/dev/null
}

serialSend() {
 [ -c /dev/ttyACM0 ] && printf "${@}" > /tmp/pyserial.fifo
 sleep 0.5
}

press_key() {
  printf $@ > /tmp/vkbdd.fifo
}

debug() {
  (( $debug )) && echo -e "[DEBUG] $@"
}

cleanup() {
  debug "Cleaning up..."
  serialSend "GAME_STOPPED 1"
  (( $retro_running )) && printf '\033' >/tmp/vkbdd.fifo #sudo pkill retroarch
}

#_____________________________________________________
#
# SETUP
#
#-----------------------------------------------------

#CONSTS:

SCRIPT=$(readlink -f $0)
HOMEDIR=$(getent passwd pi | cut -d: -f6)
DIR=$(cd $(dirname $SCRIPT) && pwd)

args=("$@")
root_user=

log_dir="$DIR/log"
log_file="$log_dir/CAFCA.log"
rc_log='/dev/shm/runcommand.log'
rc_info='/dev/shm/runcommand.info'
ra_log="$log_dir/retroarch/retroarch.log"

teensy_kbd='/dev/input/by-id/usb-Teensyduino_Serial_Keyboard_Mouse_Joystick_1008140-if02-event-kbd'

data_dir="$DIR/data"
game_table="$data_dir/$system/game_table"

file_regionlist="$data_dir/tmp/regionlist"
file_coins_count="$data_dir/tmp/cafca_coins_cnt"
file_start_count="$data_dir/tmp/cafca_start_cnt"

bypass=$(grep bypass "$data_dir/.settings" | cut -d= -f2)

#---------------------------------------------------

(( ! $(id -u) )) && root_user=1 || root_user=0

trap cleanup EXIT

wipe "$log_file"
wipe "$ra_log"

ra_log_owner=$(ls -l "$log_dir/retroarch" | grep "$(echo $ra_log | cut -d/ -f7)" | awk '{print $3" "$4}')

[[ $ra_log_owner != "pi pi" ]] && sudo chown pi:pi "$ra_log"

exec > >(sudo tee $log_file 2>&1)

debug "\n\nC A F C A\n\n`date '+%d/%m-%Y %T'`\n\nHOMEDIR:\t$HOMEDIR\nDIR:\t\t$DIR\nSCRIPT:\t\t$SCRIPT\nROOT USER:\t$root_user\n"

if [ "${#args[@]}" -gt 0 ]; then
  testing=0
  action=$(echo "$1" | tr '[:lower:]' '[:upper:]')
  [ "${#args[@]}" -gt 1 ] && mode=$(echo "$2" | tr '[:lower:]' '[:upper:]')
  debug "ARGS: \x27${args[@]}\x27 (${#args[@]})\n\n"
else
  debug "NO ARGS..\n"
fi

#debug "_____________________________________________\n"

wipe "$file_regionlist"
wipe "$file_coins_count" && echo 0 | sudo tee "$file_coins_count" &>/dev/null
wipe "$file_start_count" && echo 0 | sudo tee "$file_start_count" &>/dev/null

[[ $bypass -ne 1 ]] && Main

exit 0