package main

import (
	"fmt"
	"regexp"
	"strings"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

func main() {
	fmt.Println("# Day 19")
	fmt.Println()
	inp := h.StdinToStringSlice()
	part := h.OptArgNumber(1, "2")
	solve(inp, h.Atoi(part))
}

/*
 8: 42 | 42 8
11: 42 31 | 42 11 31

Figure out the values of 31 & 42, then see how they
affect things as they repeat

31: 95 7 | 104 130
42: 29 104 | 115 95
*/

func solve(inp []string, part int) {
	var section int
	var rules []string
	var messages []string
	for _, v := range inp {
		if v == "" {
			// End of the rules list
			section++
		}
		switch section {
		case 0:
			rules = append(rules, v)
		case 1:
			messages = append(messages, v)
		}
	}
	if part == 1 {
		solveOne(rules, messages)
	} else {
		solveTwo(rules, messages)
	}
}

func solveOne(rules, messages []string) {
	reg := regexp.MustCompile(rulesToRegex(rules))
	var answer int
	for _, msg := range messages {
		if reg.MatchString(msg) {
			answer++
		}
	}
	fmt.Printf("## Part 1\nAnswer: %d\n", answer)
}

func solveTwo(rules, messages []string) {
	// Figure out how many levels deep we have to go to stabilize
	var answer int
	var last []int
	for i := 0; i < 1000; i++ {
		answer = 0
		rules = unwrapLoops(rules, i)
		reg := regexp.MustCompile(rulesToRegex(rules))
		for _, msg := range messages {
			if reg.MatchString(msg) {
				answer++
			}
		}
		if isStable(last, 10) {
			break
		}
		last = append(last, answer)
	}
	fmt.Printf("## Part 2\nAnswer: %d\n", answer)
}

func isStable(history []int, confidence int) bool {
	if len(history) < confidence {
		return false
	}
	last := h.MAX_INT
	for k := len(history) - 1; k > len(history)-confidence; k-- {
		if last == h.MAX_INT {
			last = history[k]
			continue
		}
		if history[k] != last {
			return false
		}
	}
	return true
}

func unwrapLoops(rules []string, depth int) []string {
	for i := range rules {
		wrk := rules[i]
		if strings.HasPrefix(wrk, "8:") {
			wrk = "8: 42 +"
		} else if strings.HasPrefix(wrk, "11:") {
			rule := "42 31"
			for k := 2; k < depth; k++ {
				rule += " | "
				for j := 0; j < k; j++ {
					rule += " 42 "
				}
				for j := 0; j < k; j++ {
					rule += " 31 "
				}
			}
			wrk = "11: " + rule
		}
		rules[i] = wrk
	}
	return rules
}

func rulesToRegex(rules []string) string {
	cache := make(map[int]string)
	for {
		if _, ok := cache[0]; ok {
			// We've cached 0, so we're all done
			break
		}
	loop:
		for _, rule := range rules {
			tokens := strings.Fields(rule)
			var num int
			fmt.Sscanf(tokens[0], "%d:", &num)
			if _, ok := cache[num]; ok {
				continue
			}
			tokens = tokens[1:]
			// A 'leaf'
			if len(tokens) == 1 && tokens[0][0] == '"' {
				cache[num] = string(tokens[0][1])
				continue
			}
			// All other rules
			ruleReg := "(?:"
			for _, tkn := range tokens {
				c := tkn[0]
				switch {
				case '0' <= c && c <= '9':
					tknN := h.Atoi(tkn)
					subRuleReg, ok := cache[tknN]
					if !ok {
						continue loop
					}
					ruleReg += subRuleReg
				case c == '|':
					ruleReg += "|"
				case c == '+':
					ruleReg += "+"
				default:
					panic("Error building RegEx:" + tkn)
				}
			}
			ruleReg += ")"
			cache[num] = ruleReg
		}
	}
	// We should be done now, we just want an exact match on rule 0
	return "^" + cache[0] + "$"
}
