🍯 Glaze

package main

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

parse_input_file :: proc(filepath: string) -> []u8 {
	data, ok := os.read_entire_file_from_filename(filepath)
	if !ok {
		panic("oh no, could not read file")
	}

	return data
}

Node :: struct {
	size:     int,
	children: Node_Map,
}

Node_Map :: map[string]Node

parse_filesystem :: proc(buf: ^bytes.Buffer, parsed: ^Node_Map, current_directory: string = "") {
	line: []u8
	err: io.Error
	for ; err != .EOF; line, err = bytes.buffer_read_bytes(buf, '\n') {
		if len(line) == 0 {
			continue
		}
		line = line[:len(line) - 1]

		cmd := string(line[2:4])
		if cmd == "cd" {
			target := string(line[5:])
			if target == ".." {
				break
			}

			if !(target in parsed) {
				parsed[target] = {
					size     = 0,
					children = make(Node_Map),
				}
			}
			dir := &parsed[target]
			parse_filesystem(buf, &dir.children, target)
		} else if cmd == "ls" {
			continue
		} else {
			split: int
			for c, i in line {
				if c == ' ' {
					split = i
				}
			}

			size_part := string(line[:split])
			name_part := string(line[split + 1:])
			if !(name_part in parsed) {
				if size_part == "dir" {
					parsed[name_part] = {
						size     = 0,
						children = make(Node_Map),
					}
				} else {
					parsed[name_part] = {
						size     = strconv.atoi(size_part),
						children = nil,
					}
				}
			}
		}
	}
}

update_dir_size :: proc(parsed_filesystem: ^Node_Map) -> int {
	size: int
	for _, node in parsed_filesystem {
		if node.size == 0 {
			node.size = update_dir_size(&node.children)
		}
		size += node.size
	}
	return size
}

find_total_of_max_size :: proc(parsed_filesystem: ^Node_Map, max_size: int) -> int {
	size: int
	for _, node in parsed_filesystem {
		if node.children != nil {
			if node.size <= 100000 {
				size += node.size
			}
			size += find_total_of_max_size(&node.children, max_size)
		}
	}
	return size
}

find_size_to_save :: proc(parsed_filesystem: ^Node_Map, total_size, target_size: int) -> int {
	min_size := 70000000
	for _, node in parsed_filesystem {
		if node.children != nil {
			if total_size - node.size <= target_size {
				if node.size < min_size {
					min_size = node.size
				}
			}
			size := find_size_to_save(&node.children, total_size, target_size)
			if size < min_size {
				min_size = size
			}
		}
	}
	return min_size
}

task1 :: proc(filesystem: []u8) -> int {
	buf: bytes.Buffer
	bytes.buffer_init(&buf, filesystem)
	defer bytes.buffer_destroy(&buf)

	parsed_filesystem := make(Node_Map)
	defer delete(parsed_filesystem)
	parse_filesystem(&buf, &parsed_filesystem)
	update_dir_size(&parsed_filesystem)

	return find_total_of_max_size(&parsed_filesystem, 100000)
}

task2 :: proc(filesystem: []u8) -> int {
	buf: bytes.Buffer
	bytes.buffer_init(&buf, filesystem)
	defer bytes.buffer_destroy(&buf)

	parsed_filesystem := make(Node_Map)
	defer delete(parsed_filesystem)
	parse_filesystem(&buf, &parsed_filesystem)
	update_dir_size(&parsed_filesystem)

	return find_size_to_save(&parsed_filesystem, parsed_filesystem["/"].size, 40000000)
}

main :: proc() {
	filesystem := parse_input_file("input.txt")
	defer delete(filesystem)

	result1 := task1(filesystem)
	fmt.printf("Task 1 result: %d\n", result1)

	result2 := task2(filesystem)
	fmt.printf("Task 2 result: %d\n", result2)
}