package main

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

const (
	gridX = 150
	gridY = 150
)

var gridSize = gridX * gridY

type Cart struct {
	X, Y     int
	Dir      byte
	LastTurn int
	Dead     bool
}

func (c *Cart) Turn() {
	switch c.LastTurn {
	case 0:
		switch c.Dir {
		case '>':
			c.Dir = '^'
		case '<':
			c.Dir = 'v'
		case '^':
			c.Dir = '<'
		case 'v':
			c.Dir = '>'
		}
		c.LastTurn++
	case 1:
		c.LastTurn++
	case 2:
		switch c.Dir {
		case '>':
			c.Dir = 'v'
		case '<':
			c.Dir = '^'
		case '^':
			c.Dir = '>'
		case 'v':
			c.Dir = '<'
		}
		c.LastTurn = 0
	}
}

func (c *Cart) Move(b byte) {
	switch b {
	case '\\':
		switch c.Dir {
		case '>':
			c.Dir = 'v'
		case '<':
			c.Dir = '^'
		case '^':
			c.Dir = '<'
		case 'v':
			c.Dir = '>'
		}
	case '/':
		switch c.Dir {
		case '>':
			c.Dir = '^'
		case '<':
			c.Dir = 'v'
		case '^':
			c.Dir = '>'
		case 'v':
			c.Dir = '<'
		}
	case '+':
		c.Turn()
	}

	switch c.Dir {
	case '>':
		c.X++
	case '<':
		c.X--
	case '^':
		c.Y--
	case 'v':
		c.Y++
	}
}

type ByCoord []Cart

func (b ByCoord) Len() int {
	return len(b)
}
func (b ByCoord) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}
func (b ByCoord) Less(i, j int) bool {
	ia := (b[i].Y * gridX) + b[i].X
	ib := (b[j].Y * gridX) + b[j].X
	return ia < ib
}

type Collision struct {
	Ca, Cb int
	X, Y   int
}

type Puzzle struct {
	Playfield []byte
	Carts     []Cart
}

func (p *Puzzle) Tick() []Collision {
	sort.Sort(ByCoord(p.Carts))
	collisions := make([]Collision, 0, 2)
	for t, c := range p.Carts {
		if c.Dead {
			continue
		}

		b := p.Playfield[(c.Y*gridX)+c.X]
		p.Carts[t].Move(b)

		for u, cb := range p.Carts {
			if cb.Dead || t == u {
				continue
			}

			if p.Carts[t].X == cb.X && p.Carts[t].Y == cb.Y {
				p.Carts[t].Dead = true
				p.Carts[u].Dead = true
				collisions = append(collisions, Collision{
					Ca: t,
					Cb: u,
					X:  p.Carts[t].X,
					Y:  p.Carts[t].Y,
				})
				break
			}
		}
	}
	return collisions
}

func (p *Puzzle) Solution1(pretty bool) (int, int) {
	grid := make([]byte, gridSize)

	collisions := make([]Collision, 0, 10)
	for {
		count := 0
		for _, c := range p.Carts {
			if c.Dead {
				count++
			}
		}
		if count >= len(p.Carts)-1 {
			break
		}

		c := p.Tick()
		if len(c) > 0 {
			collisions = append(collisions, c...)
		}

		if pretty {
			copy(grid, p.Playfield)
			for _, c := range p.Carts {
				if c.Dead {
					continue
				}

				i := (c.Y * gridX) + c.X
				grid[i] = c.Dir
			}

			for y := 0; y < gridY; y++ {
				fmt.Println(string(grid[y*gridX : (y*gridX)+gridX]))
			}

			time.Sleep(100 * time.Millisecond)
		}
	}
	copy(grid, p.Playfield)
	for _, c := range collisions {
		i := (c.Y * gridX) + c.X
		grid[i] = 'X'
	}
	for _, c := range p.Carts {
		if c.Dead {
			continue
		}

		i := (c.Y * gridX) + c.X
		grid[i] = c.Dir
	}

	for y := 0; y < gridY; y++ {
		fmt.Println(string(grid[y*gridX : (y*gridX)+gridX]))
	}

	var aX, aY int
	for _, c := range p.Carts {
		if c.Dead {
			fmt.Println("dead", c.X, c.Y)
			continue
		}
		fmt.Println("alive", c.X, c.Y)
		aX = c.X
		aY = c.Y
	}
	return aX, aY
}

func main() {
	input, err := os.Open("input.txt")
	if err != nil {
		panic(err.Error())
	}
	defer input.Close()
	scanner := bufio.NewScanner(input)

	p := Puzzle{
		Playfield: make([]byte, gridSize),
		Carts:     make([]Cart, 0, 2),
	}
	for t := range p.Playfield {
		p.Playfield[t] = ' '
	}
	lines := 0
	for scanner.Scan() {
		line := []byte(scanner.Text())
		for x, c := range line {
			i := (lines * gridX) + x

			if c == '>' || c == 'v' || c == '<' || c == '^' {
				p.Carts = append(p.Carts, Cart{
					X:        x,
					Y:        lines,
					Dir:      c,
					LastTurn: 0,
				})
				// Seems there's never a train started on an intersection
				if c == '>' || c == '<' {
					c = '-'
				} else {
					c = '|'
				}
			}

			p.Playfield[i] = c
		}
		lines++
	}
	if err := scanner.Err(); err != nil {
		panic(err.Error())
	}

	/*
		palette := []color.Color{
			color.RGBA{0, 0, 0, 0xff},
			color.RGBA{0xff, 0xff, 0xff, 0xff},
		}
		size := image.Rect(0, 0, 128, 128)
		images := make([]*image.Paletted, 10)
		delays := make([]int, 10)
		for t := 0; t < 10; t++ {
			images[t] = image.NewPaletted(size, palette)

			for y := 0; y < 128; y++ {
				for x := 0; x < 128; x++ {
					c := byte((x + t) ^ (y + t))
					images[t].Set(x, y, color.RGBA{
						c, c, c, 0xff,
					})
				}
			}
		}

		f, err := os.OpenFile("out.gif", os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			panic(err)
		}
		defer f.Close()
		gif.EncodeAll(f, &gif.GIF{
			Image: images,
			Delay: delays,
		})
	*/

	fmt.Println(p.Solution1(false))
}