package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
)

const (
	DIR_N = -1i
	DIR_E = 1
	DIR_S = 1i
	DIR_W = -1

	TURN_L = -1i
	TURN_S = 1
	TURN_R = 1i
)

var width int
var input []byte
var carts []*complex64
var cartMap map[complex64]*Cart

func main() {
	stdinToByteSlice()
	setupCarts()
	//part1()
	part2()
}

func part1() {
	var crashed bool
	for !crashed {
		sort.Sort(ByPos(carts))
		for _, c := range carts {
			if !cartMap[*c].tick() {
				return
			}
		}
	}
}

var ticks int

func part2() {
	for len(cartMap) > 1 {
		ticks++
		sort.Sort(ByPos(carts))
		for _, c := range carts {
			if *c != 0 {
				cartMap[*c].tick()
			}
		}
	}
	for pos := range cartMap {
		fmt.Printf("🔵: %0.0f,%0.0f\n", real(pos), imag(pos))
	}
	fmt.Println("Ticks: ", ticks)
}

type Cart struct {
	pos      complex64
	dir      complex64
	nextTurn complex64
}

// Tick the cart, return if it's still alive
func (c *Cart) tick() bool {
	// Remove this cart from it's old position
	// and figure out it's new one
	delete(cartMap, c.pos)
	c.pos += c.dir
	if cart, ok := cartMap[c.pos]; ok {
		fmt.Printf("❌: %0.0f,%0.0f (%d ticks)\n", real(c.pos), imag(c.pos), ticks)
		// Remove the cart we crashed into from the map
		delete(cartMap, c.pos)
		// And clear the cart's pos, we can ignore them now
		cart.pos, c.pos = 0, 0
		return false
	}
	// No crash, figure out the new orientation of the cart
	cartMap[c.pos] = c
	if getByte(c.pos) == '+' {
		c.dir *= c.nextTurn
		switch c.nextTurn {
		case TURN_L:
			c.nextTurn = TURN_S
		case TURN_S:
			c.nextTurn = TURN_R
		case TURN_R:
			c.nextTurn = TURN_L
		}
	} else if getByte(c.pos) == '/' {
		// Turn counter-clockwise
		c.dir = complex(-imag(c.dir), -real(c.dir))
	} else if getByte(c.pos) == '\\' {
		// Turn clockwise
		c.dir = complex(imag(c.dir), real(c.dir))
	}
	return true
}

// We have to sort the carts on each tick by y,x position
// y is the imaginary part, x is the real part
type ByPos []*complex64

func (c ByPos) Len() int      { return len(c) }
func (c ByPos) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ByPos) Less(i, j int) bool {
	return imag(*c[i]) < imag(*c[j]) ||
		(imag(*c[i]) == imag(*c[j]) && real(*c[i]) < real(*c[j]))
}

// getByte pulls a byte from the given position in the input
func getByte(pos complex64) byte {
	return input[int(real(pos))+int(imag(pos))*width]
}

/**
 * Initialization Functions
 */
func setupCarts() {
	cartMap = make(map[complex64]*Cart)
	for i := 0; i < len(input); i++ {
		pos := complex(float32(i%width), float32(i/width))
		switch getByte(pos) {
		case '^':
			cartMap[pos] = &Cart{pos, DIR_N, TURN_L}
		case '>':
			cartMap[pos] = &Cart{pos, DIR_E, TURN_L}
		case 'v':
			cartMap[pos] = &Cart{pos, DIR_S, TURN_L}
		case '<':
			cartMap[pos] = &Cart{pos, DIR_W, TURN_L}
		default:
			continue // Not a cart
		}
		carts = append(carts, &cartMap[pos].pos)
	}
}

func stdinToByteSlice() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		data := scanner.Bytes()
		if width == 0 {
			width = len(data)
		}
		input = append(input, data...)
	}
}
