package main

import "core:log"
import "core:os"
import "core:slice"
import "core:strconv"
import "core:strings"

Rule :: struct {
	before: [dynamic]int,
	after:  [dynamic]int,
}

Input :: struct {
	rules:   map[int]Rule,
	updates: [dynamic][]int,
}

Result1 :: distinct int
Result2 :: distinct int


// --- Input --- //
print_input :: proc(input: ^Input) {
	log.infof("%v", input)
}

parse_input_file :: proc(filepath: string) -> Input {
	input: Input

	raw_data, ok := os.read_entire_file_from_filename(filepath)
	if !ok {
		panic("oh no, could not read file")
	}
	defer delete(raw_data)

	input.rules = map[int]Rule{}
	input.updates = make([dynamic][]int, 0, 10)

	rules_done: bool
	string_data := string(raw_data)
	for line in strings.split_lines_iterator(&string_data) {
		if len(line) == 0 {
			rules_done = true
			continue
		}

		if !rules_done {
			split := strings.index_rune(line, '|')
			a := strconv.atoi(line[:split])
			b := strconv.atoi(line[split + 1:])

			if a not_in input.rules {
				input.rules[a] = {
					before = make([dynamic]int, 0, 10),
					after  = make([dynamic]int, 0, 10),
				}
			}
			if b not_in input.rules {
				input.rules[b] = {
					before = make([dynamic]int, 0, 10),
					after  = make([dynamic]int, 0, 10),
				}
			}
			rule_a := &input.rules[a]
			rule_b := &input.rules[b]
			append(&rule_a.before, b)
			append(&rule_b.after, a)
		} else {
			raw_values := strings.split(line, ",")
			defer delete(raw_values)

			values := make([]int, len(raw_values))
			for v, i in raw_values {
				values[i] = strconv.atoi(v)
			}
			append(&input.updates, values)
		}
	}

	return input
}

free_input :: proc(input: ^Input) {
	for _, i in input.updates {
		delete(input.updates[i])
	}
	delete(input.updates)

	for k in input.rules {
		delete(input.rules[k].before)
		delete(input.rules[k].after)
	}
	delete(input.rules)
}
// --- Input --- //


// --- Helpers --- //
// --- Helpers --- //


// --- Task 1 --- //
run_task1 :: proc(input: ^Input, debug: bool) -> Result1 {
	result: Result1

	if debug {
		print_input(input)
	}

	for values in input.updates {
		valid := true
		for v in values {
			after: bool
			for u in values {
				if u == v {
					after = true
					continue
				}

				if (!after && !slice.contains(input.rules[v].after[:], u)) ||
				   (after && !slice.contains(input.rules[v].before[:], u)) {
					valid = false
					break
				}
			}
		}
		if valid {
			log.debugf("%v VALID", values)
			result += Result1(values[len(values) / 2])
		} else {
			log.debugf("%v INVALID", values)
		}
	}

	return result
}

print_result1 :: proc(result: ^Result1) {
	log.infof("Task 1: %d", result^)
}
// --- Task 1 --- //


// --- Task 2 --- //
run_task2 :: proc(input: ^Input, debug: bool) -> Result2 {
	result: Result2

	if debug {
		print_input(input)
	}

	for values in input.updates {
		valid := true
		for v in values {
			after: bool
			for u in values {
				if u == v {
					after = true
					continue
				}

				if (!after && !slice.contains(input.rules[v].after[:], u)) ||
				   (after && !slice.contains(input.rules[v].before[:], u)) {
					valid = false
					break
				}
			}
		}
		if !valid {
			log.debugf("%v INVALID", values)
			less :: proc(a, b: int) -> bool {
				rules := cast(^map[int]Rule)context.user_ptr
				return slice.contains(rules[a].before[:], b)
			}
			context.user_ptr = &input.rules
			slice.sort_by(values, less)
			result += Result2(values[len(values) / 2])
		}
	}

	return result
}

print_result2 :: proc(result: ^Result2) {
	log.infof("Task 2: %d", result^)
}
// --- Task 2 --- //