package main

import (
	"bufio"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

type cuboid struct {
	state      string
	xmin, xmax int
	ymin, ymax int
	zmin, zmax int
}

func main() {
	if err := myMain(); err != nil {
		log.Println(err)
	}
}

func myMain() error {
	cbs, err := parseInput("input.txt")
	if err != nil {
		return err
	}

	log.Println(solveFirst(cbs))
	log.Println(solveSecond(cbs))

	return nil
}

type point struct {
	x, y, z int
}

func solveFirst(cbs []cuboid) int {
	const (
		rangeMin = -50
		rangeMax = 50
	)
	m := map[point]struct{}{}
	for _, cb := range cbs {
		for x := max(cb.xmin, rangeMin); x <= min(cb.xmax, rangeMax); x++ {
			for y := max(cb.ymin, rangeMin); y <= min(cb.ymax, rangeMax); y++ {
				for z := max(cb.zmin, rangeMin); z <= min(cb.zmax, rangeMax); z++ {
					// log.Println(cb.state, x, y, z)
					switch cb.state {
					case "on":
						m[point{x, y, z}] = struct{}{}
					case "off":
						delete(m, point{x, y, z})
					default:
						log.Panicln("no such state")
					}
				}
			}
		}
	}
	return len(m)
}

type subcubes map[cuboid]int

func solveSecond(cbs []cuboid) int {
	m := subcubes{}
	for _, cube := range cbs {
		subMap := subcubes{}
		for orig := range m {
			ovrlp := overlap(cube, orig)
			if ovrlp.state == "noop" {
				continue
			}

			// do not count repeatedly the overlapping part
			// m[orig] can be 0, -1 and 1.
			// 0 means it was an "off" state
			// if it was 1, now we change it to -1, so adding a new cube with
			// state = "on" will change it to 0.
			subMap[ovrlp] -= m[orig]
		}

		if cube.state == "on" {
			subMap[cube] += 1
		}

		for c := range subMap {
			m[c] += subMap[c]
		}

	}

	var sum int
	for cube, v := range m {
		sum += cube.size() * v
	}
	return sum
}

func (c cuboid) size() int {
	return (c.xmax - c.xmin + 1) * (c.ymax - c.ymin + 1) * (c.zmax - c.zmin + 1)
}

func overlap(first, second cuboid) cuboid {
	// return none, if there is no overlap in one of the dimensions
	if first.xmax < second.xmin || second.xmax < first.xmin ||
		first.ymax < second.ymin || second.ymax < first.ymin ||
		first.zmax < second.zmin || second.zmax < first.zmin {
		return cuboid{state: "noop"}
	}
	return cuboid{"",
		max(first.xmin, second.xmin), min(first.xmax, second.xmax),
		max(first.ymin, second.ymin), min(first.ymax, second.ymax),
		max(first.zmin, second.zmin), min(first.zmax, second.zmax),
	}
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func parseInput(fileName string) ([]cuboid, error) {
	ret := []cuboid{}
	fd, err := os.Open(fileName)
	if err != nil {
		return ret, err
	}
	defer fd.Close()

	re := regexp.MustCompile(`[-]*\d+`)

	buf := bufio.NewScanner(fd)
	for buf.Scan() {
		line := cuboid{}
		state := strings.Split(buf.Text(), " ")
		line.state = state[0]

		tmp := re.FindAllString(buf.Text(), -1)

		line.xmin, err = strconv.Atoi(tmp[0])
		if err != nil {
			return ret, err
		}
		line.xmax, err = strconv.Atoi(tmp[1])
		if err != nil {
			return ret, err
		}
		line.ymin, err = strconv.Atoi(tmp[2])
		if err != nil {
			return ret, err
		}
		line.ymax, err = strconv.Atoi(tmp[3])
		if err != nil {
			return ret, err
		}
		line.zmin, err = strconv.Atoi(tmp[4])
		if err != nil {
			return ret, err
		}
		line.zmax, err = strconv.Atoi(tmp[5])
		if err != nil {
			return ret, err
		}

		ret = append(ret, line)
	}

	return ret, nil
}