package main

import (
	"fmt"
	"os"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

func main() {
	inp := h.StdinToString()
	var arg string
	if len(os.Args) > 1 {
		arg = os.Args[1]
	}

	p := ParsePacket(inp)
	if arg == "--json" {
		fmt.Println(p.Json())
	} else {
		fmt.Println("# Part 1")
		fmt.Println("Version Sum:", CalculateVersionSum(p))
		fmt.Println()
		fmt.Println("# Part 2")
		fmt.Println("Value:", p.value)
	}
}

func CalculateVersionSum(p *Packet) int {
	sum := p.version
	for s := range p.packets {
		sum += CalculateVersionSum(p.packets[s])
	}
	return sum
}

const (
	PacketTypeSum = iota
	PacketTypeProduct
	PacketTypeMin
	PacketTypeMax
	PacketTypeLiteral
	PacketTypeGreater
	PacketTypeLess
	PacketTypeEqual

	LengthTypeBits       = 0
	LengthTypeSubPackets = 1
)

type Packet struct {
	version    int
	packetType int

	value int

	// Operator
	lengthTypeId int
	subLength    int

	// Sub-packets
	packets []*Packet

	binaryString string
}

func ParsePacket(inp string) *Packet {
	var bin string
	for i := 0; i < len(inp); i += 2 {
		if len(inp) >= i+2 {
			bin += fmt.Sprintf("%08b", HexToByte(inp[i:i+2]))
		}
	}
	return ParseBinaryPacket(bin)
}

func ParseBinaryPacket(inp string) *Packet {
	p := Packet{
		binaryString: inp,
	}
	// Parse the version
	for i := 0; i < 3; i++ {
		p.version = p.version << 1
		if p.binaryString[i] == '1' {
			p.version |= 1
		}
	}
	for i := 3; i < 6; i++ {
		p.packetType = p.packetType << 1
		if p.binaryString[i] == '1' {
			p.packetType |= 1
		}
	}
	if p.packetType == PacketTypeLiteral {
		p.parseLiteral()
	} else {
		p.parseOperator()
	}
	return &p
}

func (p *Packet) parseLiteral() {
	// The bits for the literal start at bit 7
	// Every 5 bits is a chunk of the literal
	ptr := 6
	more := true
	for more {
		more = (p.binaryString[ptr] == '1')
		ptr++
		for i := 0; i < 4; i++ {
			p.value = p.value << 1
			if p.binaryString[ptr] == '1' {
				p.value |= 1
			}
			ptr++
		}
	}
	p.binaryString = p.binaryString[:ptr]
}

func (p *Packet) parseOperator() {
	ptr := 6
	p.lengthTypeId = int(p.binaryString[ptr] - '0')
	ptr++
	if p.lengthTypeId == LengthTypeBits {
		for i := 0; i < 15; i++ {
			p.subLength = p.subLength << 1
			if p.binaryString[ptr] == '1' {
				p.subLength |= 1
			}
			ptr++
		}
		length := p.subLength
		for length > 0 {
			sp := ParseBinaryPacket(p.binaryString[ptr:])
			p.packets = append(p.packets, sp)
			ptr += len(sp.binaryString)
			length -= len(sp.binaryString)
		}
		p.binaryString = p.binaryString[:ptr]
	} else if p.lengthTypeId == LengthTypeSubPackets {
		for i := 0; i < 11; i++ {
			p.subLength = p.subLength << 1
			if p.binaryString[ptr] == '1' {
				p.subLength |= 1
			}
			ptr++
		}
		length := p.subLength
		for length > 0 {
			sp := ParseBinaryPacket(p.binaryString[ptr:])
			p.packets = append(p.packets, sp)
			ptr += len(sp.binaryString)
			length--
		}
		p.binaryString = p.binaryString[:ptr]
	}
	switch p.packetType {
	case PacketTypeSum:
		for _, sp := range p.packets {
			p.value += sp.value
		}
	case PacketTypeProduct:
		p.value = 1
		for _, sp := range p.packets {
			p.value *= sp.value
		}
	case PacketTypeMin:
		p.value = h.MAX_INT
		for _, sp := range p.packets {
			if sp.value < p.value {
				p.value = sp.value
			}
		}
	case PacketTypeMax:
		p.value = h.MIN_INT
		for _, sp := range p.packets {
			if sp.value > p.value {
				p.value = sp.value
			}
		}
	case PacketTypeGreater:
		if len(p.packets) == 2 && (p.packets[0].value > p.packets[1].value) {
			p.value = 1
		} else {
			p.value = 0
		}
	case PacketTypeLess:
		if len(p.packets) == 2 && (p.packets[0].value < p.packets[1].value) {
			p.value = 1
		} else {
			p.value = 0
		}
	case PacketTypeEqual:
		if len(p.packets) == 2 && (p.packets[0].value == p.packets[1].value) {
			p.value = 1
		} else {
			p.value = 0
		}
	}
}

func (p Packet) Json() string {
	ret := fmt.Sprintf("{\"version\":%d, \"type\":%d, \"value\":%d,", p.version, p.packetType, p.value)
	ret += "\"packets\":["
	for i := range p.packets {
		ret += fmt.Sprintf("%s", p.packets[i].String())
		if i < len(p.packets)-1 {
			ret += ","
		}
	}
	ret += fmt.Sprintf("],\"binary\":\"%s\"}", p.binaryString)
	return ret
}

func (p Packet) String() string {
	if p.packetType == PacketTypeLiteral {
		return fmt.Sprintf("%d", p.value)
	} else {
		return p.Json()
	}
}

func HexToByte(h string) byte {
	var b byte
	for i := range h {
		var wrk byte
		if h[i] >= '0' && h[i] <= '9' {
			wrk = byte(h[i] - '0')
		} else if h[i] >= 'A' && h[i] <= 'F' {
			wrk = byte(h[i]-'A') + 10
		}
		if i == 0 {
			b = wrk << 4
		} else {
			b |= wrk
		}
	}
	return b
}
