package main

import (
	"fmt"
	"log"
	"strings"

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

func main() {
	inp := h.StdinToStringSlice()
	part1(inp)
	part2(inp)
}

func part1(inp []string) {
	monkeys := BuildMonkeys(inp, 1)
	for i := 0; i < 20; i++ {
		for _, m := range monkeys.monkeys {
			m.Do(monkeys)
		}
	}
	fmt.Println("# Part 1")
	fmt.Println("Monkey Business:", monkeys.Business())
}

func part2(inp []string) {
	monkeys := BuildMonkeys(inp, 2)
	mod := uint64(1)
	for _, m := range monkeys.monkeys {
		mod = mod * m.Div
	}
	for i := 0; i < 10000; i++ {
		for _, m := range monkeys.monkeys {
			for len(m.Items) > 0 {
				m.Inspected++
				var v uint64
				v, m.Items = m.Items[len(m.Items)-1], m.Items[:len(m.Items)-1]
				v = m.Op(v) % mod
				if v%m.Div == 0 {
					monkeys.Find(m.TrueTo).Catch(v)
				} else {
					monkeys.Find(m.FalseTo).Catch(v)
				}
			}
		}
	}
	fmt.Println("# Part 2")
	fmt.Println("Monkey Business:", monkeys.Business())
}

type AllMonkeys struct {
	monkeys []*Monkey
	Part    uint64
}

func BuildMonkeys(inp []string, part uint64) *AllMonkeys {
	var ret []*Monkey
	for i := range inp {
		if strings.HasPrefix(inp[i], "Monkey") {
			ret = append(ret, NewMonkey(inp[i:]))
		}
	}
	return &AllMonkeys{
		monkeys: ret,
		Part:    part,
	}
}

func (a *AllMonkeys) Find(id int) *Monkey {
	for i := range a.monkeys {
		if a.monkeys[i].Id == id {
			return a.monkeys[i]
		}
	}
	return nil
}

func (a *AllMonkeys) Business() uint64 {
	// Find the 2 most active monkeys, multiply their inspections together
	var top1, top2 *Monkey
	for _, m := range a.monkeys {
		if top1 == nil {
			top1 = m
		} else if m.Inspected > top1.Inspected {
			top1, top2 = m, top1
		} else if top2 == nil || m.Inspected > top2.Inspected {
			top2 = m
		}
	}
	fmt.Println("Top Two")
	fmt.Println(top1)
	fmt.Println(top2)
	return top1.Inspected * top2.Inspected
}

type Monkey struct {
	Id        int
	Items     []uint64
	Inspected uint64
	Op        func(uint64) uint64
	Value     uint64
	TrueTo    int
	FalseTo   int
	Div       uint64
}

func NewMonkey(inp []string) *Monkey {
	monkey := &Monkey{}
	fmt.Sscanf(inp[0], "Monkey %d:", &monkey.Id)
	pts := strings.Fields(inp[1])
	for i := 2; i < len(pts); i++ {
		wrk := strings.TrimSuffix(pts[i], ",")
		monkey.Items = append(monkey.Items, h.Atoui(wrk))
	}
	// Parse out this Monkey's Operation Function
	pts = strings.Fields(inp[2])
	var p1, p2 uint64
	p1S := pts[3]
	if p1S != "old" {
		p1 = h.Atoui(p1S)
	}
	p2S := pts[5]
	if p2S != "old" {
		p2 = h.Atoui(p2S)
	}
	op := pts[4]
	// And the Test
	monkey.Div = h.Atoui(inp[3][21:])

	switch op {
	case "*":
		monkey.Op = func(v uint64) uint64 {
			first, second := v, v
			if p1S != "old" {
				first = p1
			}
			if p2S != "old" {
				second = p2
			}
			return first * second
		}
	case "+":
		monkey.Op = func(v uint64) uint64 {
			first, second := v, v
			if p1S != "old" {
				first = p1
			}
			if p2S != "old" {
				second = p2
			}
			return first + second
		}
	}
	monkey.TrueTo = h.Atoi(inp[4][29:])
	monkey.FalseTo = h.Atoi(inp[5][30:])
	return monkey
}

func (m *Monkey) Do(all *AllMonkeys) {
	for i := range m.Items {
		item := m.Items[i]
		pre := item
		item = m.Op(item)
		if item < pre {
			log.Fatalf("%s; Was %d; Now: %d\n", m, pre, item)
		}
		if all.Part == 1 {
			item = item / 3
		}
		if item%m.Div == 0 {
			all.Find(m.TrueTo).Catch(item)
		} else {
			all.Find(m.FalseTo).Catch(item)
		}
		m.Inspected++
	}
	m.Items = []uint64{}
}

func (m *Monkey) Catch(item uint64) {
	m.Items = append(m.Items, item)
}

func (m Monkey) String() string {
	return fmt.Sprintf("Monkey %d; Inspected %d; Items: %v", m.Id, m.Inspected, m.Items)
}