package main

import (
	"fmt"

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

const (
	EMPTY = 'L'
	FULL  = '#'
	FLOOR = '.'

	N = iota
	NE
	E
	SE
	S
	SW
	W
	NW
)

func main() {
	fmt.Println("# Day 11")
	inp := h.StdinToStringSlice()
	pt := h.GetArgNumber(1)
	if pt == "1" {
		part1(inp)
	} else {
		part2(inp)
	}
}

func part1(inp []string) {
	fmt.Println("## Part 1")
	w := BuildWaitingRoom(inp)
	var last string
	for w.String() != last {
		last = w.String()
		w.Tick()
	}
	fmt.Println(w)
	fmt.Printf("At stabilization there are %d occupied seats\n", w.OccupiedSeats())
}

func part2(inp []string) {
	fmt.Println("## Part 2")
	w := BuildWaitingRoom(inp)
	var last string
	for w.String() != last {
		last = w.String()
		w.VisibleTick()
	}
	fmt.Println(w)
	fmt.Printf("At stabilization there are %d occupied seats\n", w.OccupiedSeats())
}

type WaitingRoom struct {
	Layout    map[h.Coordinate]byte
	NorthMost int
	SouthMost int
	EastMost  int
	WestMost  int
}

func BuildWaitingRoom(inp []string) *WaitingRoom {
	ret := WaitingRoom{
		Layout:    make(map[h.Coordinate]byte),
		NorthMost: h.MAX_INT,
		SouthMost: h.MIN_INT,
		EastMost:  h.MIN_INT,
		WestMost:  h.MAX_INT,
	}

	for y := range inp {
		for x := range inp[y] {
			ret.Layout[h.Coordinate{X: x, Y: y}] = inp[y][x]
			if x < ret.WestMost {
				ret.WestMost = x
			}
			if x > ret.EastMost {
				ret.EastMost = x
			}
		}
		if y < ret.NorthMost {
			ret.NorthMost = y
		}
		if y > ret.SouthMost {
			ret.SouthMost = y
		}
	}
	return &ret
}

func (w *WaitingRoom) OccupiedSeats() int {
	var ret int
	for k := range w.Layout {
		if !w.CoordEmpty(k) {
			ret++
		}
	}
	return ret
}

func (w *WaitingRoom) CoordEmpty(c h.Coordinate) bool {
	return w.Layout[c] != FULL
}

func (w *WaitingRoom) ShouldFill(c h.Coordinate) bool {
	if w.Layout[c] == FULL || w.Layout[c] == FLOOR {
		return false
	}
	return w.CoordEmpty(c.North()) && w.CoordEmpty(c.NE()) && w.CoordEmpty(c.East()) && w.CoordEmpty(c.SE()) && w.CoordEmpty(c.South()) && w.CoordEmpty(c.SW()) && w.CoordEmpty(c.West()) && w.CoordEmpty(c.NW())
}

func (w *WaitingRoom) ShouldEmpty(c h.Coordinate) bool {
	if w.Layout[c] == EMPTY || w.Layout[c] == FLOOR {
		return false
	}
	var fullSeats int
	for _, v := range []h.Coordinate{c.North(), c.NE(), c.East(), c.SE(), c.South(), c.SW(), c.West(), c.NW()} {
		if !w.CoordEmpty(v) {
			fullSeats++
			if fullSeats >= 4 {
				return true
			}
		}
	}
	return false
}

func (w *WaitingRoom) Tick() {
	wrk := make(map[h.Coordinate]byte)
	for k := range w.Layout {
		wrk[k] = w.Layout[k]
		switch w.Layout[k] {
		case EMPTY:
			if w.ShouldFill(k) {
				wrk[k] = FULL
			}

		case FULL:
			if w.ShouldEmpty(k) {
				wrk[k] = EMPTY
			}
		}
	}
	for k := range wrk {
		w.Layout[k] = wrk[k]
	}
}

func (w *WaitingRoom) IsFloor(c h.Coordinate) bool {
	return w.Layout[c] == FLOOR
}

func (w *WaitingRoom) GetCoordInDir(c h.Coordinate, dir int) h.Coordinate {
	switch dir {
	case N:
		return c.North()
	case NE:
		return c.NE()
	case E:
		return c.East()
	case SE:
		return c.SE()
	case S:
		return c.South()
	case SW:
		return c.SW()
	case W:
		return c.West()
	case NW:
		return c.NW()
	}
	return c
}

func (w *WaitingRoom) DirectionEmpty(c h.Coordinate, dir int) bool {
	c = w.GetCoordInDir(c, dir)
	for w.IsFloor(c) {
		c = w.GetCoordInDir(c, dir)
	}
	return w.CoordEmpty(c)
}

func (w *WaitingRoom) VisibleShouldFill(c h.Coordinate) bool {
	if w.Layout[c] == FULL || w.Layout[c] == FLOOR {
		return false
	}
	return w.DirectionEmpty(c, N) && w.DirectionEmpty(c, NE) && w.DirectionEmpty(c, E) && w.DirectionEmpty(c, SE) && w.DirectionEmpty(c, S) && w.DirectionEmpty(c, SW) && w.DirectionEmpty(c, W) && w.DirectionEmpty(c, NW)
}

func (w *WaitingRoom) VisibleShouldEmpty(c h.Coordinate) bool {
	if w.Layout[c] == EMPTY || w.Layout[c] == FLOOR {
		return false
	}
	var fullSeats int
	for _, v := range []int{N, NE, E, SE, S, SW, W, NW} {
		if !w.DirectionEmpty(c, v) {
			fullSeats++
			if fullSeats >= 5 {
				return true
			}
		}
	}
	return false
}

func (w *WaitingRoom) VisibleTick() {
	wrk := make(map[h.Coordinate]byte)
	for k := range w.Layout {
		wrk[k] = w.Layout[k]
		switch w.Layout[k] {
		case EMPTY:
			if w.VisibleShouldFill(k) {
				wrk[k] = FULL
			}

		case FULL:
			if w.VisibleShouldEmpty(k) {
				wrk[k] = EMPTY
			}
		}
	}
	for k := range wrk {
		w.Layout[k] = wrk[k]
	}
}

func (w WaitingRoom) String() string {
	var ret string
	for y := w.NorthMost; y <= w.SouthMost; y++ {
		for x := w.WestMost; x <= w.EastMost; x++ {
			ret = ret + string(w.Layout[h.Coordinate{X: x, Y: y}])
		}
		ret = ret + "\n"
	}
	return ret
}

func DirToStr(dir int) string {
	switch dir {
	case N:
		return "N"
	case NE:
		return "NE"
	case E:
		return "E"
	case SE:
		return "SE"
	case S:
		return "S"
	case SW:
		return "SW"
	case W:
		return "W"
	case NW:
		return "NW"
	}
	return "-"
}
