package main

import (
	"fmt"
	"strings"

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

func main() {
	inp := helpers.StdinToStringSlice()
	allObjects = make(map[string]*Object)
	for _, v := range inp {
		pts := strings.Split(v, ")")
		var obj1, obj2 *Object
		var ok bool
		if obj1, ok = allObjects[pts[0]]; !ok {
			obj1 = NewObject(pts[0])
			allObjects[pts[0]] = obj1
		}
		if obj2, ok = allObjects[pts[1]]; !ok {
			obj2 = NewObject(pts[1])
			allObjects[pts[1]] = obj2
			obj1.addMoon(pts[1])
		}
		obj2.setOrbiting(pts[0])
	}
	fmt.Println(len(allObjects), "total objects")
	part1()
	part2()
}

func part1() {
	var ret int
	for _, v := range allObjects {
		ret += v.countOrbits()
	}
	fmt.Println("Part 1:", ret)
}

func part2() {
	if _, ok := allObjects["YOU"]; !ok {
		panic("Invalid input for part 2")
	}
	if _, ok := allObjects["SAN"]; !ok {
		panic("Invalid input for part 2")
	}
	you := allObjects["YOU"]
	youOrbits := you.getOrbitsList()
	san := allObjects["SAN"]
	sanOrbits := san.getOrbitsList()
	min := -1
	var minShared *Object
	for _, yv := range youOrbits {
		for _, sv := range sanOrbits {
			if yv == sv {
				ySteps := you.stepsTo(yv.Name)
				sSteps := san.stepsTo(sv.Name)
				if min == -1 || ySteps+sSteps < min {
					min = ySteps + sSteps
					minShared = yv
				}
			}
		}
	}
	if minShared == nil {
		panic("No shared object found!")
	}
	fmt.Println("Closest Ancestor: ", minShared.Name)
	fmt.Println("Steps: ", min)
}

var allObjects map[string]*Object

type Object struct {
	Name   string
	Moons  []*Object
	Orbits *Object
}

func NewObject(nm string) *Object {
	return &Object{Name: nm}
}

func (o *Object) getOrbitsList() []*Object {
	var ret []*Object
	if o.Orbits == nil {
		return ret
	}
	ret = append(ret, o.Orbits)
	return append(ret, o.Orbits.getOrbitsList()...)
}

func (o *Object) stepsTo(nm string) int {
	// First check if this is it
	if o.Name == nm {
		// If so, remove the last transfer
		return -1
	}
	// Now check if it's a descendant of this
	if ret := o.hasDesc(nm); ret != nil {
		return ret.stepsTo(nm) + 1
	}
	// Ok, we must have to go up another level
	if o.Orbits == nil {
		fmt.Println(o.Name)
		panic("Nothing else to check!")
	}
	return o.Orbits.stepsTo(nm) + 1
}

func (o *Object) OrbitsAround(nm string) bool {
	if o.Orbits == nil {
		return false
	}
	if o.Orbits.Name == nm {
		return true
	}
	return o.Orbits.OrbitsAround(nm)
}

// hasDesc checks all moons to see if one is or has the requested moon
// and returns the object that does
func (o *Object) hasDesc(nm string) *Object {
	for _, v := range o.Moons {
		if v.Name == nm {
			return v
		}
		if d := v.hasDesc(nm); d != nil {
			return d
		}
	}
	return nil
}

func (o *Object) countOrbits() int {
	if o.Name == "COM" {
		return 0
	}
	return o.Orbits.countOrbits() + 1
}

func (o *Object) setOrbiting(nm string) {
	if v, ok := allObjects[nm]; !ok {
		panic(fmt.Sprintf("Object not found (%s)", nm))
	} else {
		o.Orbits = v
	}
}

func (o *Object) addMoon(nm string) {
	if v, ok := allObjects[nm]; !ok {
		panic(fmt.Sprintf("Object not found (%s)", nm))
	} else {
		for k := range o.Moons {
			if o.Moons[k].Name == nm {
				return
			}
		}
		o.Moons = append(o.Moons, v)
	}
}
