// Note: Do NOT use this solution as an example. I hate it. It's a horrible mess
// of brute force, workarounds, and shortcuts, and is generally really not a
// good idea. A syntax tree would be a better idea and what I should have done
// from the beginning but I tried to save time by not thinking|I mean using
// recursion instead.  I've fought overflows, indexing errors, recursion loops,
// general stubborn stupidity, and I'm even two days behind.
// But I guess I've learnt what _not_ to do as well.
// Next year AoC, next year..

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func reduce(exp []rune, debug bool) int {
	if debug {
		fmt.Printf("iter ")
		for _, r := range exp {
			if r < ' ' {
				fmt.Printf("%d", r)
			} else {
				fmt.Printf("%c", r)
			}
		}
		fmt.Println()
	}
	var a, b int
	var op rune

	if len(exp) == 1 {
		return int(exp[0])
	}
	if len(exp) <= 3 {
		a = int(exp[0])
		b = int(exp[2])
		op = exp[1]

		if debug {
			fmt.Printf("\t? %d %c %d\n", a, op, b)
		}
	} else {
		if exp[len(exp)-1] == ')' {
			count := 1
			for i := len(exp) - 2; i >= 0; i-- {
				if exp[i] == ')' {
					count++
				} else if exp[i] == '(' {
					count--
					if count == 0 {
						if i == 0 {
							return reduce(exp[i+1:len(exp)-1], debug)
						}

						a = reduce(exp[:i-1], debug)
						b = reduce(exp[i+1:len(exp)-1], debug)
						op = exp[i-1]
						break
					}
				}
			}
		} else {
			a = reduce(exp[:len(exp)-2], debug)
			b = int(exp[len(exp)-1])
			op = exp[len(exp)-2]
		}

		if debug {
			fmt.Printf("\t# %d %c %d\n", a, op, b)
		}
	}

	var result int
	switch op {
	case '*':
		result = a * b
	case '+':
		result = a + b
	}
	if debug {
		fmt.Printf("%d %c %d => %d\n", a, op, b, result)
	}

	return result
}

func reduceAdditions(exp []int, debug bool) []int {
	newExp := make([]int, 0, len(exp))

	for i := 0; i < len(exp); i++ {
		switch exp[i] {
		case '(':
			var count int
			for j := i; j < len(exp); j++ {
				if exp[j] == '(' {
					count++
				} else if exp[j] == ')' {
					count--
					if count == 0 {
						if debug {
							fmt.Printf("mm ")
							for _, r := range exp[i+1 : j] {
								if r == '*' || r == '+' || r == '(' || r == ')' {
									fmt.Printf("%c", r)
								} else {
									fmt.Printf("%d", r-255)
								}
							}
							fmt.Println()
						}
						e := reduceAdditions(exp[i+1:j], debug)
						for {
							prev := e
							e = reduceAdditions(e, debug)
							if len(prev) == len(e) {
								break
							}
						}

						if debug {
							fmt.Printf("++ ")
							for _, r := range e {
								if r == '*' || r == '+' || r == '(' || r == ')' {
									fmt.Printf("%c", r)
								} else {
									fmt.Printf("%d", r-255)
								}
							}
							fmt.Println()
						}

						e = reduceMultiplications(e, debug)
						for {
							prev := e
							if len(prev) == len(e) {
								break
							}
						}
						if debug {
							fmt.Printf("** ")
							for _, r := range e {
								if r == '*' || r == '+' || r == '(' || r == ')' {
									fmt.Printf("%c", r)
								} else {
									fmt.Printf("%d", r-255)
								}
							}
							fmt.Println()
						}
						if len(e) == 1 {
							newExp = append(newExp, e...)
						} else {
							newExp = append(newExp, '(')
							newExp = append(newExp, e...)
							newExp = append(newExp, ')')
						}

						i = j
						break
					}
				}
			}
		case '*':
			newExp = append(newExp, exp[i])
		case '+':
			if exp[i-1] >= 255 && exp[i+1] >= 255 {
				if debug {
					fmt.Printf("add %d %d\n", newExp[len(newExp)-1]-255, exp[i+1]-255)
				}
				newExp[len(newExp)-1] = ((newExp[len(newExp)-1] - 255) + (exp[i+1] - 255)) + 255
				i++
			} else {
				newExp = append(newExp, exp[i])
			}
		default:
			newExp = append(newExp, exp[i])
		}
	}

	return newExp
}

func reduceMultiplications(exp []int, debug bool) []int {
	newExp := make([]int, 0, len(exp))

	for i := 0; i < len(exp); i++ {
		switch exp[i] {
		case '*':
			if exp[i-1] >= 255 && exp[i+1] >= 255 {
				newExp[len(newExp)-1] = ((newExp[len(newExp)-1] - 255) * (exp[i+1] - 255)) + 255
				i++
			} else {
				newExp = append(newExp, exp[i])
			}
		default:
			newExp = append(newExp, exp[i])
		}
	}

	return newExp
}

func reduce2(exp []int, debug bool) int {
	exp = reduceAdditions(exp, debug)
	for {
		prev := exp
		exp = reduceAdditions(exp, debug)
		if debug {
			fmt.Printf("additions ")
			for _, r := range exp {
				if r == '*' || r == '+' || r == '(' || r == ')' {
					fmt.Printf("%c", r)
				} else {
					fmt.Printf("%d", r-255)
				}
			}
			fmt.Println()
		}

		if len(prev) == len(exp) {
			break
		}
	}

	exp = reduceMultiplications(exp, debug)
	for {
		prev := exp
		exp = reduceMultiplications(exp, debug)
		if debug {
			fmt.Printf("multiplications ")
			for _, r := range exp {
				if r == '*' || r == '+' || r == '(' || r == ')' {
					fmt.Printf("%c", r)
				} else {
					fmt.Printf("%d", r-255)
				}
			}
			fmt.Println()
		}

		if len(prev) == len(exp) {
			break
		}
	}

	return exp[0] - 255
}

func prepareInput(str string, fudge bool) []rune {
	line := []rune(strings.ReplaceAll(str, " ", ""))
	for i, r := range line {
		if r != '(' && r != ')' && r != '*' && r != '+' {
			if fudge {
				line[i] = 255 + r - '0'
			} else {
				line[i] = r - '0'
			}
		}
	}
	return line
}

// Task1 ...
func Task1(input [][]rune) int {
	var result int
	for i := range input {
		result += reduce(input[i], false)
	}
	return result
}

// Task2 ...
func Task2(input [][]rune) int {
	var result int
	for i := range input {
		in := make([]int, len(input[i]))
		for i, j := range input[i] {
			in[i] = int(j)
		}
		r := reduce2(in, false)
		result += r
	}
	return result
}

func main() {
	file, _ := os.Open("input.txt")
	scanner := bufio.NewScanner(file)

	input := make([]string, 0, 100)
	for scanner.Scan() {
		input = append(input, scanner.Text())
	}
	file.Close()

	in := make([][]rune, len(input))
	for i, line := range input {
		in[i] = prepareInput(line, false)
	}
	result := Task1(in)
	fmt.Printf("Task 1: %d\n", result)

	in = make([][]rune, len(input))
	for i, line := range input {
		in[i] = prepareInput(line, true)
	}
	result = Task2(in)
	fmt.Printf("Task 2: %d\n", result)
}