    #!/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