package main

import (
	"AoC2020/helper"
	"fmt"
	"os"
)

func main() {
	args := os.Args[1:]
	input, err := helper.GetInput(args[0])
	if err != nil {
		fmt.Println(err)
	}
	fields := setFields(input)
	printFields(fields)
	fmt.Println(countOccupiedSeat(runMachine(fields, getOcuupiedNeighbours, 4)))
	fmt.Println(countOccupiedSeat(runMachine(fields, getOccupiedVisibleSeats, 5)))
}

func runMachine(field [][]rune, neighbourFunction func(int, int, [][]rune) int, maxOccupied int) [][]rune {
	changed := true
	for changed {
		newField := run(field, neighbourFunction, maxOccupied)
		changed = !fieldsEqual(newField, field)
		field = newField
	}
	return field
}

func run(fields [][]rune, neighbourFunction func(int, int, [][]rune) int, maxOccupied int) [][]rune {
	var newField = make([][]rune, len(fields))
	for i, _ := range newField {
		newField[i] = make([]rune, len(fields[0]))
		copy(newField[i], fields[i])
	}
	for i := 0; i < len(fields[0]); i++ {
		for j := 0; j < len(fields); j++ {
			if fields[j][i] == 'L' && neighbourFunction(i, j, fields) == 0 {
				newField[j][i] = '#'
			}
			if fields[j][i] == '#' && neighbourFunction(i, j, fields) >= maxOccupied {
				newField[j][i] = 'L'
			}
		}
	}
	return newField
}

func fieldsEqual(field1 [][]rune, field2 [][]rune) bool {
	for i := 0; i < len(field1[0]); i++ {
		for j := 0; j < len(field1); j++ {
			if field1[j][i] != field2[j][i] {
				return false
			}
		}
	}
	return true
}

func getOcuupiedNeighbours(x int, y int, field [][]rune) int {
	neighbours := [][]int{{x - 1, y - 1}, {x, y - 1}, {x + 1, y - 1}, {x + 1, y}, {x + 1, y + 1}, {x, y + 1}, {x - 1, y + 1}, {x - 1, y}}
	count := 0
	for _, val := range neighbours {
		if isOccupied(val[0], val[1], field) {
			count++
		}
	}
	return count
}

func getOccupiedVisibleSeats(x int, y int, field [][]rune) int {
	directions := [][]int{{-1, -1}, {0,-1}, {1,-1}, {+1, 0}, {1,1}, {0,1}, {-1,1}, {-1, 0}}
	count := 0
	for _, val := range directions {
		if getVisibleSeat(x, y, val, field) == '#' {
			count++
		}
	}
	return count
}

func getVisibleSeat(x int, y int, direction []int, field [][]rune) rune {
	px := x + direction[0]
	py := y + direction[1]
	for py < len(field) && py >= 0 && px < len(field[0]) && px >= 0 {
		if field[py][px] != '.' {
			return field[py][px]
		}
		px += direction[0]
		py += direction[1]
	}
	return '.'
}

func isOccupied(x int, y int, field [][]rune) bool {
	if y > len(field)-1 || y < 0 {
		return false
	}
	if x > len(field[0])-1 || x < 0 {
		return false
	}
	if field[y][x] == 35 {
		return true
	}
	return false
}

func printFields(fields [][]rune) {
	for _, val := range fields {
		fmt.Println(string(val))
	}
	fmt.Println()
}

func setFields(input []string) [][]rune {
	var field = make([][]rune, len(input))
	for i, val := range input {
		field[i] = stringToRuneArr(val)
	}
	return field
}

func stringToRuneArr(input string) []rune {
	var arr = make([]rune, len(input))
	for i, val := range input {
		arr[i] = val
	}
	return arr
}

func countOccupiedSeat(field [][]rune) int {
	count := 0
	for i := 0; i < len(field[0]); i++ {
		for j := 0; j < len(field); j++ {
			if field[j][i] == '#' {
				count++
			}
		}
	}
	return count
}
