package main

import (
	"fmt"

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

func main() {
	fmt.Println("# Day 17")
	fmt.Println()
	inp := h.StdinToStringSlice()
	fmt.Println("## Part 1:\nAnswer:", run(inp, 3))
	fmt.Println("## Part 2:\nAnswer:", run(inp, 4))
}

func run(inp []string, dim int) int {
	cs := NewCubeMapFromLines(inp)
	for i := 0; i < 6; i++ {
		next := NewCubeMap()
		wrk := cs.Copy()
		for _, c := range cs.ToSlice() {
			wrk.AddMany(c.Neighbors(dim))
		}
		for _, c := range wrk.ToSlice() {
			active := cs.Contains(c)
			cnt := 0
			for _, n := range c.Neighbors(dim) {
				if cs.Contains(n) {
					cnt++
				}
			}
			if cnt == 3 || (active && cnt == 2) {
				next.Add(c)
			}
		}
		cs = next
	}
	return cs.Size()
}

type Cube struct {
	X, Y, Z, W int
}

func (c Cube) Neighbors(d int) []Cube {
	ret := []Cube{}
	for dx := -1; dx <= 1; dx++ {
		for dy := -1; dy <= 1; dy++ {
			for dz := -1; dz <= 1; dz++ {
				for dw := -(d - 3); dw <= d-3; dw++ {
					if dx == 0 && dy == 0 && dz == 0 && dw == 0 {
						continue
					}
					ret = append(ret, Cube{c.X + dx, c.Y + dy, c.Z + dz, c.W + dw})
				}
			}
		}
	}
	return ret
}

type CubeMap struct {
	field map[Cube]bool
}

func NewCubeMap() *CubeMap {
	return &CubeMap{map[Cube]bool{}}
}
func NewCubeMapFromLines(inp []string) *CubeMap {
	cs := NewCubeMap()
	for y, line := range inp {
		for x, b := range line {
			if b == '#' {
				cs.Add(Cube{x, y, 0, 0})
			}
		}
	}
	return cs
}

func (cs *CubeMap) Add(c Cube) {
	cs.field[c] = true
}
func (cs *CubeMap) AddMany(cubes []Cube) {
	for _, c := range cubes {
		cs.Add(c)
	}
}
func (cs *CubeMap) Remove(c Cube) {
	delete(cs.field, c)
}
func (cs *CubeMap) Contains(c Cube) bool {
	_, ok := cs.field[c]
	return ok
}
func (cs *CubeMap) Copy() *CubeMap {
	field := map[Cube]bool{}
	for c := range cs.field {
		field[c] = true
	}
	return &CubeMap{field}
}
func (cs *CubeMap) Size() int {
	return len(cs.field)
}
func (cs *CubeMap) ToSlice() []Cube {
	ret := []Cube{}
	for c := range cs.field {
		ret = append(ret, Cube{c.X, c.Y, c.Z, c.W})
	}
	return ret
}
