#!/bin/bash

declare -a data

get_value() {
    local -n da=$1
    local index=$2

    if [ $index -lt 0 ]; then
        echo "Index negative, give up!"
        exit 2
    fi

    if ! [ ${da[$index]+a} ]; then
        da[$index]=0
    fi

    declare -g __comp_value="${da[$index]}"
}

get_param() {
    local -n dap=$1
    local pos=$2
    local mode=$3
    local relative_base=$4

    get_param_loc dap $pos $mode $relative_base
    loc=$__comp_param_loc

    get_value dap $loc
}

get_param_loc() {
    local -n dapl=$1
    local pos=$2
    local mode=$3
    local relative_base=$4

    case $mode in
        0)
            get_value dapl $pos
            declare -g __comp_param_loc=$__comp_value
            ;;
        1)
            declare -g __comp_param_loc=$pos
            ;;
        2)
            get_value dapl $pos
            move_by=$__comp_value
            declare -g __comp_param_loc=$((relative_base+$move_by))
            ;;
        *)
            echo "Unknown parameter mode: $mode"
            exit 2
            ;;
    esac
}

run_program() {
    local -n od=$1
    local relative_base=0
    data=("${od[@]}")
    pos=0
    declare -a immediate
    while [ $pos -le ${#data[@]} ]; do
        # first, printf the value to 5 digits
        instruction=$(printf "%05d" ${data[$pos]})
        for (( a=0; a<3; a++ )); do
            immediate[$((3-a))]=${instruction:$a:1}
        done
        instruction=${instruction:3}
        param_count=3
        echo -n $'\r'"    "$'\r'$instruction >&2
        case $instruction in
            01|02)
                # 01 - add, 02 multiply
                symbol="+"
                if [ $instruction == "02" ]; then
                    symbol="*"
                fi
                get_param data $((pos+1)) ${immediate[1]} $relative_base
                val1=$__comp_value
                get_param data $((pos+2)) ${immediate[2]} $relative_base
                val2=$__comp_value
                get_param_loc data $((pos+3)) ${immediate[3]} $relative_base
                res_loc=$__comp_param_loc
                data[$res_loc]=$(($val1 $symbol $val2))
                ;;
            03)
                # 03 - read input
                echo -n $'\r'"    "$'\r' >&2
                echo "input: "
                read input
                input=${input//\n/}
                input=${input//\r/}
                echo >&2
                echo "$instruction: '$input'" >&2
                echo >&2
                get_param_loc data $((pos+1)) ${immediate[1]} $relative_base
                res_loc=$__comp_param_loc
                data[$res_loc]=$input
                param_count=1
                ;;
            04)
                # 04 - output
                get_param data $((pos+1)) ${immediate[1]} $relative_base
                val=$__comp_value
                echo -n $'\r'"    "$'\r' >&2
                echo $val
                param_count=1
                ;;
            05|06)
                # 05 - jump-if-true, 06 - jump-if-false
                get_param data $((pos+1)) ${immediate[1]} $relative_base
                val=$__comp_value
                get_param data $((pos+2)) ${immediate[2]} $relative_base
                jumpto=$__comp_value
                param_count=2
                if ( [ $val -ne 0 ] && [ "$instruction" == "05" ] ) ||
                    ( [ $val -eq 0 ] && [ "$instruction" == "06" ] ); then
                    pos=$jumpto
                    continue
                fi
                ;;
            07|08)
                # 07 - is less than, 08 - is equal
                get_param data $((pos+1)) ${immediate[1]} $relative_base
                val1=$__comp_value
                get_param data $((pos+2)) ${immediate[2]} $relative_base
                val2=$__comp_value >&90
                echo >&2
                echo "$instruction: '$val1' '$val2'" >&2
                echo >&2
                get_param_loc data $((pos+3)) ${immediate[3]} $relative_base
                res_pos=$__comp_param_loc
                data[$res_pos]=0
                if [ "$instruction" == "07" ]; then
                    if [ $val1 -lt $val2 ]; then
                        data[$res_pos]=1
                    fi
                else
                    if [ $val1 -eq $val2 ]; then
                        data[$res_pos]=1
                    fi
                fi
                ;;
            09)
                # adjust relative base
                param_count=1
                get_param data $((pos+1)) ${immediate[1]} $relative_base
                val=$__comp_value
                relative_base=$((relative_base+$val))
                ;;
            99)
                break
                ;;
            *)
                echo "Invalid opcode: $instruction at position $pos"
                exit 1
                ;;
        esac
        pos=$(($pos+$param_count+1))
    done
    echo -n $'\r'"    "$'\r' >&2
}

