package main

import (
	"bufio"
	"log"
	"math"
	"os"
	"strconv"
	"strings"
)

type packet struct {
	version uint8
	typeID  uint8
	number  uint64
	subs    []packet
}

func main() {
	if err := myMain(); err != nil {
		log.Println(err)
	}
}

func myMain() error {
	in, err := parseInput("input.txt")
	if err != nil {
		return err
	}

	log.Println(solveFirst(in))

	log.Println(solveSecond(in))

	return nil
}

func solveFirst(in []string) int {
	var sum int
	for _, st := range in {
		var p packet
		p.parsePacket(st)
		sum = sumVersion(p)
	}
	return sum
}

func solveSecond(in []string) int {
	var res int
	for _, st := range in {
		var p packet
		p.parsePacket(st)
		res = p.evaluate()
	}
	return res
}

func (p packet) evaluate() int {
	switch p.typeID {
	case 0: // sum
		var sum int
		for _, subs := range p.subs {
			sum += subs.evaluate()
		}
		return sum
	case 1: // product
		var prod = 1
		for _, subs := range p.subs {
			prod *= subs.evaluate()
		}
		return prod
	case 2: // min
		var min int = math.MaxInt
		for _, subs := range p.subs {
			if subs.evaluate() < min {
				min = subs.evaluate()
			}
		}
		return min
	case 3: // max
		var max int
		for _, subs := range p.subs {
			if subs.evaluate() > max {
				max = subs.evaluate()
			}
		}
		return max
	case 4: // literal
		return int(p.number)
	case 5: // greater
		if p.subs[0].evaluate() > p.subs[1].evaluate() {
			return 1
		}
		return 0
	case 6: // less
		if p.subs[0].evaluate() < p.subs[1].evaluate() {
			return 1
		}
		return 0
	case 7: // equal
		if p.subs[0].evaluate() == p.subs[1].evaluate() {
			return 1
		}
		return 0
	default:
		log.Panicf("unknown typeID: %d", p.typeID)
		return 0
	}
}

func sumVersion(p packet) int {
	var sum int
	sum += int(p.version)
	for _, sub := range p.subs {
		sum += sumVersion(sub)
	}

	return sum
}

func (b *packet) parsePacket(in string) string {
	// log.Println("parsePacket:", in)

	temp, err := strconv.ParseUint(in[0:3], 2, 8)
	if err != nil {
		panic(err)
	}
	b.version = uint8(temp)

	temp, err = strconv.ParseUint(in[3:6], 2, 8)
	if err != nil {
		panic(err)
	}
	b.typeID = uint8(temp)

	if b.typeID == 4 {
		b.number, in = parseLiteral(in[6:])
	} else {
		subp := packet{}
		in = subp.parseOperator(in[6:])
		b.subs = subp.subs
	}

	return in
}

func (b *packet) parseOperator(in string) string {
	// log.Println("parseOperator:", in)
	lengthTypeID, err := strconv.ParseUint(in[:1], 2, 8)
	if err != nil {
		panic(err)
	}

	switch lengthTypeID {
	case 0:
		total, err := strconv.ParseUint(in[1:16], 2, 64)
		if err != nil {
			panic(err)
		}
		// log.Println("total length of subpackets:", total)

		in = in[16:]
		inLength := len(in)

		for pivot := 0; pivot < int(total); {
			var p packet
			in = p.parsePacket(in)
			pivot = inLength - len(in)
			b.subs = append(b.subs, p)
		}
	case 1:
		subpackets, err := strconv.ParseInt(in[1:12], 2, 8)
		if err != nil {
			panic(err)
		}
		// log.Println("number of subpackets", subpackets)

		in = in[12:]

		for counter := 0; counter < int(subpackets); counter++ {
			var p packet
			in = p.parsePacket(in)
			b.subs = append(b.subs, p)
		}
	default:
		panic("invalid lengthTypeID")
	}

	return in
}

func parseLiteral(in string) (uint64, string) {
	// log.Println("parseLiteral:", in)
	tmp := strings.Builder{}

	for i := 0; i < len(in); {
		tmp.WriteString(in[i+1 : i+5])
		if in[i] == '0' {
			in = in[i+5:]
			break
		}
		i += 5
	}

	temp, err := strconv.ParseUint(tmp.String(), 2, 64)
	if err != nil {
		panic(err)
	}

	return temp, in
}

func parseInput(fileName string) ([]string, error) {
	ret := []string{}
	fd, err := os.Open(fileName)
	if err != nil {
		return ret, err
	}
	defer fd.Close()

	hexToBin := map[rune]string{
		'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",
	}

	buf := bufio.NewScanner(fd)
	for buf.Scan() {
		tmp := strings.Builder{}
		for _, ch := range buf.Text() {
			tmp.WriteString(hexToBin[ch])
		}

		ret = append(ret, tmp.String())

	}
	return ret, nil
}