
from typing import Tuple, Union
import traceback
import numbers
import math

from geogst.core.geometries.shapes.points import Point
from geogst.core.geometries.shapes.segments import Segment
from geogst.core.utils.inspections.errors import Error
from geogst.core.utils.inspections.functions import caller_name
from geogst.core.mathematics.utils import are_close
from geogst.core.mathematics.vectors3d import Vect3D
from geogst.core.utils.types import check_type


class ParamLine3D(object):
    """
    A parametric line is defined by a source point
    and three scalar line coefficients (l, m, n).
    """

    def __init__(self,
                 srcPt: Point,
                 l: numbers.Real,
                 m: numbers.Real,
                 n: numbers.Real
            ):

        check_type(srcPt, "Source point", Point)

        self.srcPt = srcPt.clone()
        norm = math.sqrt(l*l + m*m + n*n)
        self.l = l / norm
        self.m = m / norm
        self.n = n / norm

    def __repr__(self):

        return f"ParamLine3D({self.srcPt}, {self.l:.4f}, {self.m:.4f}, {self.n:.4f})"

    def is_point_on_line(self,
        pt: Point
    ) -> Tuple[Union[None, bool], Error]:
        """
        Determines if a point lies on the line.

        :param pt: the point to check for.
        :result: whether the point lies on the line and an error message.
        """

        try:

            if are_close(pt.distance(self.srcPt), 0):
                return True, Error()

            versor_pt, err = Segment(self.srcPt, pt).as_vector3d().to_versor()
            if err:
                return None, err

            versor_ln = Vect3D(self.l, self.m, self.n)

            cross_vect, err = versor_pt.cross_vector(versor_ln)

            if err:
                return None, err

            if cross_vect.is_close_to_zero():
                return True, Error()

            return False, Error()

        except Exception as e:

            return None, Error(True, caller_name(), e, traceback.format_exc())

    def as_versor(self) -> Vect3D:
        """
        Converts a parametric line to a versor.

        Examples:
        >>> par_ln = ParamLine3D(Point(4,2,8), 6, 9, 3)
        >>> par_ln.as_versor()
        Vect3D(0.5345, 0.8018, 0.2673)
        """

        return Vect3D(self.l, self.m, self.n)
