package main

import (
	"AOC2021/src/helper"
	"fmt"
	"os"
	"strconv"
	"strings"
)

type cubid struct {
	xValues     [2]int
	yValues     [2]int
	zValues     [2]int
	cuts        []*cubid
	alreadyUsed []*cubid
	command     string
}

func main() {
	args := os.Args[1:]
	input, err := helper.GetInput(args[0])
	if err != nil {
		fmt.Println(err)
	}
	part1(input)

	cubids := []*cubid{}
	for _, row := range input {
		commandSplit := strings.Split(row, " ")
		valueSplit := strings.Split(helper.RemoveCharactersFromString(commandSplit[1], "yxz,"), "=")
		xValues := getValuesFromString(valueSplit[1], false)
		yValues := getValuesFromString(valueSplit[2], false)
		zValues := getValuesFromString(valueSplit[3], false)
		cubids = addCubid(cubids, cubid{xValues, yValues, zValues, []*cubid{}, []*cubid{}, commandSplit[0]})

	}
	fmt.Println(countActiveCubids(cubids))
}

func part1(input []string) {
	coreMap := make(map[[3]int]struct{})
	for _, row := range input {
		commandSplit := strings.Split(row, " ")
		valueSplit := strings.Split(helper.RemoveCharactersFromString(commandSplit[1], "yxz,"), "=")
		xValues := getValuesFromString(valueSplit[1], true)
		yValues := getValuesFromString(valueSplit[2], true)
		zValues := getValuesFromString(valueSplit[3], true)
		for i := xValues[0]; i <= xValues[1]; i++ {
			for j := yValues[0]; j <= yValues[1]; j++ {
				for k := zValues[0]; k <= zValues[1]; k++ {
					if commandSplit[0] == "on" {
						coreMap[[3]int{i, j, k}] = struct{}{}
					} else if commandSplit[0] == "off" {
						delete(coreMap, [3]int{i, j, k})
					}
				}
			}
		}
	}
	fmt.Println(int64(len(coreMap)))
}

func checkOverlap(cubid1 *cubid, cubid2 *cubid) bool {
	if !overlapLines(cubid1.xValues, cubid2.xValues) {
		return false
	}
	if !overlapLines(cubid1.yValues, cubid2.yValues) {
		return false
	}
	if !overlapLines(cubid1.zValues, cubid2.zValues) {
		return false
	}
	return true
}

func addCubid(cubids []*cubid, newCubid cubid) []*cubid {
	newCubids := []*cubid{}
	if newCubid.command == "off" {
		for i := range cubids {
			if checkOverlap(cubids[i], &newCubid) {
				cut(cubids[i], newCubid)
				newCubids = append(newCubids, cubids[i])
			} else {
				newCubids = append(newCubids, cubids[i])
			}
		}
	}

	if newCubid.command == "on" {
		newCubids = handleOnCubid(cubids, newCubid, newCubids)
	}
	return newCubids
}

func handleOnCubid(cubids []*cubid, newCubid cubid, newCubids []*cubid) []*cubid {
	tmpNewCubid := newCubid
	tmpOffCubid := newCubid
	tmpOffCubid.command = "off"
	for i := range cubids {
		if checkOverlap(cubids[i], &tmpNewCubid) {
			alreadyUsed(&tmpNewCubid, cubids[i])
			addCubid(cubids[i].cuts, tmpOffCubid)
		}
		newCubids = append(newCubids, cubids[i])
	}
	newCubids = append(newCubids, &tmpNewCubid)
	return newCubids
}

func countActiveCubids(cubids []*cubid) int64 {
	sum := int64(0)
	for i := range cubids {
		allPoints := countActiveCubid(cubids[i])
		inactivePoints := countActiveCubids(cubids[i].cuts)
		alreadyUsedPoints := countActiveCubids(cubids[i].alreadyUsed)
		sum += allPoints - inactivePoints - alreadyUsedPoints
	}
	return sum
}

func countActiveCubid(cubid *cubid) int64 {
	lengthX := cubid.xValues[1] - cubid.xValues[0] + 1
	lengthY := cubid.yValues[1] - cubid.yValues[0] + 1
	lengthZ := cubid.zValues[1] - cubid.zValues[0] + 1
	sum := int64(lengthX) * int64(lengthY) * int64(lengthZ)
	return sum
}

func cut(cubid1 *cubid, cubid2 cubid) int {
	cutCubid := cubid{}
	cutCubid.xValues[0] = max(cubid1.xValues[0], cubid2.xValues[0])
	cutCubid.yValues[0] = max(cubid1.yValues[0], cubid2.yValues[0])
	cutCubid.zValues[0] = max(cubid1.zValues[0], cubid2.zValues[0])
	cutCubid.xValues[1] = min(cubid1.xValues[1], cubid2.xValues[1])
	cutCubid.yValues[1] = min(cubid1.yValues[1], cubid2.yValues[1])
	cutCubid.zValues[1] = min(cubid1.zValues[1], cubid2.zValues[1])
	cutCubid.command = "on"
	for _, overlapCubid := range cubid1.alreadyUsed {
		if checkOverlap(&cutCubid, overlapCubid) {
			alreadyUsed(&cutCubid, overlapCubid)
		}
	}
	cubid1.cuts = addCubid(cubid1.cuts, cutCubid)
	return 0
}

func alreadyUsed(cubid1 *cubid, cubid2 *cubid) int {
	cutCubid := cubid{}
	cutCubid.xValues[0] = max(cubid1.xValues[0], cubid2.xValues[0])
	cutCubid.yValues[0] = max(cubid1.yValues[0], cubid2.yValues[0])
	cutCubid.zValues[0] = max(cubid1.zValues[0], cubid2.zValues[0])
	cutCubid.xValues[1] = min(cubid1.xValues[1], cubid2.xValues[1])
	cutCubid.yValues[1] = min(cubid1.yValues[1], cubid2.yValues[1])
	cutCubid.zValues[1] = min(cubid1.zValues[1], cubid2.zValues[1])
	cutCubid.command = "on"
	cubid1.alreadyUsed = addCubid(cubid1.alreadyUsed, cutCubid)
	return 0
}

func checkSameCoordinates(cubid1 cubid, cubid2 cubid) bool {
	return cubid1.xValues == cubid2.xValues && cubid1.yValues == cubid2.yValues && cubid1.zValues == cubid2.zValues
}

func getValuesFromString(input string, part1 bool) (output [2]int) {
	split := strings.Split(input, "..")
	output[0], _ = strconv.Atoi(split[0])
	if part1 && output[0] < -50 {
		output[0] = -50
	}
	output[1], _ = strconv.Atoi(split[1])
	if part1 && output[1] > 50 {
		output[1] = 50
	}
	return
}

func isBetween(x, a, b int) bool {
	if x >= a && x <= b {
		return true
	}
	return false
}

func overlapLines(a, b [2]int) bool {
	for _, aVal := range a {
		if isBetween(aVal, b[0], b[1]) {
			return true
		}
	}
	for _, bVal := range b {
		if isBetween(bVal, a[0], a[1]) {
			return true
		}
	}
	return false
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func printTwoDimensions(cubids []*cubid) {
	field := initiateDimension(100, 100)
	offset := 40

	for _, cubid := range cubids {
		for i := cubid.xValues[0] + offset; i <= cubid.xValues[1]+offset; i++ {
			for j := cubid.yValues[0] + offset; j <= cubid.yValues[1]+offset; j++ {
				field[j][i] = '#'
			}
		}
		for _, cut := range cubid.cuts {
			for i := cut.xValues[0] + offset; i <= cut.xValues[1]+offset; i++ {
				for j := cut.yValues[0] + offset; j <= cut.yValues[1]+offset; j++ {
					field[j][i] = 'X'
				}
			}
		}
	}
	for _, row := range field {
		fmt.Println(string(row))
	}
	fmt.Printf("Active Points: %d \n", countActiveCubids(cubids))
}

func initiateDimension(x int, y int) [][]rune {
	var emptyLayer [][]rune
	for i := 0; i < y; i++ {
		emptyLayer = append(emptyLayer, []rune(strings.Repeat(".", x)))
	}
	return emptyLayer
}
