import matrix
import shared
from dataclasses import dataclass, field
from functools import cached_property
from typing import Tuple, List
import operator


# ####
FLAT = ((0,0),(0,1),(0,2),(0,3))
# .#.
# ###
# .#.
CROSS = (
        (-2,1),
        (-1,0),(-1,1),(-1,2),
        (0,1))
# ..#
# ..#
# ###
J = ( (-2,2),
      (-1,2),
      (0,0),(0,1),(0,2))
# #
# #
# #
# #
I = ((0,0),(-1,0),(-2,0),(-3,0))
# ##
# ##
SQUARE = (
    (-1,0),(-1,1),
    (0,0),(0,1),
)

ORDER = (('-',FLAT), ('+',CROSS), ('j',J), ('I',I), ('o',SQUARE))

OPS = {'>':operator.add, '<':operator.sub}
OFF = {'>': (0,1), '<':(0,-1)}


@dataclass
class Shape:
    rock:int
    y:int
    x:int
    shape: Tuple[str,Tuple[Tuple[int,int]]] # ('j', ((0,0),....))
    moving: bool = True

    @property
    def char(self):
        if self.moving:
            return '@'
        return self.at_rest_char
    
    @property
    def at_rest_char(self):
        return ORDER[self.rock%5][0]

    @property
    def str(self):
        return self.shape[0]

    @property
    def offsets(self):
        return self.shape[1]

    @property
    def coords(self):
        return [ (y+self.y,x+self.x) for y,x in self.shape[1]]




def find_highest(shapes, default_height):
    if not shapes:
        return default_height
    #return min([y for s in shapes for y,_ in s.coords])
    return min([s.coords[0][0] for s in shapes[-20:]])
               #all_y = []
               #for s in shapes[-20:]:
               #    all_y.append(s.coords[0][0])
               #return min(all_y)

all_coords = set()

def collision_at(shapes, offset):
    if len(shapes) == 1:
        # Nothing to compare
        return False
    shape = shapes[-1]
    # Take in all existing coordinates
    for y,x in shape.coords:
        if (y+offset[0],x+offset[1]) in all_coords:
            return True
    return False

def out_of_bounds(height, width, shapes, offset):
    shape = shapes[-1]
    if shape is None:
        raise Exception("Please provide a list of coordinate offsets from Y,X to draw")
    for y,x in shape.coords:
        #print(f"\t{row}+{y} > {height}\t", f"{col}+{x} > {width}\t", f"{col}+{x} < 0")
        if y+offset[0] >= height:
            return True
        if x+offset[1] >= width:
            return True
        if x+offset[1] < 0:
            return True
    return False


@shared.profile
def part1(rows):
    instructions = [r for r in rows]
    height = 20 
    height = 2022*4+4
    width = 7
    view = height - 20

    rock = 0
    shapes = []
    spawning = True
    while rock < 2022:
        if spawning:
            # Add last rock's coords to all coords
            if shapes:
                for c in shapes[-1].coords:
                    all_coords.add(c)
            X = 2
            Y = find_highest(shapes, height) - 4
            shape = Shape(rock=rock, y=Y, x=X, shape=ORDER[rock%len(ORDER)])
            shapes.append(shape)
            spawning = False
        #render(width, height, shapes)
        #print("~"*20)


        # loop through instructions
        try:
            next_move = instructions.pop(0)
        except IndexError:
            instructions = [r for r in rows]
            next_move = instructions.pop(0)


        # Try to move right/left

        next_offset = OFF[next_move]
        if not out_of_bounds(height,width, shapes, (0, next_offset[1])) and not collision_at(shapes, next_offset):
                shapes[-1].x += next_offset[1] 

        # check if hit bottom
        next_offset = (1, next_offset[1]) 
        if out_of_bounds(height, width, shapes, (next_offset[0],0)):
            # hit bottom dont move down
            shapes[-1].moving = False
            spawning = True
            rock += 1
            continue

        if collision_at(shapes, (next_offset[0], 0)):
            # Hit another Block dont move down
            shapes[-1].moving = False
            spawning = True
            rock += 1
        else:
            # can move down
            shapes[-1].y += 1

    #render(width, height, shapes)
    print(height - find_highest(shapes, height))
    

def render(width, height, shapes):
    print("-"*15)
    mx = matrix.matrix_of_size(width, height)
    for s in shapes:
        matrix.draw_shape_at(mx, s.y, s.x, s.offsets, s.char)
    print(matrix.ppmx(mx,pad=False,space=False))



# @shared.profile
def part2(rows):
    print("NAH BRO")


def main():
    rows = [row for row in shared.load_rows(17)][0]
    with shared.elapsed_timer() as elapsed:
        part1(rows)
        print("🕒", elapsed())
    with shared.elapsed_timer() as elapsed:
        part2(rows)
        print("🕒", elapsed())


if __name__ == "__main__":
    main()
