import matrix
import shared
from pprint import pprint
from dataclasses import dataclass
from typing import Set, Tuple
from functools import cached_property


@dataclass
class Cube:
    x: int
    y: int
    z: int
    neighbor_coords: Set[Tuple[int,int,int]] = None

    @cached_property
    def xyz(self):
        return (self.x,self.y,self.z)
    
    def set_neighbor_coords(self):
        self.neighbor_coords = get_neighbors(self.x,self.y,self.z)

def get_neighbors(x,y,z):
        # Generate the six neighbor_coords
        # Look at a die 6 to the left, 2 on top, one on right
        offsets = (
           (1,0,0), # 1 
           (0,0,1), # 2 
           (0,-1,0),# 3 
           (0,1,0), # 4 
           (0,0,-1),# 5 
           (-1,0,0) # 6 
        )
        neighbor_coords = set([(x+o[0], y+o[1], z+o[2]) for o in offsets])
        return neighbor_coords

def count_neighbor_coords(x,y,z):
    pass
        


# @shared.profile
def part1(rows):
    cubes = {}
    for idx, (x,y,z) in enumerate(rows):
        cubes[(x,y,z)] = {'n':get_neighbors(x,y,z), 'not_me': tuple(map(tuple,rows[:idx] + rows[idx+1:]))}
    potential = len(cubes) * 6
    for coords, cube in cubes.items():
        for other in cube['not_me']:
            if other in cube['n']:
                potential -= 1
    print(potential)



def part2(rows):
    cubes = {}
    maxX,maxY,maxZ = 0,0,0
    for idx, (x,y,z) in enumerate(rows):
        cubes[(x,y,z)] = {'n':get_neighbors(x,y,z), 'not_me': set(map(tuple,rows[:idx] + rows[idx+1:]))}
        maxX = max(maxX,x)
        maxY = max(maxY,y)
        maxZ = max(maxZ,z)
    _cubes = frozenset(cubes.keys())

    potential = len(cubes) * 6
    for coords, cube in cubes.items():
        for other in cube['not_me']:
            if other in cube['n']:
                potential -= 1

    air = set()
    for x in range(0,maxX+1):
        for y in range(0,maxY+1):
            for z in range(0,maxZ+1):
                air.add((x,y,z))
    air -= _cubes # Remove all lava from air

    for a in air:
        neighbors = get_neighbors(*a)
        if len(neighbors & _cubes)==6:
            #print(a, "====", neighbors & _cubes)
            potential -= 6


    #            _ns = get_neighbors(x,y,z)
    #            lava_neighbors = _ns & _cubes
    #            if len(lava_neighbors) == 6:
    #                potential -= 6
    print(potential)



# @shared.profile
def _part2(rows):
    cubes = []

    _lava = set()
    for row in rows:
        x,y,z = row
        maxX = max(maxX,x)
        maxY = max(maxY,y)
        maxZ = max(maxZ,z)
        cube = Cube(x=x,y=y,z=z)
        cube.set_neighbor_coords()
        cubes.append(cube)
        _lava.add((x,y,z))
    print(maxX,maxY,maxZ)

    potential = len(cubes) * 6
    for idx in range(len(cubes)):
        cube = cubes[idx] 
        others = cubes[:idx] + cubes[idx+1:]
        for other in others:
            if other.xyz in cube.neighbor_coords:
                potential -= 1


    air = []
    for x in range(0,maxX+1):
        for y in range(0,maxY+1):
            for z in range(0,maxZ+1):
                lava_count = 0
                if (x,y,z) in _lava:
                    continue

                # is air
                air_touching_lava = False
                for _n in _ns:
                    if _n in _lava:
                        lava_count += 1
                        air_touching_lava = True

                if air_touching_lava:
                    air.append((x,y,z))

                if lava_count == 6:
                    potential -= 6
                        

    print(potential, air)
            


def main():
    rows = [list(map(int,row.split(","))) for row in shared.load_rows(18)]
    with shared.elapsed_timer() as elapsed:
        part1(rows)
        print("🕒", elapsed())
    #rows = [map(int,row.split(",")) for row in shared.load_rows(18)]
    with shared.elapsed_timer() as elapsed:
        part2(rows)
        print("🕒", elapsed())


if __name__ == "__main__":
    main()
