#!/bin/bash

set -u

filename=${1:-p1_16.txt}
debug=${2:-0}

exec 3<"${filename}"

declare -A hex_to_binary=( [0]="0000" [1]="0001" [2]="0010" [3]="0011" [4]="0100" [5]="0101" [6]="0110" [7]="0111" [8]="1000" [9]="1001" [A]="1010" [B]="1011" [C]="1100" [D]="1101" [E]="1110" [F]="1111" )

read -u 3 hex_string

declare -g __packets=""
for (( a=0; a<${#hex_string}; a++ )); do
    __packets+=${hex_to_binary[${hex_string:$a:1}]}
done

declare -g __versions_total=0

declare __packet_offset=0
declare -g __cur_level=0

if [ $debug == "0"  ]; then
    echo_verbose() { true; }
else
    echo_verbose() { echo "$@" >&2; }
fi

parse_version() {
    local level="${1:- 1}"
    declare -g __packet_version=$((2#${__packets:$__packet_offset:3}))
    ((__packet_offset+=3))
    echo_verbose "Packet version: $__packet_version"
}

parse_type() {
    local level="${1:- 1}"
    declare -g __packet_type=$((2#${__packets:$__packet_offset:3}))
    ((__packet_offset+=3))
    echo_verbose "Packet type: $__packet_type"
}

parse_literal() {
    local level="${1:- 1}"
    local keep_going=${__packets:$__packet_offset:1}
    local binary_value=""
    ((__packet_offset+=1))
    while [ $keep_going -eq 1 ]; do
        binary_value+=${__packets:$__packet_offset:4}
        ((__packet_offset+=4))
        keep_going=${__packets:$__packet_offset:1}
        ((__packet_offset+=1))
    done
    # if we're here, we're on to the last group
    binary_value+=${__packets:$__packet_offset:4}
    ((__packet_offset+=4))
    declare -g __packet_value=$((2#$binary_value))
    echo_verbose "Packet literal: $__packet_value"
}

parse_operator() {
    local level="${1:- 1}"
    local ptype="${2:-}"
    local len_type=$((2#${__packets:$__packet_offset:1}))
    local number=0
    ((__packet_offset+=1))

    case $len_type in
        0)
            number=$((2#${__packets:$__packet_offset:15}))
            ((__packet_offset+=15))
            ;;
        1)
            number=$((2#${__packets:$__packet_offset:11}))
            ((__packet_offset+=11))
            ;;
    esac

    case $ptype in
        0)
            parse_sum $len_type $number
            ;;
        1)
            parse_product $len_type $number
            ;;
        2)
            parse_minimum $len_type $number
            ;;
        3)
            parse_maximum $len_type $number
            ;;
        5)
            parse_greaterthan $len_type $number
            ;;
        6)
            parse_lessthan $len_type $number
            ;;
        7)
            parse_equal $len_type $number
            ;;
    esac
}

parse_sum() {
    local len_type=$1
    local count=$2
    local total=0
    local id=$__packet_offset
    local a=0

    echo_verbose "[$id]: Doing a sum ($len_type - $count)"

    case $len_type in
        0)
            local end_offset=$((__packet_offset+$count))
            while [ $__packet_offset -lt $end_offset ]; do
                echo_verbose "[$id]: Getting sum sub packet"
                parse_packet
                ((total+=$__packet_value))
            done
            ;;
        1)
            for (( a=0; a<$count; a++ )); do
                echo_verbose "[$id]: Getting sum sub packet"
                parse_packet
                ((total+=$__packet_value))
            done
            ;;
    esac

    echo_verbose "[$id]: Sum total: $total (offset: $__packet_offset)"

    declare -g __packet_value=$total
}

parse_product() {
    local len_type=$1
    local count=$2
    local total=1
    local id=$__packet_offset
    local a=0

    echo_verbose "[$id]: Doing a product ($len_type - $count)"

    case $len_type in
        0)
            local end_offset=$((__packet_offset+$count))
            while [ $__packet_offset -lt $end_offset ]; do
                echo_verbose "[$id]: Getting product sub packet"
                parse_packet
                ((total*=$__packet_value))
            done
            ;;
        1)
            for (( a=0; a<$count; a++ )); do
                echo_verbose "[$id]: Getting product sub packet"
                parse_packet
                ((total*=$__packet_value))
            done
            ;;
    esac

    echo_verbose "[$id]: Product total: $total (offset: $__packet_offset)"

    declare -g __packet_value=$total
}

parse_minimum() {
    local len_type=$1
    local count=$2
    local minimum=
    local id=$__packet_offset
    local a=0

    echo_verbose "[$id]: Doing mimimum"

    case $len_type in
        0)
            local end_offset=$((__packet_offset+$count))
            while [ $__packet_offset -lt $end_offset ]; do
                echo_verbose "[$id]: Getting minimum sub packet"
                parse_packet
                if [ -z $minimum ] || [ $__packet_value -lt $minimum ]; then
                    minimum=$__packet_value
                fi
            done
            ;;
        1)
            for (( a=0; a<$count; a++ )); do
                echo_verbose "[$id]: Getting minimum sub packet"
                parse_packet
                if [ -z $minimum ] || [ $__packet_value -lt $minimum ]; then
                    minimum=$__packet_value
                fi
            done
            ;;
    esac

    echo_verbose "[$id]: Minimum value: $minimum (offset: $__packet_offset)"

    declare -g __packet_value=$minimum
}

parse_maximum() {
    local len_type=$1
    local count=$2
    local maximum=
    local id=$__packet_offset
    local a

    echo_verbose "[$id]: Doing maximum ($len_type - $count)"

    case $len_type in
        0)
            local end_offset=$((__packet_offset+$count))
            while [ $__packet_offset -lt $end_offset ]; do
                echo_verbose "[$id]: Getting maximum sub packet"
                parse_packet
                if [ -z $maximum ] || [ $__packet_value -gt $maximum ]; then
                    maximum=$__packet_value
                fi
            done
            ;;
        1)
            for (( a=0; a<$count; a++ )); do
                echo_verbose "[$id]: Getting maximum sub packet"
                parse_packet
                if [ -z $maximum ] || [ $__packet_value -gt $maximum ]; then
                    maximum=$__packet_value
                fi
            done
            ;;
    esac

    echo_verbose "[$id]: Maximum value: $maximum (offset: $__packet_offset)"

    declare -g __packet_value=$maximum
}

parse_greaterthan() {
    local len_type=$1
    local count=$2
    local id=$__packet_offset

    echo_verbose "[$id]: Doing >"

    # we don't actually care, we know that it's exactly 2 sub packets that we want... so
    parse_packet
    local val1=$__packet_value
    parse_packet
    local val2=$__packet_value

    if [ $val1 -gt $val2 ]; then
        declare -g __packet_value=1
    else
        declare -g __packet_value=0
    fi

    echo_verbose "[$id]: Got $__packet_value (offset: $__packet_offset)"
}

parse_lessthan() {
    local len_type=$1
    local count=$2
    local id=$__packet_offset

    echo_verbose "[$id]: Doing <"

    # we don't actually care, we know that it's exactly 2 sub packets that we want... so
    parse_packet
    local val1=$__packet_value
    parse_packet
    local val2=$__packet_value

    if [ $val1 -lt $val2 ]; then
        declare -g __packet_value=1
    else
        declare -g __packet_value=0
    fi

    echo_verbose "[$id]: Got $__packet_valuei (offset: $__packet_offset)"
}

parse_equal() {
    local len_type=$1
    local count=$2
    local id=$__packet_offset

    echo_verbose "[$id]: Doing ="

    # we don't actually care, we know that it's exactly 2 sub packets that we want... so
    parse_packet
    local val1=$__packet_value
    parse_packet
    local val2=$__packet_value

    if [ $val1 -eq $val2 ]; then
        declare -g __packet_value=1
    else
        declare -g __packet_value=0
    fi

    echo_verbose "[$id]: Got $__packet_value (offset: $__packet_offset)"
}

parse_packet() {
    parse_version && local pversion=$__packet_version
    parse_type && local ptype=$__packet_type

    ((__versions_total+=$pversion))

    case $ptype in
        4)
            parse_literal
            ;;
        *)
            parse_operator $__cur_level $ptype
            ;;
    esac
}

parse_packets() {
    local level="${1:- 1}"
    # we'll first need the version and the type, so grab those

    while [ $__packet_offset -lt ${#__packets} ]; do

        parse_packet

        if [ $(($__packet_offset+7)) -ge ${#__packets} ]; then
            break
        fi
    done
}

parse_packets

echo "Total: $__versions_total"
echo "End value: $__packet_value"
