package main

import (
	"fmt"

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

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

	part1(inp)
	part2(inp)
}

func part1(inp []string) {
	s := NewSystem(inp)
	for i := 1; i <= 1000; i++ {
		s.Step()
	}
	fmt.Println("System Energy:", s.GetTotalEnergy())
}

func part2(inp []string) {
	s := NewSystem(inp)
	sInit := NewSystem(inp)
	// Find how long it takes for all planets to repeat
	done := make([]bool, 3)
	count := make([]int, 3)
	for !AllTrue(done...) {
		s.Step()
		for i := 0; i < len(done); i++ {
			if !done[i] {
				equal := true
				for pi := range s.moons {
					switch i {
					case 0:
						if s.moons[pi].posX != sInit.moons[pi].posX || s.moons[pi].velX != sInit.moons[pi].velX {
							equal = false
						}
					case 1:
						if s.moons[pi].posY != sInit.moons[pi].posY || s.moons[pi].velY != sInit.moons[pi].velY {
							equal = false
						}
					case 2:
						if s.moons[pi].posZ != sInit.moons[pi].posZ || s.moons[pi].velZ != sInit.moons[pi].velZ {
							equal = false
						}
					}
				}
				if equal {
					done[i] = true
				} else {
					count[i]++
				}
			}
		}
	}
	fmt.Println(count[0], count[1], count[2])
	fmt.Println(helpers.Lcm(count[0]+1, count[1]+1, count[2]+1))
}

func AllTrue(done ...bool) bool {
	for _, v := range done {
		if !v {
			return false
		}
	}
	return true
}

type System struct {
	moons []*Moon
}

func NewSystem(inp []string) *System {
	s := &System{}
	for k := range inp {
		var x, y, z int
		_, err := fmt.Sscanf(inp[k], "<x=%d, y=%d, z=%d>", &x, &y, &z)
		if err != nil {
			panic(err)
		}
		s.AddMoon(NewMoon(x, y, z))
	}
	return s
}

func (s *System) AddMoon(m *Moon) {
	s.moons = append(s.moons, m)
}

func (s *System) Step() {
	for i := range s.moons {
		for j := i + 1; j < len(s.moons); j++ {
			s.moons[i].Interact(s.moons[j])
		}
	}
	for i := range s.moons {
		s.moons[i].Step()
	}
}

func (s *System) GetTotalEnergy() int {
	var ret int
	for v := range s.moons {
		ret = ret + (s.moons[v].GetPotentialEnergy() * s.moons[v].GetKineticEnergy())
	}
	return ret
}

func (s System) String() string {
	var ret string
	for k := range s.moons {
		ret = ret + s.moons[k].String() + "\n"
	}
	return ret
}

type Moon struct {
	posX, posY, posZ int
	velX, velY, velZ int
}

func NewMoon(x, y, z int) *Moon {
	return &Moon{
		posX: x, posY: y, posZ: z,
	}
}

func (m *Moon) GetPotentialEnergy() int {
	return helpers.AbsInt(m.posX) + helpers.AbsInt(m.posY) + helpers.AbsInt(m.posZ)
}

func (m *Moon) GetKineticEnergy() int {
	return helpers.AbsInt(m.velX) + helpers.AbsInt(m.velY) + helpers.AbsInt(m.velZ)
}

func (m *Moon) Interact(n *Moon) {
	if n.posX > m.posX {
		n.velX--
		m.velX++
	} else if n.posX < m.posX {
		n.velX++
		m.velX--
	}
	if n.posY > m.posY {
		n.velY--
		m.velY++
	} else if n.posY < m.posY {
		n.velY++
		m.velY--
	}
	if n.posZ > m.posZ {
		n.velZ--
		m.velZ++
	} else if n.posZ < m.posZ {
		n.velZ++
		m.velZ--
	}
}

func (m *Moon) Step() {
	m.posX += m.velX
	m.posY += m.velY
	m.posZ += m.velZ
}

func (m *Moon) PosEquals(n *Moon) (bool, bool, bool) {
	return (m.posX == n.posX), (m.posY == n.posY), (m.posZ == n.posZ)
}

func (m Moon) String() string {
	return fmt.Sprintf("pos=<x=%3d, y=%3d, z=%3d>, vel=<x=%3d, y=%3d, z=%3d>",
		m.posX, m.posY, m.posZ,
		m.velX, m.velY, m.velZ,
	)
}
