from __future__ import print_function
from __future__ import absolute_import
from __future__ import division

from compas.geometry import allclose
from compas.geometry import argmax
from compas.geometry import axis_angle_vector_from_matrix
from compas.geometry import basis_vectors_from_matrix
from compas.geometry import cross_vectors
from compas.geometry import decompose_matrix
from compas.geometry import euler_angles_from_matrix
from compas.geometry import matrix_from_axis_angle_vector
from compas.geometry import matrix_from_basis_vectors
from compas.geometry import matrix_from_euler_angles
from compas.geometry import matrix_from_quaternion
from compas.geometry import quaternion_from_matrix
from compas.geometry import subtract_vectors
from compas.geometry import Transformation

from compas.geometry.primitives import Point
from compas.geometry.primitives import Primitive
from compas.geometry.primitives import Quaternion
from compas.geometry.primitives import Vector


class Frame(Primitive):
    """A frame is defined by a base point and two orthonormal base vectors.

    Parameters
    ----------
    point : [float, float, float] | :class:`~compas.geometry.Point`
        The origin of the frame.
    xaxis : [float, float, float] | :class:`~compas.geometry.Vector`
        The x-axis of the frame.
    yaxis : [float, float, float] | :class:`~compas.geometry.Vector`
        The y-axis of the frame.

    Attributes
    ----------
    point : :class:`~compas.geometry.Point`
        The base point of the frame.
    xaxis : :class:`~compas.geometry.Vector`
        The local X axis of the frame.
    yaxis : :class:`~compas.geometry.Vector`
        The local Y axis of the frame.
    zaxis : :class:`~compas.geometry.Vector`, read-only
        The Z axis of the frame.
    normal : :class:`~compas.geometry.Vector`, read-only
        The normal of the base plane of the frame.
    quaternion : :class:`~compas.geometry.Quaternion`, read-only
        The quaternion from the rotation given by the frame.
    axis_angle_vector : :class:`~compas.geometry.Vector`, read-only
        The axis-angle vector representing the rotation of the frame.

    Notes
    -----
    All input vectors are orthonormalized when creating a frame, with the first
    vector as starting point.

    Examples
    --------
    >>> from compas.geometry import Point
    >>> from compas.geometry import Vector
    >>> f = Frame([0, 0, 0], [1, 0, 0], [0, 1, 0])
    >>> f = Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0))

    """

    def __init__(self, point, xaxis, yaxis, **kwargs):
        super(Frame, self).__init__(**kwargs)
        self._point = None
        self._xaxis = None
        self._yaxis = None
        self.point = point
        self.xaxis = xaxis
        self.yaxis = yaxis

    # ==========================================================================
    # data
    # ==========================================================================

    @property
    def DATASCHEMA(self):
        """:class:`schema.Schema` : Schema of the data representation."""
        from schema import Schema
        return Schema({
            'point': Point.DATASCHEMA.fget(None),
            'xaxis': Vector.DATASCHEMA.fget(None),
            'yaxis': Vector.DATASCHEMA.fget(None)
        })

    @property
    def JSONSCHEMANAME(self):
        """str : Name of the schema of the data representation in JSON format."""
        return 'frame'

    @property
    def data(self):
        """dict : The data dictionary that represents the frame."""
        return {'point': self.point.data,
                'xaxis': self.xaxis.data,
                'yaxis': self.yaxis.data}

    @data.setter
    def data(self, data):
        self.point = Point.from_data(data['point'])
        self.xaxis = Vector.from_data(data['xaxis'])
        self.yaxis = Vector.from_data(data['yaxis'])

    @classmethod
    def from_data(cls, data):
        """Construct a frame from its data representation.

        Parameters
        ----------
        data : dict
            The data dictionary.

        Returns
        -------
        :class:`~compas.geometry.Frame`
            The constructed frame.

        Examples
        --------
        >>> data = {'point': [0.0, 0.0, 0.0], 'xaxis': [1.0, 0.0, 0.0], 'yaxis': [0.0, 1.0, 0.0]}
        >>> frame = Frame.from_data(data)
        >>> frame.point
        Point(0.000, 0.000, 0.000)
        >>> frame.xaxis
        Vector(1.000, 0.000, 0.000)
        >>> frame.yaxis
        Vector(0.000, 1.000, 0.000)

        """
        frame = cls(Point.from_data(data['point']), Vector.from_data(data['xaxis']), Vector.from_data(data['yaxis']))
        return frame

    # ==========================================================================
    # properties
    # ==========================================================================

    @property
    def point(self):
        return self._point

    @point.setter
    def point(self, point):
        self._point = Point(*point)