package main

import (
	"fmt"
	"os"
	"strings"
	"time"

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

var sleepTime = (time.Second / 5)

var minx, miny, maxx, maxy int

var part = -1
var knots = 2

func main() {
	inp := h.StdinToStringSlice()
	var watch bool
	var partSet bool
	if len(os.Args) > 1 {
		for _, arg := range os.Args[1:] {
			if strings.HasPrefix(arg, "-w") {
				watch = true
				if strings.Contains(arg, "=") {
					pts := strings.Split(arg, "=")
					sleepTime = (time.Second / time.Duration(h.Atoi(pts[1])))
				}
			} else if strings.HasPrefix(arg, "-k") {
				if strings.Contains(arg, "=") {
					pts := strings.Split(arg, "=")
					knots = h.Atoi(pts[1])
				}
			} else if strings.HasPrefix(arg, "-p") {
				partSet = true
				if strings.Contains(arg, "=") {
					pts := strings.Split(arg, "=")
					part = h.Atoi(pts[1])
				}
			}
		}
	}
	if part == 1 {
		knots = 2
	} else if part == 2 {
		knots = 10
	}
	if !watch && !partSet {
		// Run both parts
		fmt.Println("# Part 1")
		simulate(inp, 2, watch)
		fmt.Println()
		fmt.Println("# Part 2")
		simulate(inp, 10, watch)
	} else {
		if part > 0 && part <= 2 {
			fmt.Println("# Part", part)
		} else {
			fmt.Println("# Simulation with", knots, "knots")
		}
		simulate(inp, knots, watch)
	}
}

func buildInstructions(inp []string) []byte {
	var inst []byte
	for i := range inp {
		dir, count := inp[i][0], h.Atoi(inp[i][2:])
		for j := 0; j < count; j++ {
			inst = append(inst, dir)
		}
	}
	return inst
}

func simulate(inp []string, knotCount int, watch bool) {
	if watch {
		fmt.Print(h.CLEAR_SCREEN)
	}
	visited := make(map[h.Coordinate]bool)
	inst := buildInstructions(inp)
	var knots []h.Coordinate
	for i := 0; i < knotCount; i++ {
		knots = append(knots, h.Coordinate{X: 0, Y: 0})
	}
	if watch {
		printVisits(visited, knots)
	}
	for i, dir := range inst {
		moveHead(dir, knots)
		visited[knots[len(knots)-1]] = true
		if watch {
			time.Sleep(sleepTime)
			fmt.Print(h.CLEAR_SCREEN)
			fmt.Printf("# Knots %d (%d/%d)\n", knots, i, len(inst))
			printVisits(visited, knots)
		}
	}
	if watch {
		time.Sleep(sleepTime)
		fmt.Print(h.CLEAR_SCREEN)
	}
	if watch {
		printVisits(visited, knots)
	}
	fmt.Printf("Tail visited %d positions\n", len(visited))
}

func moveHead(dir byte, knots []h.Coordinate) {
	prevKnots := make([]h.Coordinate, len(knots))
	copy(prevKnots, knots)
	switch dir {
	case 'U':
		knots[0] = knots[0].North()
	case 'R':
		knots[0] = knots[0].East()
	case 'D':
		knots[0] = knots[0].South()
	case 'L':
		knots[0] = knots[0].West()
	}
	if knots[0].X < minx {
		minx = knots[0].X
	}
	if knots[0].X > maxx {
		maxx = knots[0].X
	}
	if knots[0].Y < miny {
		miny = knots[0].Y
	}
	if knots[0].Y > maxy {
		maxy = knots[0].Y
	}
	for i := 1; i < len(knots); i++ {
		// Go through all knots and check if any need to move
		if !knots[i].Equals(knots[i-1]) && !knots[i].Adjacent(knots[i-1]) {
			if knots[i].X == knots[i-1].X {
				// Same column
				if knots[i].Y < knots[i-1].Y {
					knots[i].Y++
				} else {
					knots[i].Y--
				}
			} else if knots[i].Y == knots[i-1].Y {
				// Save row
				if knots[i].X < knots[i-1].X {
					knots[i].X++
				} else {
					knots[i].X--
				}
			} else {
				// Not in the same row or column
				if knots[i].X < knots[i-1].X {
					knots[i].X++
				} else {
					knots[i].X--
				}
				if knots[i].Y < knots[i-1].Y {
					knots[i].Y++
				} else {
					knots[i].Y--
				}
			}
		} else {
			// If one knot didn't need to move, none behind it will
			break
		}
	}
}

func printVisits(m map[h.Coordinate]bool, knots []h.Coordinate) {
	for y := miny; y <= maxy; y++ {
		for x := minx; x <= maxx; x++ {
			var isKnot bool
			for i := range knots {
				if knots[i].X == x && knots[i].Y == y {
					isKnot = true
					var bt byte
					if i == 0 {
						bt = 'H'
					} else {
						bt = '0' + byte(i)
					}
					fmt.Print(string(bt))
					break
				}
			}
			if !isKnot {
				if v, ok := m[h.Coordinate{X: x, Y: y}]; ok && v {
					fmt.Print("#")
				} else {
					fmt.Print(".")
				}
			}
		}
		fmt.Println()
	}
	fmt.Printf("Bridge: %s\n", knots)
	fmt.Println()
}