package day_09

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

Input :: struct {
	bounds:    struct {
		x1, y1, x2, y2: int,
	},
	red_tiles: [][2]int,
}

Result1 :: int
Result2 :: int

// --- Input --- //
print_input :: proc(input: Input) {
	b: strings.Builder
	strings.builder_init(&b)
	defer strings.builder_destroy(&b)

	fmt.sbprint(&b, "\n")
	for y in input.bounds.y1 - 1 ..= input.bounds.y2 + 1 {
		for x in input.bounds.x1 - 1 ..= input.bounds.x2 + 1 {
			if slice.contains(input.red_tiles, [2]int{x, y}) {
				fmt.sbprintf(&b, "\x1b[%d;%dm%c", 31, 41, '#')
			} else {
				fmt.sbprintf(&b, "\x1b[%d;%dm%c", 90, 100, '.')
			}
		}
		fmt.sbprint(&b, "\x1b[0m\n")
	}

	log.infof("%s", strings.to_string(b))
}

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)

	lines, err := strings.split_lines(strings.trim_space(string(raw_data)))
	if err != .None {
		panic("oh no, failed splitting into lines")
	}
	defer delete(lines)

	input.red_tiles = make([][2]int, len(lines))
	for line, i in lines {
		coord := strings.split(line, ",")
		defer delete(coord)

		pos := [2]int{strconv.parse_int(coord[0]) or_else 0, strconv.parse_int(coord[1]) or_else 0}
		input.red_tiles[i] = pos

		if input.bounds.x1 == 0 || input.bounds.x1 >= pos.x {
			input.bounds.x1 = pos.x
		}
		if input.bounds.x2 == 0 || input.bounds.x2 <= pos.x {
			input.bounds.x2 = pos.x
		}
		if input.bounds.y1 == 0 || input.bounds.y1 >= pos.y {
			input.bounds.y1 = pos.y
		}
		if input.bounds.y2 == 0 || input.bounds.y2 <= pos.y {
			input.bounds.y2 = pos.y
		}
	}

	return input
}

free_input :: proc(input: ^Input) {
	delete(input.red_tiles)
}
// --- Input --- //


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


// --- Task 1 --- //
run_task1 :: proc(input: Input, iteration_count: int = 1000) -> Result1 {
	result: Result1

	for i in 0 ..< len(input.red_tiles) {
		for j in i + 1 ..< len(input.red_tiles) {
			a := input.red_tiles[i]
			b := input.red_tiles[j]
			if a.x == b.x || a.y == b.y {
				continue
			}

			w := (a.x - b.x if a.x > b.x else b.x - a.x) + 1
			h := (a.y - b.y if a.y > b.y else b.y - a.y) + 1
			area := w * h
			if area > result {
				result = area
			}
			log.debugf("%v to %v => %d", a, b, area)
		}
	}

	return result
}

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


// --- Task 2 --- //
Edge :: struct {
	a, b: [2]int,
}

is_vertical :: proc(e: Edge) -> bool {
	return e.a.x == e.b.x
}

is_horizontal :: proc(e: Edge) -> bool {
	return e.a.y == e.b.y
}

edge_intersection :: proc(a, b: Edge) -> bool {
	a_vertical := is_vertical(a)
	a_horizontal := is_horizontal(a)
	b_vertical := is_vertical(b)
	b_horizontal := is_horizontal(b)

	// Only perpendicular cases can "properly" intersect
	if a_vertical && b_horizontal {
		vx := a.a.x
		vy_min := min(a.a.y, a.b.y)
		vy_max := max(a.a.y, a.b.y)

		hy := b.a.y
		hx_min := min(b.a.x, b.b.x)
		hx_max := max(b.a.x, b.b.x)

		// 1) Check if intersection point lies on both segments (inclusive)
		if vx < hx_min || vx > hx_max {
			return false
		}
		if hy < vy_min || hy > vy_max {
			return false
		}

		// Intersection point
		ix := vx
		iy := hy

		// 2) Check if this point is an endpoint of each segment
		is_endpoint_a := (ix == a.a.x && iy == a.a.y) || (ix == a.b.x && iy == a.b.y)
		is_endpoint_b := (ix == b.a.x && iy == b.a.y) || (ix == b.b.x && iy == b.b.y)

		// If it's a shared endpoint (corner touch), allow it
		if is_endpoint_a && is_endpoint_b {
			return false
		}

		// Otherwise it's a "real" intersection (including your 2,1–2,5 with 2,3–7,3 case)
		return true
	}

	if a_horizontal && b_vertical {
		// Just swap roles
		return edge_intersection(b, a)
	}

	// Parallel segments (both vertical or both horizontal) are treated as no crossing.
	// Overlap along the same line is "on the boundary" and allowed.
	return false
}

// Doesn't actually work on actual input
// Returns:    518586912
// Should be: 1476550548
run_task2 :: proc(input: Input) -> Result2 {
	result: Result2

	edges := make([]Edge, len(input.red_tiles))
	defer delete(edges)

	current := 0
	for i in 0 ..< len(input.red_tiles) {
		j := (i + 1) % len(input.red_tiles)
		edges[current] = {
			a = input.red_tiles[i],
			b = input.red_tiles[j],
		}
		current += 1
	}
	log.debugf("edges: %v", edges)

	for i in 0 ..< len(input.red_tiles) {
		for j in i + 1 ..< len(input.red_tiles) {
			a := input.red_tiles[i]
			b := input.red_tiles[j]
			if a.x == b.x || a.y == b.y {
				continue
			}

			x_min, x_max := min(a.x, b.x), max(a.x, b.x)
			y_min, y_max := min(a.y, b.y), max(a.y, b.y)

			area := ((x_max - x_min) + 1) * ((y_max - y_min) + 1)
			if area <= result {
				continue
			}

			A := [2]int{x_min, y_min}
			B := [2]int{x_min, y_max}
			C := [2]int{x_max, y_min}
			D := [2]int{x_max, y_max}

			AB := Edge {
				a = A,
				b = B,
			}
			BD := Edge {
				a = B,
				b = D,
			}
			DC := Edge {
				a = D,
				b = C,
			}
			CA := Edge {
				a = C,
				b = A,
			}

			log.debugf("%v %v %v %v", AB, BD, DC, CA)

			intersects: bool
			for e in edges {
				intersects =
					edge_intersection(AB, e) ||
					edge_intersection(BD, e) ||
					edge_intersection(DC, e) ||
					edge_intersection(CA, e)

				if intersects {
					break
				}
			}

			log.debugf("%v to %v => %d%s", a, b, area, " INTERSECTS" if intersects else "")

			result = area if !intersects else result
		}
	}

	return result
}

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

run :: proc() {
	input := parse_input_file("input/day_09.txt")
	defer free_input(&input)

	result1 := run_task1(input)
	print_result1(result1)

	result2 := run_task2(input)
	print_result2(result2)
}