package main

import (
	"fmt"
	"math"

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

func main() {
	inp := h.StdinToStringSlice()

	var build *SNum
	for i := range inp {
		wrk := ParseSNum(inp[i])
		if build == nil {
			build = wrk
		} else {
			build = build.add(wrk)
		}
	}
	fmt.Println()
	fmt.Println("# Part 1")
	fmt.Println("Magnitude:", build.magnitude())
	fmt.Println()
	fmt.Println("# Part 2")
	highest := h.MIN_INT
	for i := 0; i < len(inp)-1; i++ {
		for j := i + 1; j < len(inp); j++ {
			w1 := ParseSNum(inp[i]).add(ParseSNum(inp[j]))
			w2 := ParseSNum(inp[j]).add(ParseSNum(inp[i]))

			m1, m2 := w1.magnitude(), w2.magnitude()
			if m1 >= m2 && m1 > highest {
				highest = m1
			} else if m2 > highest {
				highest = m2
			}
		}
	}
	fmt.Println("Highest magnitude from 2:", highest)
	//3225 is too low
}

type SNum struct {
	parent, left, right *SNum
	regular             bool
	value               int
}

func ParseSNum(inp string) *SNum {
	s, _ := rParseSNum(inp)
	return s
}

// ParseSNum returns an SNum and what's left of inp
// after it was produced
func rParseSNum(inp string) (*SNum, string) {
	// inp should always start with either a number or a '['
	var rest string
	s := SNum{}
	if inp[0] == '[' {
		s.regular = false
		s.left, rest = rParseSNum(inp[1:])
		s.right, rest = rParseSNum(rest[1:])
		s.left.parent = &s
		s.right.parent = &s
		return &s, rest[1:]
	} else {
		s.regular = true
		var num []byte
		for i := range inp {
			if inp[i] >= '0' && inp[i] <= '9' {
				num = append(num, inp[i])
			} else {
				s.value = h.Atoi(string(num))
				return &s, inp[i:]
			}
		}
	}
	return &s, rest
}

// When exploding or splitting, we need to find the leftmost pair that matches the condition
func (s *SNum) needsExplode(depth int) *SNum {
	if s.regular {
		return nil
	}
	if depth >= 4 {
		if !s.left.regular {
			return s.left.needsExplode(depth + 1)
		} else if !s.right.regular {
			return s.right.needsExplode(depth + 1)
		} else {
			return s
		}
	}

	if tst := s.left.needsExplode(depth + 1); tst != nil {
		return tst
	} else if tst := s.right.needsExplode(depth + 1); tst != nil {
		return tst
	}
	return nil
}

func (s *SNum) explode() {
	if s.parent == nil {
		return
	}
	s.parent.addToRegularLeftOf(s.left.value, s)
	s.parent.addToRegularRightOf(s.right.value, s)
	// Replace s with a regular number that just equals 0
	s.regular, s.value = true, 0
	s.right, s.left = nil, nil
}

func (s *SNum) needsSplit() *SNum {
	if s == nil || (s.regular && s.value >= 10) {
		return s
	}
	if tst := s.left.needsSplit(); tst != nil {
		return tst
	} else if tst := s.right.needsSplit(); tst != nil {
		return tst
	}
	return nil
}

func (s *SNum) split() {
	if s.parent == nil {
		return
	}
	l := int(math.Floor(float64(s.value) / 2))
	r := int(math.Ceil(float64(s.value) / 2))
	n := SNum{
		parent: s.parent,
		left: &SNum{
			regular: true,
			value:   int(l),
		},
		right: &SNum{
			regular: true,
			value:   int(r),
		},
	}
	n.left.parent = &n
	n.right.parent = &n
	if s.parent.left == s {
		s.parent.left = &n
	} else if s.parent.right == s {
		s.parent.right = &n
	}
}

func (s *SNum) addToRegularLeftOf(v int, c *SNum) {
	if s == nil || v == 0 {
		return
	}
	if s.right != nil && s.right == c {
		s.left.addToRightmostRegular(v)
	} else if s.left != nil && s.left == c {
		s.parent.addToRegularLeftOf(v, s)
	}
}

func (s *SNum) addToRegularRightOf(v int, c *SNum) {
	if s == nil || v == 0 {
		return
	}
	if s.left == c {
		s.right.addToLeftmostRegular(v)
	} else if s.right == c {
		s.parent.addToRegularRightOf(v, s)
	}
}

func (s *SNum) contains(n *SNum) bool {
	return s == n || s.left.contains(n) || s.right.contains(n)
}

func (s *SNum) addToRightmostRegular(v int) {
	if s.regular {
		s.value += v
	} else if s.right != nil {
		s.right.addToRightmostRegular(v)
	}
}
func (s *SNum) addToLeftmostRegular(v int) {
	if s.regular {
		s.value += v
	} else if s.left != nil {
		s.left.addToLeftmostRegular(v)
	}
}

func (s *SNum) add(n *SNum) *SNum {
	wrk := SNum{
		left:  s,
		right: n,
	}
	s.parent = &wrk
	n.parent = &wrk

	changed := true
	for changed {
		changed = false
		if v := wrk.needsExplode(0); v != nil {
			v.explode()
			changed = true
		} else if v := wrk.needsSplit(); v != nil {
			v.split()
			changed = true
		}
	}

	return &wrk
}

func (s *SNum) magnitude() int {
	if s.regular {
		return s.value
	}
	return s.left.magnitude()*3 + s.right.magnitude()*2
}

func (s SNum) String() string {
	if s.regular {
		return fmt.Sprintf("%d", s.value)
	}
	return fmt.Sprintf("[%s,%s]", s.left, s.right)
}
