package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

func main() {
	inp := StdinToStringSlice()
	fmt.Printf("# Part 1\nIn Range of Strongest: %d\n", StrongestReachable(NewBots(inp)))
	// Part 2: 94270682 is too low
	// 				 94481123 is too low
	fmt.Printf("# Part 2\nClosest Success: %d\n", ClosestSuccess(NewBots(inp)))
}

func StrongestReachable(bots Bots) int {
	var largestRadius, count int
	var largestPos Coordinate
	for c, rs := range bots {
		for _, r := range rs {
			if r > largestRadius {
				largestPos = c
				largestRadius = r
			}
		}
	}
	for c, rs := range bots {
		if largestPos.Distance(c) <= largestRadius {
			count += len(rs)
		}
	}
	return count
}

func ClosestSuccess(bots Bots) int {
	var cur, topLeft, bottomRight Coordinate
	zoom := 1 << (strconv.IntSize - 2)

	for {
		zoomedBots := make(Bots)
		best := struct {
			pos   Coordinate
			count int
		}{}

		for c, rs := range bots {
			for _, r := range rs {
				zc := Coordinate{c.X / zoom, c.Y / zoom, c.Z / zoom}
				zoomedBots[zc] = append(zoomedBots[zc], r/zoom)
			}
		}

		for cur.X = topLeft.X; cur.X <= bottomRight.X; cur.X++ {
			for cur.Y = topLeft.Y; cur.Y <= bottomRight.Y; cur.Y++ {
				for cur.Z = topLeft.Z; cur.Z <= bottomRight.Z; cur.Z++ {
					c := zoomedBots.HaveInRange(cur)

					// skip less bots
					if c < best.count {
						continue
					}
					// skip same amount of bots but Distance from Zero is the same or more
					if c == best.count && Zero.Distance(cur) >= Zero.Distance(best.pos) {
						continue
					}
					// more bots or same and closer to Zero
					best.pos, best.count = cur, c
				}
			}
		}

		// zoom in
		topLeft.X, topLeft.Y, topLeft.Z = (best.pos.X-1)<<1, (best.pos.Y-1)<<1, (best.pos.Z-1)<<1
		bottomRight.X, bottomRight.Y, bottomRight.Z = (best.pos.X+1)<<1, (best.pos.Y+1)<<1, (best.pos.Z+1)<<1
		zoom >>= 1

		if zoom == 0 {
			return Zero.Distance(best.pos)
		}
	}
}

func StdinToStringSlice() []string {
	var input []string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		input = append(input, scanner.Text())
	}
	return input
}
