🍯 Glaze

package main

import "core:bytes"
import "core:fmt"
import "core:io"
import "core:os"
import "core:slice"

Item :: struct {
	list:   ^List,
	number: int,
}

List :: struct {
	parent: ^List,
	items:  [dynamic]Item,
}

// NOTE: Have to free all of Packet
parse_input_file :: proc(filepath: string) -> [dynamic]List {
	data, ok := os.read_entire_file_from_filename(filepath)
	if !ok {
		panic("oh no, could not read file")
	}

	buf: bytes.Buffer
	bytes.buffer_init(&buf, data)
	defer bytes.buffer_destroy(&buf)

	packets := make([dynamic]List, 0, 10)

	line: string
	err: io.Error
	for ; err != .EOF; line, err = bytes.buffer_read_string(&buf, '\n') {
		if line == "" || line == "\n" {
			continue
		}
		line = line[:len(line) - 1]

		packet: List
		current: ^List
		for i := 0; i < len(line[:]); i += 1 {
			c := line[i]
			switch (c) {
			case '[':
				if current == nil {
					current = &packet
					current.items = make([dynamic]Item, 0, 10)
				} else {
					list := new(List)
					list.parent = current
					list.items = make([dynamic]Item, 0, 10)
					append(¤t.items, Item{list = list})
					current = list
				}
			case ']':
				current = current.parent
			case '0' ..= '9':
				num := int(c - '0')
				if (line[i + 1] >= '0' && line[i + 1] <= '9') {
					num = (num * 10) + int(line[i + 1] - '0')
					i += 1
				}
				append(¤t.items, Item{number = num})
			case:
			}
		}

		append(&packets, packet)
	}

	return packets
}

free_list :: proc(list: ^List) {
	for item in list.items {
		if item.list != nil {
			free_list(item.list)
			free(item.list)
		}
	}
}

free_lists :: proc(lists: [dynamic]List) {
	for _, i in lists {
		free_list(&lists[i])
	}
	delete(lists)
}

print_list :: proc(list: []Item) {
	fmt.printf(" [")
	for item in list {
		if item.list != nil {
			print_list(item.list.items[:])
		} else {
			fmt.printf(" %d ", item.number)
		}
	}
	fmt.printf("] ")
}

compare_lists :: proc(a, b: []Item, depth := 0, debug := false) -> int {
	if debug {
		for _ in 0 ..< depth {
			fmt.printf("  ")
		}
		fmt.printf("- Compare ")
		print_list(a)
		fmt.printf(" vs ")
		print_list(b)
		fmt.printf("\n")
	}

	max_len := len(a) if len(a) < len(b) else len(b)
	for i in 0 ..< max_len {
		if a[i].list == nil && b[i].list == nil {
			x, y := a[i].number, b[i].number
			if debug {
				for _ in 0 ..< depth + 1 {
					fmt.printf("  ")
				}
				fmt.printf("- Compare %d vs %d\n", x, y)
			}
			if x < y {
				if debug {
					for _ in 0 ..< depth + 2 {
						fmt.printf("  ")
					}
					fmt.printf("- Left side is smaller, so inputs are in the right order\n")
				}
				return 1
			} else if x > y {
				if debug {
					for _ in 0 ..< depth + 2 {
						fmt.printf("  ")
					}
					fmt.printf("- Right side is smaller, so inputs are not in the right order\n")
				}
				return -1
			}
		} else {
			aa := a[i].list.items[:] if a[i].list != nil else []Item{{number = a[i].number}}
			bb := b[i].list.items[:] if b[i].list != nil else []Item{{number = b[i].number}}
			cmp := compare_lists(aa, bb, depth + 1, debug)
			if cmp != 0 {
				return cmp
			}
		}
	}
	if len(a) < len(b) {
		if debug {
			for _ in 0 ..< depth + 1 {
				fmt.printf("  ")
			}
			fmt.printf("- Left side ran out of items, so inputs are in the right order\n")
		}
		return 1
	}
	if len(a) > len(b) {
		if debug {
			for _ in 0 ..< depth + 1 {
				fmt.printf("  ")
			}
			fmt.printf("- Right side ran out of items, so inputs are not in the right order\n")
		}
		return -1
	}

	return 0
}

task1 :: proc(packets: []List, debug := false) -> int {
	result: int

	num_pairs := len(packets) / 2
	for i in 0 ..< num_pairs {
		packet_a := packets[i * 2].items[:]
		packet_b := packets[(i * 2) + 1].items[:]

		if debug {
			fmt.printf("== Pair %d ==\n", i + 1)
		}

		if compare_lists(packet_a, packet_b, 0, debug) == 1 {
			result += i + 1
			if debug {
				fmt.printf("%d is okay\n", i + 1)
			}
		}

		if debug {
			fmt.printf("\n")
		}
	}

	return result
}

task2 :: proc(packets: []List, debug := false) -> int {
	result := 1

	if debug {
		for packet in packets {
			print_list(packet.items[:])
			fmt.printf("\n")
		}
	}

	slice.sort_by(packets, proc(a, b: List) -> bool {
		return compare_lists(a.items[:], b.items[:], 0, false) == 1
	})

	if debug {
		fmt.printf("===\n")
		for packet in packets {
			print_list(packet.items[:])
			fmt.printf("\n")
		}
	}

	for packet, i in packets {
		if len(packet.items) == 1 &&
		   packet.items[0].list != nil &&
		   len(packet.items[0].list.items) == 1 {
			item := packet.items[0].list.items[0]
			if item.number == 2 || item.number == 6 {
				result *= (i + 1)
			}
		}
	}

	return result
}

add_task2_values :: proc(packets: ^[dynamic]List) {
	{
		outer := List {
			items = make([dynamic]Item, 0, 1),
		}
		inner := new(List)
		inner.items = make([dynamic]Item, 0, 1)
		append(&inner.items, Item{number = 2})
		append(&outer.items, Item{list = inner})
		append(packets, outer)
	}
	{
		outer := List {
			items = make([dynamic]Item, 0, 1),
		}
		inner := new(List)
		inner.items = make([dynamic]Item, 0, 1)
		append(&inner.items, Item{number = 6})
		append(&outer.items, Item{list = inner})
		append(packets, outer)
	}
}

main :: proc() {
	debug1: bool
	debug2: bool
	if len(os.args) > 1 {
		for arg in os.args {
			switch arg {
			case "-d1":
				debug1 = true
			case "-d2":
				debug2 = true
			}
		}
	}

	packets := parse_input_file("input.txt")
	defer free_lists(packets)

	result1 := task1(packets[:], debug1)
	fmt.printf("Task 1 result: %d\n", result1)

	add_task2_values(&packets)
	result2 := task2(packets[:], debug2)
	fmt.printf("Task 2 result: %d\n", result2)
}