package main

import (
	"flag"
	"fmt"
	"sort"
	"strings"

	"github.com/alexchao26/advent-of-code-go/cast"
	"github.com/alexchao26/advent-of-code-go/mathy"
	"github.com/alexchao26/advent-of-code-go/util"
)

func main() {
	var part int
	flag.IntVar(&part, "part", 1, "part 1 or 2")
	flag.Parse()
	fmt.Println("Running part", part)

	if part == 1 {
		ans := part1(util.ReadFile("./input.txt"))
		util.CopyToClipboard(fmt.Sprintf("%v", ans))
		fmt.Println("Output:", ans)
	} else {
		ans := part2(util.ReadFile("./input.txt"))
		util.CopyToClipboard(fmt.Sprintf("%v", ans))
		fmt.Println("Output:", ans)
	}
}

func part1(input string) int {
	nums := parseInput(input)
	nums = append(nums, mathy.MaxInt(nums...)+3)
	sort.Ints(nums)

	var oneDiff, threeDiff int
	var currentJoltage int
	for _, v := range nums {
		switch v - currentJoltage {
		case 1: // check for 1 diff first, so no adapters are skipped
			oneDiff++
		case 3:
			threeDiff++
		default:
			panic("adpaters not connected by 3 or 1")
		}
		currentJoltage = v
	}

	return oneDiff * threeDiff
}

func part2(input string) int {
	nums := parseInput(input)
	nums = append(nums, mathy.MaxInt(nums...)+3)
	sort.Ints(nums)

	// return dynamicProgramming(input)
	return memoCountPossibilities(nums, 0)
}

func parseInput(input string) []int {
	var ans []int

	lines := strings.Split(input, "\n")
	for _, l := range lines {
		ans = append(ans, cast.ToInt(l))
	}

	return ans
}

// storing memo in global state isn't ideal... but it's fastser to code
var memo = map[string]int{}

func memoCountPossibilities(nums []int, lastJolt int) int {
	// if in memo, return that value
	str := makeMemoKey(nums, lastJolt)
	if v, ok := memo[str]; ok {
		return v
	}

	// if all adapters used up, return 1
	if len(nums) == 0 {
		return 1
	}

	// create a recursive call for each adapter within 3 of the lastJoltage
	var count int
	for i, v := range nums {
		if v-lastJolt <= 3 {
			count += memoCountPossibilities(nums[i+1:], v)
		} else { // stop counting if the joltage diff is too larger (>3)
			break
		}
	}

	// update memo
	memo[str] = count

	return count
}
func makeMemoKey(nums []int, lastJolt int) string {
	ans := cast.ToString(lastJolt) + "x"
	for _, v := range nums {
		ans += cast.ToString(v)
	}
	return ans
}

func dynamicProgramming(input string) int {
	nums := parseInput(input)
	nums = append(nums, mathy.MaxInt(nums...)+3, 0)
	sort.Ints(nums)

	// initialize table with "1 way" to get to zero jolts
	table := make([]int, len(nums))
	table[0] = 1

	for i := 1; i < len(nums); i++ {
		currentJolts := nums[i]
		for j := i - 1; j >= 0; j-- {
			// add the ways to get to currentJolts that are within 3 jolts
			if currentJolts-nums[j] <= 3 {
				table[i] += table[j]
			} else {
				break
			}
		}
	}

	return table[len(table)-1]
}
