package main

import (
	"bufio"
	"fmt"
	"log"
	"math"
	"os"
	"strconv"
	"strings"
)

var particles map[int]*Particle
var confidenceThreshold int

func main() {
	inp := StdinToStrings()
	particles = make(map[int]*Particle)
	for i := range inp {
		particles[i] = NewParticle(inp[i], i)
	}
	confidenceThreshold = 1000
	doPart := 2
	if len(os.Args) > 1 {
		if os.Args[1] == "-1" {
			doPart = 1
		}
	}
	switch doPart {
	case 1:
		part1(inp)
	case 2:
		part2(inp)
	}
}

func part1(inp []string) {
	var ticks, confidence, last int
	cPart := -1
	for {
		cDist := 10000000000
		ticks++
		for i := range particles {
			particles[i].Tick()
			if particles[i].GetDistance() < cDist {
				cPart = i
				cDist = particles[i].GetDistance()
			}
		}
		if cPart == last {
			confidence++
		} else {
			confidence = 0
			last = cPart
		}
		if confidence >= confidenceThreshold {
			break
		}
	}
	fmt.Println("After", ticks, "the closest is", cPart)
}

func part2(inp []string) {
	var ticks, lastNum, confidence int
	for {
		ticks++
		for i := range particles {
			particles[i].Tick()
		}
		var collisions []int
		for k1, v1 := range particles {
			for k2, v2 := range particles {
				if k1 == k2 {
					continue
				}
				if v1.Collides(v2) {
					collisions = AddToSlice(collisions, k1)
					collisions = AddToSlice(collisions, k2)
					break
				}
			}
		}
		for i := len(collisions) - 1; i >= 0; i-- {
			delete(particles, collisions[i])
		}
		if len(particles) == lastNum {
			confidence++
		} else {
			confidence = 0
			lastNum = len(particles)
		}
		if confidence >= confidenceThreshold {
			break
		}
	}
	fmt.Println("After", ticks, "there are", len(particles), "particles left")
}

func AddToSlice(sl []int, v int) []int {
	for i := range sl {
		if sl[i] == v {
			return sl
		}
	}
	sl = append(sl, v)
	return sl
}

type Coordinate struct {
	X, Y, Z int
}

func NewCoordinate(inp string) Coordinate {
	inp = inp[1 : len(inp)-1]
	pts := strings.Split(inp, ",")
	px, py, pz := Atoi(pts[0]), Atoi(pts[1]), Atoi(pts[2])
	return Coordinate{X: px, Y: py, Z: pz}
}

func (c *Coordinate) toString() string {
	return fmt.Sprintf("<%d,%d,%d>", c.X, c.Y, c.Z)
}

type Particle struct {
	Index        int
	Position     Coordinate
	Velocity     Coordinate
	Acceleration Coordinate

	LastPosition Coordinate
}

func NewParticle(inp string, idx int) *Particle {
	ret := new(Particle)
	ret.Index = idx
	pts := strings.Split(inp, " ")
	for i := range pts {
		keyVal := strings.Split(pts[i], "=")
		if keyVal[1][len(keyVal[1])-1] == ',' {
			keyVal[1] = keyVal[1][:len(keyVal[1])-1]
		}
		switch keyVal[0] {
		case "p":
			ret.Position = NewCoordinate(keyVal[1])
		case "v":
			ret.Velocity = NewCoordinate(keyVal[1])
		case "a":
			ret.Acceleration = NewCoordinate(keyVal[1])
		}
	}
	ret.LastPosition = NewCoordinate(ret.Position.toString())
	return ret
}

func (p *Particle) Collides(tst *Particle) bool {
	if p.Position.X == tst.Position.X &&
		p.Position.Y == tst.Position.Y &&
		p.Position.Z == tst.Position.Z {
		return true
	}
	return false
}

func (p *Particle) String() string {
	return fmt.Sprintf(
		"p=<%d,%d,%d>, v=<%d,%d,%d>, a=<%d,%d,%d>",
		p.Position.X, p.Position.Y, p.Position.Z,
		p.Velocity.X, p.Velocity.Y, p.Velocity.Z,
		p.Acceleration.X, p.Acceleration.Y, p.Acceleration.Z,
	)
}

func (p *Particle) Tick() {
	p.LastPosition = NewCoordinate(p.Position.toString())
	p.Velocity.X += p.Acceleration.X
	p.Velocity.Y += p.Acceleration.Y
	p.Velocity.Z += p.Acceleration.Z
	p.Position.X += p.Velocity.X
	p.Position.Y += p.Velocity.Y
	p.Position.Z += p.Velocity.Z
}

func (p *Particle) IsDone() bool {
	return p.GetDistance() == p.GetLastDistance()
}

func (p *Particle) GetDistance() int {
	return GetAbs(p.Position.X) + GetAbs(p.Position.Y) + GetAbs(p.Position.Z)
}

func (p *Particle) GetLastDistance() int {
	return int(math.Abs(float64(p.LastPosition.X + p.LastPosition.Y + p.LastPosition.Z)))
}

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

func GetAbs(v int) int {
	return int(math.Abs(float64(v)))
}

func Atoi(i string) int {
	var ret int
	var err error
	if ret, err = strconv.Atoi(i); err != nil {
		log.Fatal("Invalid Atoi")
	}
	return ret
}
