from minecraft_pb2 import *
from utils_blocks_categories import *
class Coordinates:
    
    y_min: int = -64
    y_max: int = 319
    
    def __init__(self, x: int, y: int, z: int):
        self.x: int = x
        self.y: int = y
        self.z: int = z

    # Crop or not to crop?
    def crop_y(self):
        self.y = max(self.y_min, min(self.y_max, self.y))
    
    def __repr__(self):
        return f"Coordinates(x={self.x}, y={self.y}, z={self.z})"

    def __neg__(self):
        return Coordinates(-self.x, -self.y, -self.z)

    def __add__(self, other):
        if isinstance(other, Coordinates):
            return Coordinates(self.x + other.x, self.y + other.y, self.z + other.z)
        elif isinstance(other, int):
            return Coordinates(self.x + other, self.y + other, self.z + other)
        else:
            raise TypeError("Unsupported operand type for +: Coordinates and {}".format(type(other)))

    def __sub__(self, other):
        if isinstance(other, Coordinates):
            return Coordinates(self.x - other.x, self.y - other.y, self.z - other.z)
        elif isinstance(other, int):
            return Coordinates(self.x - other, self.y - other, self.z - other)
        else:
            raise TypeError("Unsupported operand type for -: Coordinates and {}".format(type(other)))\
    
    def __mul__(self, other):
        if isinstance(other, int):
            return Coordinates(self.x * other, self.y * other, self.z * other)
        # elif isinstance(other, Coordinates):
        #     return Coordinates(self.x * other.x, self.y * other.y, self.z * other.z)
        else:
            raise TypeError("Unsupported operand type for *: Coordinates and {}".format(type(other)))

    def __truediv__(self, other):
        if isinstance(other, int):
            return Coordinates(self.x // other, self.y // other, self.z // other)
        # elif isinstance(other, Coordinates):
        #     return Coordinates(self.x // other.x, self.y // other.y, self.z // other.z)
        else:
            raise TypeError("Unsupported operand type for /: Coordinates and {}".format(type(other)))

    def __eq__(self, other):
        if isinstance(other, Coordinates):
            return self.x==other.x and self.y==other.y and self.z==other.z
        return NotImplemented

    def __ne__(self, other):
        if isinstance(other, Coordinates):
            return self.x!=other.x and self.y!=other.y and self.z!=other.z
        return NotImplemented

    def __lt__(self, other):
        if isinstance(other, Coordinates):
            return self.x<other.x and self.y<other.y and self.z<other.z
        raise TypeError("Cannot compare Coordinates with non-Coordinates object")

    def __le__(self, other):
        if isinstance(other, Coordinates):
            return self.x<=other.x and self.y<=other.y and self.z<=other.z
        raise TypeError("Cannot compare Coordinates with non-Coordinates object")

    def __gt__(self, other):
        if isinstance(other, Coordinates):
            return self.x>other.x and self.y>other.y and self.z>other.z
        raise TypeError("Cannot compare Coordinates with non-Coordinates object")

    def __ge__(self, other):
        if isinstance(other, Coordinates):
            return self.x>=other.x and self.y>=other.y and self.z>=other.z
        raise TypeError("Cannot compare Coordinates with non-Coordinates object")

    @classmethod   
    def from_tuple(cls, coord_tuple):
        if len(coord_tuple) == 3:
            return cls(*coord_tuple)
        else:
            raise ValueError("Input tuple must have exactly 3 elements")
        
    @classmethod   
    def from_point(cls, point):
        return cls(point.x,point.y,point.z)

    @staticmethod     
    def move_east(coor, amout: int=1):
        """
        Moves along x axis in positive direction.
        """
        return Coordinates(coor.x + amout, coor.y, coor.z)
    
    @staticmethod 
    def move_west(coor, amout: int=1):
        """
        Moves along x axis in negative direction.
        """
        return Coordinates(coor.x - amout, coor.y, coor.z)
    
    @staticmethod 
    def move_up(coor, amout: int=1):
        """
        Moves along y axis in positive direction.
        """
        return Coordinates(coor.x, coor.y + amout, coor.z)
    
    @staticmethod 
    def move_down(coor, amout: int=1):
        """
        Moves along y axis in negative direction.
        """
        return Coordinates(coor.x, coor.y - amout, coor.z)
    
    @staticmethod 
    def move_south(coor, amout: int=1):
        """
        Moves along z axis in positive direction.
        """
        return Coordinates(coor.x, coor.y, coor.z + amout)
    
    @staticmethod 
    def move_north(coor, amout: int=1):
        """
        Moves along z axis in negative direction.
        """
        return Coordinates(coor.x, coor.y, coor.z - amout)
    
    @staticmethod 
    def zero():
        return Coordinates(0,0,0)
    
    @staticmethod     
    def cardinal_east():
        return Coordinates(1,0,0)
    
    @staticmethod     
    def cardinal_west():
        return Coordinates(-1,0,0)
    
    @staticmethod     
    def cardinal_up():
        return Coordinates(0,1,0)
    
    @staticmethod     
    def cardinal_down():
        return Coordinates(0,-1,0)
    
    @staticmethod     
    def cardinal_south():
        return Coordinates(0,0,1)
    
    @staticmethod     
    def cardinal_north():
        return Coordinates(0,0,-1)
    
    @staticmethod
    def get_cardinal(direction:Orientation):
        if not direction in CARDINAL_DIRECTIONS:
            raise ValueError("Direction must be cardinal")
        elif direction==NORTH:
            return Coordinates.cardinal_north()
        elif direction==SOUTH:
            return Coordinates.cardinal_south()
        elif direction==DOWN:
            return Coordinates.cardinal_down()
        elif direction==UP:
            return Coordinates.cardinal_up()
        elif direction==WEST:
            return Coordinates.cardinal_west()
        elif direction==EAST:
            return Coordinates.cardinal_east()
        else:
            return Coordinates.zero()
    
    def to_point(self):
        return Point( x=self.x, y=self.y, z=self.z)
    
    def to_tuple(self):
        return (self.x, self.y, self.z)
    
    def l_norm(self, l:int):
        if l>0:
            ValueError("l must be positive")
        return sum([abs(c)**l for c in [self.x, self.y, self.z]])**(1/l)
    