
from typing import Union, Self, List, Any, Optional, Iterable
from numpy import False_
from sympy import Matrix
from math import sqrt
from uuid import uuid4, UUID

"""
This module will be used to process the geometric locations of points, vectors and operations between them.

It will be used for the spacial processing of Pipes and other components, so that the positioning 
and elevation of the components are correctly calculated
"""

class Point():
    def __init__(self,
                 x: Union[Optional[int],Optional[float]],
                 y: Union[Optional[int], Optional[float]],
                 z: Union[Optional[int], Optional[float]]):

        self.coords: List[Union[Optional[int], Optional[float]]] = [x, y, z]

        self.id: UUID = uuid4()

        #if isinstance(*args, List) == True:

    def has_none_coord(self) -> bool:
        if any(obj == None for obj in self.coords) == True:
            return True
        
        elif all(obj != None for obj in self.coords) == True:
            
            return False
            
        else:
            
            return False
        
    def is_generic(self) -> bool:

        if all(obj == None for obj in self.coords):
            return True
        
        else:
            return False
    """  

    def __add__(self, other):
        try:
            #assert isinstance(self, Point) == True
            assert isinstance(other, Point) == True

            assert all(obj != None for obj in self.coords) == True
            assert all(obj != None for obj in other.coords) == True
        
        except AssertionError:
            raise TypeError(f'\nSome of the objects are not from type Vector')
        
        new_x = self.coords[0] + other.coords[0]
        new_y = self.coords[1] + other.coords[1]
        new_z = self.coords[2] + other.coords[2]

        new_point = Point(new_x, new_y, new_z)

        return new_vector

    def __sub__(self, other):
        try:
            assert isinstance(self, Self) == True
            assert isinstance(other, Self) == True

        except AssertionError:
            raise TypeError(f'\nSome of the objects are not from type Vector')
        
        try:
            assert Point.has_none_coord(self) == False
            assert Point.has_none_coord(other) == False

        except AssertionError:
            raise ValueError(f'\nSome of the points have coordinates == None')
        
        new_x = self.coords[0] - other.coords[0]
        new_y = self.coords[1] - other.coords[1]
        new_z = self.coords[2] - other.coords[2]

        new_point: Self = Point(new_x, new_y, new_z)

        return new_point
    """

    def __ne__(self, other: Union[Self, None]) -> bool:
        """
        This method is the one used to compare is the self object is different from the other object
        """

        #print(f'\n\n{other = }, {type(other) = }\n\n')

        if (other is None):
            if all(coord == None for coord in self.coords) == False:
                return True
            
            elif all(coord == None for coord in self.coords) == True:
                return False
            #elif any(coord == None)

            else:
                raise ValueError
        
        assert  isinstance(other, Point) == True
        if isinstance(other, Point):
            for coord_index, coord in enumerate(self.coords):
                if coord == other.coords[coord_index]:
                    return False

                else:
                   pass
            
            
            return True
        
        else:
            raise TypeError(f'\n\nThe other object is from {type(other) = }, and is not supported by this method')

    def __eq__(self, other: Union[Self, None]) -> bool:
        #print(f'\n\n{other = }, {type(other) = }\n\n')

        if (other is None):
            if all(coord == None for coord in self.coords) == True:
                return True
            
            elif all(coord == None for coord in self.coords) == False:
                return False
            #elif any(coord == None)

            else:
                raise ValueError
            
        if isinstance(other, Point):

            assert other is not None

            for coord_index, coord in enumerate(self.coords):

                assert coord is not None
                assert coord_index is not None

                if coord == other.coords[coord_index]:
                    pass

                else:
                    return False
            
            
            return True
        
        else:
            raise TypeError(f'\n\nThe other object is from {type(other) = }, and is not supported by this method')

    def __repr__(self):
        """
        This method will be called when some piece of code tries to create a repsenteation to this object, simmilar to
        when this object is turned into a string, but this method is speciffic to when this object is represented in text
        with or without being converted into a string
        """

        return f'({self.coords[0]}, {self.coords[1]}, {self.coords[2]})'

    def __str__(self):
        """
        """

        return f'({self.coords[0]}, {self.coords[1]}, {self.coords[2]})'

class Vector():
    def __init__(self, x = None, y = None, z = None, points: Optional[List[Point]] = None) -> None:



        self.points = points
        self.coords: Point = Point(None, None, None)

        if ((self.points != None) and (all(obj != None for obj in self.points) == True)):
            try:
                assert len(self.points) == 2

            except AssertionError:
                raise ValueError(f'\nThe self.points list should contain two elements, this error means that there is more or less than two points onside this list')
            
            for point in self.points:

                if ((Point.has_none_coord(point) == False) and  (Point.is_generic(point) == False)):
                    pass

                else:
                    raise ValueError(f'\nSome of the points passed in the self.points list have None Values inside its coordinates')           

            assert points is not None
            assert points[1].coords[0] is not None
            assert points[1].coords[1] is not None
            assert points[1].coords[2] is not None

            assert points[0].coords[0] is not None
            assert points[0].coords[1] is not None
            assert points[0].coords[2] is not None

            self.coords.coords[0] = points[1].coords[0] - points[0].coords[0]
            self.coords.coords[1] = points[1].coords[1] - points[0].coords[1]
            self.coords.coords[2] = points[1].coords[2] - points[0].coords[2]


        if ((x != None) and (y != None) and (z != None)):
            self.coord = Point(x, y, z)
        
        if ((x == None) and (y == None) and (z == None) and (self.points == None) and (self.coords == None)):
            """
            This is the case where the Vector is created inside the __init__ method, and it should be substituted later
            by the rigth coordinates if the user does not call the __add__ or __radd__ methods from the components
            """
            pass

        # else:
        #     raise ValueError(f'\nThe vector creation went wrong')

    def __add__(self: Self, other: Self) -> Self:
        """
        addition method made to add two vectors
        """

        try:
            #assert isinstance(self, Self) == True
            assert isinstance(other, Vector) == True

            assert self.coords.coords[0] is not None
            assert self.coords.coords[1] is not None
            assert self.coords.coords[2] is not None

            assert other.coords.coords[0] is not None
            assert other.coords.coords[1] is not None
            assert other.coords.coords[2] is not None

            assert all(obj != None for obj in self.coords.coords) == True
            assert all(obj != None for obj in other.coords.coords) == True
        
        except AssertionError:
            raise TypeError(f'\nSome of the objects are not from type Vector')
        
        new_x = self.coords.coords[0] + other.coords.coords[0]
        new_y = self.coords.coords[1] + other.coords.coords[1]
        new_z = self.coords.coords[2] + other.coords.coords[2]

        new_vector: Self = Vector(new_x, new_y, new_z)

        return new_vector

    def __sub__(self: Self, other: Self) -> Self:
        """
        subtraction method made to subtract two vectors
        """
        try:
            #assert isinstance(self, Self) == True
            assert isinstance(other, Vector) == True

            assert all(obj != None for obj in self.coords) == True
            assert all(obj != None for obj in other.coords) == True
        
        except AssertionError:
            raise TypeError(f'\nSome of the objects are not from type Vector')
        
        new_x = self.coords[0] - other.coords[0]
        new_y = self.coords[1] - other.coords[1]
        new_z = self.coords[2] - other.coords[2]

        new_vector = Vector(new_x, new_y, new_z)

        return new_vector

    def cross_product(self: Self, other: Self) -> Self:
        """
        this method is made to return the vectorial product of two vectors

        i need to create another method to make the scalar product of two vectors
        """




        new_vector = Vector(new_x, new_y, new_z)
    
    def dot_product(self, other) -> Union[int, float]:
        """
        This method will be used to calculate the scalar product between two vectors
        """

        try:
            #assert isinstance(self, Self) == True
            assert isinstance(other, Vector) == True

            assert all(obj != None for obj in self.coords) == True
            assert all(obj != None for obj in other.coords) == True
        
        except AssertionError:
            raise TypeError(f'\nSome of the objects are not from type Vector')


        scalar_product = (self.coords[0] * other.coords[0]) + (self.coords[1] * other.coords[1]) + (self.coords[2] * other.coords[2])

        return scalar_product

    def length(self) -> Union[int, float]:

        length: Union[int, float] = sqrt((self.coords[0]**2) + (self.coords[1]**2) + (self.coords[2]**2))

        return length

    def __repr__(self):
        """
        This method will be called when some piece of code tries to create a repsenteation to this object, simmilar to
        when this object is turned into a string, but this method is speciffic to when this object is represented in text
        with or without being converted into a string
        """

        return f'({self.coords.coords[0]}, {self.coords.coords[1]}, {self.coords.coords[2]})'

    def __str__(self):
        """
        """

        return f'({self.coords.coords[0]}, {self.coords.coords[1]}, {self.coords.coords[2]})'

class Plane():
    def __init__(self, points, vectors):
        self.points: List[Point]
        self.vectors: List[Vector]

class Distance():
    """
    The objects of this class will be used to calculate the distance between two objects, independent of their type
    """
    def __init__(self, obj1, obj2) -> Union[int, float]:
        self.distance: Union[int, float]

        match obj1, obj2:
            case x, y if (isinstance(obj1, Point) == True) and (isinstance(obj2, Point) == True):
                new_vec: Vector = Vector(points = [obj1, obj2])

                return new_vec.length
