package main

import (
	"bufio"
	"fmt"
	"os"
)

func printWorld(world []rune, stride int) {
	for z := 0; z < stride; z++ {
		i := z * (stride * stride)
		fmt.Printf("Z: %d\n", z)
		for y := 0; y < stride; y++ {
			j := i + (y * stride)
			for x := 0; x < stride; x++ {
				k := j + x
				r := world[k]
				if r == 0 {
					fmt.Printf("%c", '+')
				} else {
					fmt.Printf("%c", r)
				}
			}
			fmt.Println()
		}
		fmt.Println()
	}
}

func printWorld4D(world []rune, stride int) {
	for w := 0; w < stride; w++ {
		t := w * (stride * stride * stride)
		fmt.Printf("W: %d\n", w)
		for z := 0; z < stride; z++ {
			i := t + (z * (stride * stride))
			fmt.Printf("Z: %d\n", z)
			for y := 0; y < stride; y++ {
				j := i + (y * stride)
				for x := 0; x < stride; x++ {
					k := j + x
					r := world[k]
					if r == 0 {
						fmt.Printf("%c", '+')
					} else {
						fmt.Printf("%c", r)
					}
				}
				fmt.Println()
			}
			fmt.Println()
		}
		fmt.Println()
	}
}

func getNumNeighbors(world []rune, stride int, x, y, z int) int {
	var result int
	for zz := z - 1; zz < z+2; zz++ {
		if zz < 0 || zz >= stride {
			continue
		}

		i := zz * (stride * stride)
		for yy := y - 1; yy < y+2; yy++ {
			if yy < 0 || yy >= stride {
				continue
			}

			j := i + (yy * stride)
			for xx := x - 1; xx < x+2; xx++ {
				if xx == x && yy == y && zz == z {
					continue
				}
				if xx < 0 || xx >= stride {
					continue
				}

				k := j + xx
				if world[k] == '#' {
					result++
				}
			}
		}
	}
	return result
}

func getNumNeighbors4D(world []rune, stride int, x, y, z, w int) int {
	var result int
	for ww := w - 1; ww < w+2; ww++ {
		if ww < 0 || ww >= stride {
			continue
		}
		t := ww * (stride * stride * stride)
		for zz := z - 1; zz < z+2; zz++ {
			if zz < 0 || zz >= stride {
				continue
			}

			i := t + (zz * (stride * stride))
			for yy := y - 1; yy < y+2; yy++ {
				if yy < 0 || yy >= stride {
					continue
				}

				j := i + (yy * stride)
				for xx := x - 1; xx < x+2; xx++ {
					if xx == x && yy == y && zz == z && ww == w {
						continue
					}
					if xx < 0 || xx >= stride {
						continue
					}

					k := j + xx
					if world[k] == '#' {
						result++
					}
				}
			}
		}
	}
	return result
}

// Task1 ...
func Task1(input []rune, stride int) int {
	offset := 1
	worldStride := stride + (offset * 2)
	size := worldStride * worldStride * worldStride
	world := make([]rune, size)
	for i := range world {
		world[i] = '.'
	}

	for y := 0; y < stride; y++ {
		for x := 0; x < stride; x++ {
			zz := offset * (worldStride * worldStride)
			yy := (y + offset) * worldStride
			xx := x + offset
			world[zz+yy+xx] = input[(y*stride)+x]
		}
	}

	printWorld(world, worldStride)
	prevWorld := make([]rune, size)
	for step := 0; step < 6; step++ {
		//fmt.Printf("Step %d\n==========\n", step+1)
		copy(prevWorld, world)

		var grow bool
		for z := 0; z < worldStride; z++ {
			i := z * (worldStride * worldStride)
			for y := 0; y < worldStride; y++ {
				j := i + (y * worldStride)
				for x := 0; x < worldStride; x++ {
					k := j + x
					num := getNumNeighbors(prevWorld, worldStride, x, y, z)
					if prevWorld[k] == '#' {
						if num != 2 && num != 3 {
							world[k] = '.'
						}
					} else if prevWorld[i] == '.' {
						if num == 3 {
							world[k] = '#'

							if x == 0 || x == stride-1 ||
								y == 0 || y == stride-1 ||
								z == 0 || z == stride-1 {
								grow = true
							}
						}
					}
				}
			}
		}

		if grow {
			offset++
			oldStride := worldStride
			worldStride = stride + (offset * 2)
			size = worldStride * worldStride * worldStride

			prevWorld = make([]rune, size)
			for i := range prevWorld {
				prevWorld[i] = '.'
			}
			for z := 0; z < oldStride; z++ {
				for y := 0; y < oldStride; y++ {
					for x := 0; x < oldStride; x++ {
						zz := (z + 1) * (worldStride * worldStride)
						yy := (y + 1) * worldStride
						xx := x + 1
						prevWorld[zz+yy+xx] = world[(z*(oldStride*oldStride))+(y*oldStride)+x]
					}
				}
			}
			world = make([]rune, size)
			copy(world, prevWorld)
		}

		//printWorld(world, worldStride)
	}

	var result int
	for i := range world {
		if world[i] == '#' {
			result++
		}
	}
	return result
}

// Task2 ...
func Task2(input []rune, stride int) int {
	offset := 1
	worldStride := stride + (offset * 2)
	size := worldStride * worldStride * worldStride * worldStride
	world := make([]rune, size)
	for i := range world {
		world[i] = '.'
	}

	for y := 0; y < stride; y++ {
		for x := 0; x < stride; x++ {
			ww := offset * (worldStride * worldStride * worldStride)
			zz := offset * (worldStride * worldStride)
			yy := (y + offset) * worldStride
			xx := x + offset
			world[ww+zz+yy+xx] = input[(y*stride)+x]
		}
	}

	//printWorld4D(world, worldStride)
	prevWorld := make([]rune, size)
	for step := 0; step < 6; step++ {
		//fmt.Printf("Step %d\n==========\n", step+1)
		copy(prevWorld, world)

		var grow bool
		for w := 0; w < worldStride; w++ {
			t := w * (worldStride * worldStride * worldStride)
			for z := 0; z < worldStride; z++ {
				i := t + (z * (worldStride * worldStride))
				for y := 0; y < worldStride; y++ {
					j := i + (y * worldStride)
					for x := 0; x < worldStride; x++ {
						k := j + x
						num := getNumNeighbors4D(prevWorld, worldStride, x, y, z, w)
						if prevWorld[k] == '#' {
							if num != 2 && num != 3 {
								world[k] = '.'
							}
						} else if prevWorld[i] == '.' {
							if num == 3 {
								world[k] = '#'

								if x == 0 || x == stride-1 ||
									y == 0 || y == stride-1 ||
									z == 0 || z == stride-1 ||
									w == 0 || w == stride-1 {
									grow = true
								}
							}
						}
					}
				}
			}
		}

		if grow {
			offset++
			oldStride := worldStride
			worldStride = stride + (offset * 2)
			size = worldStride * worldStride * worldStride * worldStride

			prevWorld = make([]rune, size)
			for i := range prevWorld {
				prevWorld[i] = '.'
			}
			for w := 0; w < oldStride; w++ {
				for z := 0; z < oldStride; z++ {
					for y := 0; y < oldStride; y++ {
						for x := 0; x < oldStride; x++ {
							ww := (w + 1) * (worldStride * worldStride * worldStride)
							zz := (z + 1) * (worldStride * worldStride)
							yy := (y + 1) * worldStride
							xx := x + 1
							prevWorld[ww+zz+yy+xx] = world[(w*(oldStride*oldStride*oldStride))+(z*(oldStride*oldStride))+(y*oldStride)+x]
						}
					}
				}
			}
			world = make([]rune, size)
			copy(world, prevWorld)
		}

		//printWorld4D(world, worldStride)
	}

	var result int
	for i := range world {
		if world[i] == '#' {
			result++
		}
	}
	return result
}

func main() {
	file, _ := os.Open("input.txt")
	scanner := bufio.NewScanner(file)

	var stride int
	input := make([]rune, 0, 100)
	for scanner.Scan() {
		line := scanner.Text()
		if stride == 0 {
			stride = len(line)
		}
		for _, r := range line {
			input = append(input, rune(r))
		}
	}
	file.Close()

	result := Task1(input, stride)
	fmt.Printf("Task 1: %d\n", result)

	result = Task2(input, stride)
	fmt.Printf("Task 2: %d\n", result)
}