package main

import "core:bytes"
import "core:fmt"
import "core:io"
import "core:os"
import "core:slice"
import "core:sort"
import "core:strconv"
import "core:strings"

HandType :: enum (int) {
	HighCard,
	OnePair,
	TwoPair,
	ThreeOfAKind,
	FullHouse,
	FourOfAKind,
	FiveOfAKind,
}

Card := map[rune]int {
	'A' = 12,
	'K' = 11,
	'Q' = 10,
	'J' = 9,
	'T' = 8,
	'9' = 7,
	'8' = 6,
	'7' = 5,
	'6' = 4,
	'5' = 3,
	'4' = 2,
	'3' = 1,
	'2' = 0,
}

CardJoker := map[rune]int {
	'A' = 12,
	'K' = 11,
	'Q' = 10,
	'T' = 9,
	'9' = 8,
	'8' = 7,
	'7' = 6,
	'6' = 5,
	'5' = 4,
	'4' = 3,
	'3' = 2,
	'2' = 1,
	'J' = 0,
}

Hand :: struct {
	cards:         [5]rune,
	bid:           int,
	type:          HandType,
	type_if_joker: HandType,
}

Input :: struct {
	hands: [dynamic]Hand,
}

Result1 :: distinct int
Result2 :: distinct int


// --- Input --- //
print_input :: proc(input: ^Input) {
	for h in input.hands {
		fmt.printf("%v(%s|%s) -> %d\n", h.cards, h.type, h.type_if_joker, h.bid)
	}
}

identify_hand_type :: proc(cards: []rune) -> HandType {
	card_map := make(map[rune]int)
	defer delete(card_map)

	for r in cards {
		card_map[r] += 1
	}
	high: int
	for _, n in card_map {
		if n > high {
			high = n
		}
	}

	switch len(card_map) {
	case 1:
		return .FiveOfAKind
	case 2:
		if high > 3 {
			return .FourOfAKind
		}
		return .FullHouse
	case 3:
		if high > 2 {
			return .ThreeOfAKind
		}
		return .TwoPair
	case 4:
		return .OnePair
	}

	return .HighCard
}

identify_hand_type_if_joker :: proc(cards: []rune) -> HandType {
	card_map := make(map[rune]int)
	defer delete(card_map)

	num_jokers: int
	for r in cards {
		if r == 'J' {
			num_jokers += 1
			continue
		}
		card_map[r] += 1
	}
	high: int
	for _, n in card_map {
		if n > high {
			high = n
		}
	}
	high += num_jokers

	if high == 5 {
		return .FiveOfAKind
	}

	switch len(card_map) {
	case 1:
		return .FiveOfAKind
	case 2:
		if high > 3 {
			return .FourOfAKind
		}
		return .FullHouse
	case 3:
		if high > 2 {
			return .ThreeOfAKind
		}
		return .TwoPair
	case 4:
		return .OnePair
	}

	return .HighCard
}

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")
	}

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

	input.hands = make([dynamic]Hand, 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]

		hand_line := strings.fields(line)
		defer delete(hand_line)

		hand: Hand
		for r, i in hand_line[0] {
			hand.cards[i] = r
		}
		hand.bid = strconv.atoi(hand_line[1])
		hand.type = identify_hand_type(hand.cards[:])
		hand.type_if_joker = identify_hand_type_if_joker(hand.cards[:])

		append(&input.hands, hand)
	}

	return input
}

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


// --- Task 1 --- //
sort_hands :: proc(hands: ^[]Hand) -> sort.Interface {
	it := sort.Interface {
		collection = rawptr(hands),
		len = proc(it: sort.Interface) -> int {
			hands := (^[]Hand)(it.collection)
			return len(hands^)
		},
		less = proc(it: sort.Interface, i, j: int) -> bool {
			hands := (^[]Hand)(it.collection)
			if hands[i].type == hands[j].type {
				for t in 0 ..< 5 {
					if hands[i].cards[t] != hands[j].cards[t] {
						return Card[hands[i].cards[t]] < Card[hands[j].cards[t]]
					}
				}
			}
			return hands[i].type < hands[j].type
		},
		swap = proc(it: sort.Interface, i, j: int) {
			hands := (^[]Hand)(it.collection)
			hands[i], hands[j] = hands[j], hands[i]
		},
	}
	return it
}

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

	if debug {
		print_input(input)
	}

	sorted_hands := slice.clone(input.hands[:])
	defer delete(sorted_hands)

	sort.sort(sort_hands(&sorted_hands))
	for hand, i in sorted_hands {
		result += Result1((i + 1) * hand.bid)
	}

	return result
}

print_result1 :: proc(result: ^Result1) {
	fmt.printf("Task 1: %d\n", result^)
}
// --- Task 1 --- //


// --- Task 2 --- //
sort_joker_hands :: proc(hands: ^[]Hand) -> sort.Interface {
	it := sort.Interface {
		collection = rawptr(hands),
		len = proc(it: sort.Interface) -> int {
			hands := (^[]Hand)(it.collection)
			return len(hands^)
		},
		less = proc(it: sort.Interface, i, j: int) -> bool {
			hands := (^[]Hand)(it.collection)
			if hands[i].type_if_joker == hands[j].type_if_joker {
				for t in 0 ..< 5 {
					if hands[i].cards[t] != hands[j].cards[t] {
						return CardJoker[hands[i].cards[t]] < CardJoker[hands[j].cards[t]]
					}
				}
			}
			return hands[i].type_if_joker < hands[j].type_if_joker
		},
		swap = proc(it: sort.Interface, i, j: int) {
			hands := (^[]Hand)(it.collection)
			hands[i], hands[j] = hands[j], hands[i]
		},
	}
	return it
}

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

	if debug {
		print_input(input)
	}

	sorted_hands := slice.clone(input.hands[:])
	defer delete(sorted_hands)

	sort.sort(sort_joker_hands(&sorted_hands))
	for hand, i in sorted_hands {
		result += Result2((i + 1) * hand.bid)
	}

	return result
}

print_result2 :: proc(result: ^Result2) {
	fmt.printf("Task 2: %d\n", result^)
}
// --- Task 2 --- //