package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"strings"

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

type Layout3d struct {
	Area        map[h.Coordinate3d]bool
	TopLeft     h.Coordinate3d
	BottomRight h.Coordinate3d
}

func NewLayout3d(filename string) *Layout3d {
	ret := Layout3d{
		Area:        make(map[h.Coordinate3d]bool),
		TopLeft:     h.Coordinate3d{X: 0, Y: 0, Z: 0},
		BottomRight: h.Coordinate3d{X: 0, Y: 0, Z: 0},
	}
	input, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatal(err.Error())
	}
	lines := make([]string, 0)
	for _, line := range strings.Split(string(input), "\n") {
		lines = append(lines, line)
	}
	for y := 0; y < len(lines); y++ {
		if len(lines[y]) < ret.BottomRight.X {
			break
		}
		ret.BottomRight.Y = y
		for x := 0; x < len(lines[y]); x++ {
			ret.BottomRight.X = x
			ret.Area[h.Coordinate3d{X: x, Y: y}] = lines[y][x] == '#'
		}
	}
	return &ret
}

func (l *Layout3d) Step() {
	a := make(map[h.Coordinate3d]bool)
	for k, v := range l.Area {
		a[k] = v
	}
	middle := h.Coordinate{X: l.BottomRight.X / 2, Y: l.BottomRight.Y / 2}
	for z := l.TopLeft.Z - 1; z <= l.BottomRight.Z+1; z++ {
		for y := l.TopLeft.Y; y <= l.BottomRight.Y; y++ {
			for x := l.TopLeft.X; x <= l.BottomRight.X; x++ {
				c := h.Coordinate3d{X: x, Y: y, Z: z}
				if x == middle.X && y == middle.Y {
					a[c] = false
					continue
				}
				neighbors := l.GetNeighborCount(c)
				if l.Area[c] {
					if neighbors != 1 {
						a[c] = false
					}
				} else {
					if neighbors == 1 || neighbors == 2 {
						a[c] = true
						// If c is on a new 'z', we need to expand our TopLeft/BottomRight
						if c.Z < l.TopLeft.Z {
							l.TopLeft.Z = c.Z
						} else if c.Z > l.BottomRight.Z {
							l.BottomRight.Z = c.Z
						}
					}
				}
			}
		}
	}
	for k, v := range a {
		l.Area[k] = v
	}
}

func (l *Layout3d) GetNeighborCount(c h.Coordinate3d) int {
	middle := h.Coordinate3d{X: l.BottomRight.X / 2, Y: l.BottomRight.Y / 2, Z: c.Z}
	var neighbors []h.Coordinate3d

	if c.Y == l.TopLeft.Y {
		neighbors = append(neighbors, middle.Up().North())
		neighbors = append(neighbors, c.South())
		if c.X == l.TopLeft.X {
			neighbors = append(neighbors, middle.Up().West())
			neighbors = append(neighbors, c.East())
		} else if c.X == l.BottomRight.X {
			neighbors = append(neighbors, middle.Up().East())
			neighbors = append(neighbors, c.West())
		} else {
			neighbors = append(neighbors, c.West())
			neighbors = append(neighbors, c.East())
		}
	} else if c.Y == l.BottomRight.Y {
		neighbors = append(neighbors, middle.Up().South())
		neighbors = append(neighbors, c.North())
		if c.X == l.TopLeft.X {
			neighbors = append(neighbors, middle.Up().West())
			neighbors = append(neighbors, c.East())
		} else if c.X == l.BottomRight.X {
			neighbors = append(neighbors, middle.Up().East())
			neighbors = append(neighbors, c.West())
		} else {
			neighbors = append(neighbors, c.West())
			neighbors = append(neighbors, c.East())
		}
	} else if c.X == l.TopLeft.X {
		neighbors = append(neighbors, middle.Up().West())
		neighbors = append(neighbors, c.East())
		if c.Y == l.TopLeft.Y {
			neighbors = append(neighbors, middle.Up().North())
			neighbors = append(neighbors, c.South())
		} else if c.Y == l.BottomRight.Y {
			neighbors = append(neighbors, middle.Up().South())
			neighbors = append(neighbors, c.North())
		} else {
			neighbors = append(neighbors, c.South())
			neighbors = append(neighbors, c.North())
		}
	} else if c.X == l.BottomRight.X {
		neighbors = append(neighbors, middle.Up().East())
		neighbors = append(neighbors, c.West())
		if c.Y == l.TopLeft.Y {
			neighbors = append(neighbors, middle.Up().North())
			neighbors = append(neighbors, c.South())
		} else if c.Y == l.BottomRight.Y {
			neighbors = append(neighbors, middle.Up().South())
			neighbors = append(neighbors, c.North())
		} else {
			neighbors = append(neighbors, c.South())
			neighbors = append(neighbors, c.North())
		}
	} else if c.Y == middle.Y-1 && c.X == middle.X {
		neighbors = append(neighbors, c.North())
		neighbors = append(neighbors, c.East())
		neighbors = append(neighbors, c.West())
		// South is the North row of the lower grid
		for x := l.TopLeft.X; x <= l.BottomRight.X; x++ {
			neighbors = append(neighbors, h.Coordinate3d{X: x, Y: l.TopLeft.Y, Z: c.Down().Z})
		}
	} else if c.Y == middle.Y+1 && c.X == middle.X {
		neighbors = append(neighbors, c.East())
		neighbors = append(neighbors, c.South())
		neighbors = append(neighbors, c.West())
		// North is the South row of the lower grid
		for x := l.TopLeft.X; x <= l.BottomRight.X; x++ {
			neighbors = append(neighbors, h.Coordinate3d{X: x, Y: l.BottomRight.Y, Z: c.Down().Z})
		}
	} else if c.X == middle.X-1 && c.Y == middle.Y {
		neighbors = append(neighbors, c.North())
		neighbors = append(neighbors, c.South())
		neighbors = append(neighbors, c.West())
		// East is the West row of the lower grid
		for y := l.TopLeft.Y; y <= l.BottomRight.Y; y++ {
			neighbors = append(neighbors, h.Coordinate3d{X: l.TopLeft.X, Y: y, Z: c.Down().Z})
		}
	} else if c.X == middle.X+1 && c.Y == middle.Y {
		neighbors = append(neighbors, c.North())
		neighbors = append(neighbors, c.East())
		neighbors = append(neighbors, c.South())
		// West is the East row of the lower grid
		for y := l.TopLeft.Y; y <= l.BottomRight.Y; y++ {
			neighbors = append(neighbors, h.Coordinate3d{X: l.BottomRight.X, Y: y, Z: c.Down().Z})
		}
	} else {
		neighbors = append(neighbors, c.North())
		neighbors = append(neighbors, c.East())
		neighbors = append(neighbors, c.South())
		neighbors = append(neighbors, c.West())
	}
	ret := 0
	for _, v := range neighbors {
		if n, ok := l.Area[v]; ok && n {
			ret++
		}
	}
	return ret
}

func (l *Layout3d) CountBugs() int {
	var ret int
	for _, v := range l.Area {
		if v {
			ret++
		}
	}
	return ret
}

func (l Layout3d) String() string {
	var ret string
	middle := h.Coordinate{X: l.BottomRight.X / 2, Y: l.BottomRight.Y / 2}
	for z := l.TopLeft.Z; z <= l.BottomRight.Z; z++ {
		ret = fmt.Sprintf("%s\nDepth %d:\n", ret, z)
		for y := l.TopLeft.Y; y <= l.BottomRight.Y; y++ {
			for x := l.TopLeft.X; x <= l.BottomRight.X; x++ {
				if x == middle.X && y == middle.Y {
					ret = ret + "?"
				} else if l.Area[h.Coordinate3d{X: x, Y: y, Z: z}] {
					ret = ret + "#"
				} else {
					ret = ret + "."
				}
			}
			ret = ret + "\n"
		}
	}
	return ret
}
