package main

import (
	"fmt"
	"strings"

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

var steps []cuboid

func main() {
	inp := h.StdinToStringSlice()
	for i := range inp {
		pts := strings.Split(inp[i], " ")
		c := strings.Split(pts[1], ",")
		steps = append(steps, NewCuboid(
			pts[0] == "on",
			c[0][2:],
			c[1][2:],
			c[2][2:],
		))
	}
	m := c3dMap{
		Field: make(map[h.Coordinate3d]bool),
		blfX:  -50, trbX: 50,
		blfY: -50, trbY: 50,
		blfZ: -50, trbZ: 50,
	}
	initialize(&m)
	reboot()
}

func initialize(m *c3dMap) {
	fmt.Println("# Part 1")
	for i := range steps {
		m.doInitialize(steps[i])
	}
	fmt.Printf("After initialization, %d bits are on.\n", m.count(true))
}

func reboot() {
	fmt.Println("# Part 2")
	var cuboids []cuboid
	var volume int64
	for _, s1 := range steps {
		adds := make([]cuboid, 0)
		if s1.val {
			adds = append(adds, s1)
		}
		for _, s2 := range cuboids {
			if i := s1.intersect(s2, !s2.val); i.valid() {
				adds = append(adds, i)
			}
		}
		cuboids = append(cuboids, adds...)
	}
	for _, c := range cuboids {
		if c.val {
			volume += c.volume()
		} else {
			volume -= c.volume()
		}
	}
	fmt.Printf("After reboot, %d bits are on.\n", volume)
}

type cuboid struct {
	val    bool
	x1, x2 int
	y1, y2 int
	z1, z2 int
}

func NewCuboid(val bool, x, y, z string) cuboid {
	s := cuboid{val: val}
	fmt.Sscanf(x, "%d..%d", &s.x1, &s.x2)
	fmt.Sscanf(y, "%d..%d", &s.y1, &s.y2)
	fmt.Sscanf(z, "%d..%d", &s.z1, &s.z2)
	return s
}

func (s cuboid) volume() int64 {
	return int64((h.Abs(s.x2-s.x1) + 1) * (h.Abs(s.y2-s.y1) + 1) * (h.Abs(s.z2-s.z1) + 1))
}
func (s cuboid) valid() bool {
	return s.x1 <= s.x2 && s.y1 <= s.y2 && s.z1 <= s.z2
}
func (s cuboid) intersect(t cuboid, val bool) cuboid {
	return cuboid{
		val: val,
		x1:  h.Max(s.x1, t.x1),
		x2:  h.Min(s.x2, t.x2),
		y1:  h.Max(s.y1, t.y1),
		y2:  h.Min(s.y2, t.y2),
		z1:  h.Max(s.z1, t.z1),
		z2:  h.Min(s.z2, t.z2),
	}
}
func (s cuboid) String() string {
	return fmt.Sprintf("{%v; x:%d..%d, y:%d..%d, z:%d..%d}", s.val, s.x1, s.x2, s.y1, s.y2, s.z1, s.z2)
}

// Ended up not even using this for part 2
type c3dMap struct {
	Field      map[h.Coordinate3d]bool
	blfX, trbX int
	blfY, trbY int
	blfZ, trbZ int
}

func (c *c3dMap) count(v bool) int {
	var ret int
	for x := c.blfX; x <= c.trbX; x++ {
		for y := c.blfY; y <= c.trbY; y++ {
			for z := c.blfZ; z <= c.trbZ; z++ {
				if c.Field[h.Coordinate3d{X: x, Y: y, Z: z}] == v {
					ret++
				}
			}
		}
	}
	return ret
}

func (c *c3dMap) doInitialize(s cuboid) {
	if !c.inRange(s) {
		return
	}
	c.do(s)
}

func (c *c3dMap) do(s cuboid) {
	for x := s.x1; x <= s.x2; x++ {
		if x < c.blfX || x > c.trbX {
			continue
		}
		for y := s.y1; y <= s.y2; y++ {
			if y < c.blfY || x > c.trbY {
				continue
			}
			for z := s.z1; z <= s.z2; z++ {
				if z < c.blfZ || x > c.trbZ {
					continue
				}
				c.Field[h.Coordinate3d{X: x, Y: y, Z: z}] = s.val
			}
		}
	}
}

func (c *c3dMap) inRange(s cuboid) bool {
	if s.x2 < c.blfX {
		return false
	}
	if s.x1 > c.trbX {
		return false
	}
	if s.y2 < c.blfY {
		return false
	}
	if s.y1 > c.trbY {
		return false
	}
	if s.z2 < c.blfZ {
		return false
	}
	if s.z1 > c.trbZ {
		return false
	}
	return true
}

func (c c3dMap) Meta() string {
	return fmt.Sprintf(
		"{x: %d..%d, y: %d..%d, z: %d..%d}",
		c.blfX, c.trbX, c.blfY, c.trbY, c.blfZ, c.trbZ,
	)
}
