package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

var lights [][]bool

func main() {
	ticks := -1
	speed := 2
	var hideSim, lockedCorners bool
	for i := range os.Args {
		if os.Args[i] == "-help" {
			fmt.Println("Usage: day18 [-ticks=<num>] [-speed=<num>]")
			fmt.Println("\t-ticks=<num>\tSpecify how many iterations should be performed")
			fmt.Println("\t\t\t\tIf not manually set, run forever.")
			fmt.Println("\t-speed=<num>\tSpecify how many times/second we iterate")
			fmt.Println("\t-hide\t\tHide the actual simulation (for AoC)")
			fmt.Println("\t-lockcorners\t\tLock the corner lights 'on' (for AoC pt 2)")
			os.Exit(1)
		}
		if j := strings.Index(os.Args[i], "="); j > 0 {
			flagKey := os.Args[i][:j]
			flagVal := os.Args[i][j+1:]
			if flagKey == "-ticks" {
				ticks = mustAtoi(flagVal)
			} else if flagKey == "-speed" {
				speed = mustAtoi(flagVal)
			}
		}
		if os.Args[i] == "-hide" {
			hideSim = true
		}
		if os.Args[i] == "-lockcorners" {
			lockedCorners = true
		}
	}
	if hideSim && ticks == -1 {
		// Doesn't make sense to run this configuration
		fmt.Println("You asked to run indefinitely and hide the simulation...")
		fmt.Println("Refusing to needlessly consume your CPU.")
		os.Exit(1)
	}

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

	for i := range input {
		var bldRow []bool
		for j := range input[i] {
			bldRow = append(bldRow, (input[i][j] == '#'))
		}
		lights = append(lights, bldRow)
	}

	if !hideSim {
		cls()
		printLights(lights)
	}
	for i := 0; i < ticks || ticks == -1; i++ {
		if lockedCorners {
			lights = lockCorners(lights)
		}
		lights = tick(lights)
		if lockedCorners {
			lights = lockCorners(lights)
		}
		if !hideSim {
			cls()
			printLights(lights)
		}
		if !hideSim {
			// No reason to sleep if we're hiding the simulation
			time.Sleep(time.Second / time.Duration(speed))
		}
	}

	fmt.Println("After " + strconv.Itoa(ticks) + " ticks, " + strconv.Itoa(countLights(lights)) + " are on")
}

func lockCorners(c [][]bool) [][]bool {
	c[0][0] = true
	c[0][len(c[0])-1] = true
	c[len(c)-1][0] = true
	c[len(c)-1][len(c[0])-1] = true
	return c
}

func cls() {
	cmd := exec.Command("clear")
	cmd.Stdout = os.Stdout
	cmd.Run()
}

func printLights(curr [][]bool) {
	for i := range curr {
		for j := range curr {
			if curr[i][j] {
				fmt.Print("#")
			} else {
				fmt.Print(".")
			}
		}
		fmt.Println()
	}
}

func tick(curr [][]bool) [][]bool {
	var next [][]bool
	for i := range curr {
		var bldRow []bool
		for j := range curr[i] {
			num := checkNeighbors(curr, i, j)
			var newVal bool
			if curr[i][j] {
				// On proc
				if num == 2 || num == 3 {
					newVal = true
				}
			} else {
				// Off proc
				if num == 3 {
					newVal = true
				}
			}
			bldRow = append(bldRow, newVal)
		}
		next = append(next, bldRow)
	}
	return next
}

// Returns how many neighbors are on
func checkNeighbors(curr [][]bool, i, j int) int {
	var ret int
	// nw
	if isOn(curr, (i - 1), (j - 1)) {
		ret++
	}
	// n
	if isOn(curr, (i - 1), j) {
		ret++
	}
	// ne
	if isOn(curr, (i - 1), (j + 1)) {
		ret++
	}
	// w
	if isOn(curr, i, (j - 1)) {
		ret++
	}
	// e
	if isOn(curr, i, (j + 1)) {
		ret++
	}
	// sw
	if isOn(curr, (i + 1), (j - 1)) {
		ret++
	}
	// s
	if isOn(curr, (i + 1), j) {
		ret++
	}
	// se
	if isOn(curr, (i + 1), (j + 1)) {
		ret++
	}
	return ret
}

func isOn(curr [][]bool, i, j int) bool {
	if i < 0 || i >= len(curr) {
		return false
	}
	if j < 0 || j >= len(curr[i]) {
		return false
	}
	return curr[i][j]
}

func countLights(c [][]bool) int {
	var ttl int
	for i := range c {
		for j := range c[i] {
			if c[i][j] {
				ttl++
			}
		}
	}
	return ttl
}

func mustAtoi(s string) int {
	var i int
	var err error
	if i, err = strconv.Atoi(s); err != nil {
		fmt.Println("Tried to atoi " + s)
		os.Exit(1)
	}
	return i
}
