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)
	}
	dimension := readInputLayer(input,0)
	fmt.Println(len(getDirectionsPart2()))
	for i := 0; i < 6; i++ {
		dimension = run(dimension, getDirectionsPart2)
	}
}

func run(dimension map[[4]int]rune, getDirections func() [][4]int) map[[4]int]rune {
	var changedValues = make(map[[4]int]rune)
	directions := getDirections()
	visitedNeighbours := make(map[[4]int]int)
	for position, status := range dimension {
		neighbourPosition := getNeighbourPoints(position,directions)
		neighbours := getActiveNeighbours(dimension, neighbourPosition)
		if status == '#' && !(neighbours == 2 || neighbours == 3) {
			changedValues[position] = '.'
		}
		for _, position := range neighbourPosition {
			visitedNeighbours[position] ++
		}
	}
	for position, val := range visitedNeighbours {
		if val == 3 {
			changedValues[position] = '#'
		}
	}
	for position, status := range changedValues {
		//fmt.Printf("Changed Position: %v Status: %v \n",position,string(status))
		if status == '.' {
			delete(dimension, position)
		}
		if status == '#' {
			dimension[position] = status
		}
	}
	fmt.Printf("Active Points: %v \n", len(dimension))
	return dimension
}

func readInputLayer(input []string, layerLevel int) map[[4]int]rune {
	var layer = make(map[[4]int]rune)
	for y, row := range input {
		for x, val := range row {
			if val == '#' {
				layer[[4]int{y,x,layerLevel,0}] = val
			}
		}
	}
	return layer
}

func printLayer(layer int, width int, height int, dimension map[[3]int]rune) {
	for i:= 0; i < height; i++ {
		var row []rune
		for j:= 0; j < width; j++ {
			val := dimension[[3]int{i,j,layer}]
			if val == '#' {
				row = append(row,val)
			}else {
				row = append(row, '.')
			}
		}
		fmt.Println(string(row))
	}
	fmt.Println()
}

func getNeighbourPoints(position [4]int, directions [][4]int) [][4]int{
	var neighbourPositions [][4]int
	for _, direction := range directions {
		neighbourPositions = append(neighbourPositions, add(position, direction))
	}
	return neighbourPositions
}

func getActiveNeighbours(dimension map[[4]int]rune, neighbourPositions [][4]int) int {
	activeNeighbours := 0
	for _, neighbourPosition := range neighbourPositions {
		if dimension[neighbourPosition] == '#' {
			activeNeighbours++
		}
	}
	//fmt.Printf("Active Neighbours: %v \n" ,activeNeighbours)
	return activeNeighbours
}

func add(a [4]int, b [4]int) [4]int {
	return [4]int{a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]}
}

func getDirections() [][4]int {
	possibleValues := [3]int{-1, 0, 1}
	var directions [][4]int
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			for k := 0; k < 3; k++ {
				if possibleValues[i] != 0 || possibleValues[j] != 0 || possibleValues[k] != 0 {
					directions = append(directions, [4]int{possibleValues[i], possibleValues[j], possibleValues[k],0})
				}
			}
		}
	}
	return directions
}

func getDirectionsPart2() [][4]int {
	possibleValues := [3]int{-1, 0, 1}
	var directions [][4]int
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			for k := 0; k < 3; k++ {
				for l := 0; l < 3; l++ {
					if possibleValues[i] != 0 || possibleValues[j] != 0 || possibleValues[k] != 0 || possibleValues[l] != 0 {
						directions = append(directions, [4]int{possibleValues[i], possibleValues[j], possibleValues[k],possibleValues[l]})
					}
				}
			}
		}
	}
	return directions
}

func getStatus(position [3]int, dimension [][][]rune) rune {
	if len(dimension) < position[0] {
		return '.'
	}
	layer := dimension[position[0]]
	if len(layer) < position[1] {
		return '.'
	}
	row := layer[position[1]]
	if len(row) < position[2] {
		return '.'
	}
	return rune(row[position[2]])
}


func initiateDimension(z int, x int, y int) [][][]rune {
	var emptyRow []rune
	for i := 0; i < x; i++ {
		emptyRow = append(emptyRow, '.')
	}
	var emptyLayer [][]rune
	for i := 0; i < y; i++ {
		emptyLayer = append(emptyLayer, emptyRow)
	}
	var emptyDimension [][][]rune
	for i := 0; i < z; i++ {
		emptyDimension = append(emptyDimension, emptyLayer)
	}
	return emptyDimension
}
