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

from math import sqrt

from compas.plugins import pluggable
from compas.geometry import Point
from compas.geometry import Frame

from .curve import Curve


@pluggable(category="factories")
def new_nurbscurve(cls, *args, **kwargs):
    raise NotImplementedError


@pluggable(category="factories")
def new_nurbscurve_from_parameters(cls, *args, **kwargs):
    raise NotImplementedError


@pluggable(category="factories")
def new_nurbscurve_from_points(cls, *args, **kwargs):
    raise NotImplementedError


@pluggable(category="factories")
def new_nurbscurve_from_interpolation(cls, *args, **kwargs):
    raise NotImplementedError


@pluggable(category="factories")
def new_nurbscurve_from_step(cls, *args, **kwargs):
    raise NotImplementedError


class NurbsCurve(Curve):
    """A NURBS curve is defined by control points, weights, knots, and a degree.

    Parameters
    ----------
    name : str, optional
        The name of the curve.

    Attributes
    ----------
    points : list[:class:`~compas.geometry.Point`], read-only
        The control points.
    weights : list[float], read-only
        The weights of the control points.
    knots : list[float], read-only
        The knots, without multiplicity.
    knotsequence : list[float], read-only
        The complete knot vector.
    multiplicity : list[int], read-only
        The multiplicities of the knots.
    continuity : int, read-only
        The degree of continuity of the curve.
    degree : int, read-only
        The degree of the curve.
    order : int, read-only
        The order of the curve (degree + 1).

    """

    JSONSCHEMA = {
        "type": "object",
        "properties": {
            "points": {"type": "array", "minItems": 2, "items": Point.JSONSCHEMA},
            "weights": {"type": "array", "items": {"type": "number"}},
            "knots": {"type": "array", "items": {"type": "number"}},
            "multiplicities": {"type": "array", "items": {"type": "integer"}},
            "degree": {"type": "integer", "exclusiveMinimum": 0},
            "is_periodic": {"type": "boolean"},
        },
        "additionalProperties": False,
        "minProperties": 6,
    }

    def __new__(cls, *args, **kwargs):
        return new_nurbscurve(cls, *args, **kwargs)

    def __init__(self, name=None):
        super(NurbsCurve, self).__init__(name=name)

    def __eq__(self, other):
        raise NotImplementedError

    def __str__(self):
        lines = [
            "NurbsCurve",
            "----------",
            "Points: {}".format(self.points),
            "Weights: {}".format(self.weights),
            "Knots: {}".format(self.knots),
            "Mults: {}".format(self.multiplicities),
            "Degree: {}".format(self.degree),
            "Order: {}".format(self.order),
            "Domain: {}".format(self.domain),
            "Closed: {}".format(self.is_closed),
            "Periodic: {}".format(self.is_periodic),
            "Rational: {}".format(self.is_rational),
        ]
        return "\n".join(lines)

    # ==============================================================================
    # Data
    # ==============================================================================

    @property
    def dtype(self):
        """str : The type of the object in the form of a '2-level' import and a class name."""
        return "compas.geometry/NurbsCurve"

    @property
    def data(self):
        """dict : Representation of the curve as a dict containing only native Python data."""
        return {
            "points": self.points,
            "weights": self.weights,
            "knots": self.knots,
            "multiplicities": self.multiplicities,
            "degree": self.degree,
            "is_periodic": self.is_periodic,
        }

    @data.setter
    def data(self, data):
        raise NotImplementedError

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

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

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`
            The constructed curve.

        """
        points = data["points"]
        weights = data["weights"]
        knots = data["knots"]
        multiplicities = data["multiplicities"]
        degree = data["degree"]
        is_periodic = data["is_periodic"]
        return cls.from_parameters(points, weights, knots, multiplicities, degree, is_periodic)

    # ==============================================================================
    # Properties
    # ==============================================================================

    @property
    def points(self):
        raise NotImplementedError

    @property
    def weights(self):
        raise NotImplementedError

    @property
    def knots(self):
        raise NotImplementedError

    @property
    def knotsequence(self):
        raise NotImplementedError

    @property
    def multiplicities(self):
        raise NotImplementedError

    @property
    def continuity(self):
        raise NotImplementedError

    @property
    def degree(self):
        raise NotImplementedError

    @property
    def order(self):
        return self.degree + 1

    @property
    def is_rational(self):
        raise NotImplementedError

    # ==============================================================================
    # Constructors
    # ==============================================================================

    @classmethod
    def from_step(cls, filepath):
        """Load a NURBS curve from an STP file.

        Parameters
        ----------
        filepath : str
            The path to the file.

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`
        """
        return new_nurbscurve_from_step(cls, filepath)

    @classmethod
    def from_parameters(cls, points, weights, knots, multiplicities, degree, is_periodic=False):
        """Construct a NURBS curve from explicit curve parameters.

        Parameters
        ----------
        points : list[[float, float, float] | :class:`~compas.geometry.Point`]
            The control points.
        weights : list[float]
            The weights of the control points.
        knots : list[float]
            The curve knots, without multiplicity.
        multiplicities : list[int]
            Multiplicity of the knots.
        degree : int
            Degree of the curve.
        is_periodic : bool, optional
            Flag indicating that the curve is periodic.

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`

        """
        return new_nurbscurve_from_parameters(cls, points, weights, knots, multiplicities, degree, is_periodic=False)

    @classmethod
    def from_points(cls, points, degree=3):
        """Construct a NURBS curve from control points.

        Parameters
        ----------
        points : list[[float, float, float] | :class:`~compas.geometry.Point`]
            The control points.
        degree : int, optional
            The degree of the curve.

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`

        """
        return new_nurbscurve_from_points(cls, points, degree=degree)

    @classmethod
    def from_interpolation(cls, points, precision=1e-3):
        """Construct a NURBS curve by interpolating a set of points.

        Parameters
        ----------
        points : list[[float, float, float] | :class:`~compas.geometry.Point`]
            A list of interpolation points.
        precision : int, optional
            The desired precision of the interpolation.

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`

        """
        return new_nurbscurve_from_interpolation(cls, points, precision=1e-3)

    @classmethod
    def from_arc(cls, arc):
        """Construct a NURBS curve from an arc.

        Parameters
        ----------
        arc : :class:`~compas.geometry.Arc`

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`

        """
        raise NotImplementedError

    @classmethod
    def from_circle(cls, circle):
        """Construct a NURBS curve from a circle.

        Parameters
        ----------
        circle : :class:`~compas.geometry.Circle`

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`

        """
        frame = Frame.from_plane(circle.plane)
        w = 0.5 * sqrt(2)
        dx = frame.xaxis * circle.radius
        dy = frame.yaxis * circle.radius
        points = [
            frame.point - dy,
            frame.point - dy - dx,
            frame.point - dx,
            frame.point + dy - dx,
            frame.point + dy,
            frame.point + dy + dx,
            frame.point + dx,
            frame.point - dy + dx,
            frame.point - dy,
        ]
        knots = [0, 1 / 4, 1 / 2, 3 / 4, 1]
        mults = [3, 2, 2, 2, 3]
        weights = [1, w, 1, w, 1, w, 1, w, 1]
        return cls.from_parameters(points=points, weights=weights, knots=knots, multiplicities=mults, degree=2)

    @classmethod
    def from_ellipse(cls, ellipse):
        """Construct a NURBS curve from an ellipse.

        Parameters
        ----------
        ellipse : :class:`~compas.geometry.Ellipse`

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`

        """
        frame = Frame.from_plane(ellipse.plane)
        frame = Frame.worldXY()
        w = 0.5 * sqrt(2)
        dx = frame.xaxis * ellipse.major
        dy = frame.yaxis * ellipse.minor
        points = [
            frame.point - dy,
            frame.point - dy - dx,
            frame.point - dx,
            frame.point + dy - dx,
            frame.point + dy,
            frame.point + dy + dx,
            frame.point + dx,
            frame.point - dy + dx,
            frame.point - dy,
        ]
        knots = [0, 1 / 4, 1 / 2, 3 / 4, 1]
        mults = [3, 2, 2, 2, 3]
        weights = [1, w, 1, w, 1, w, 1, w, 1]
        return cls.from_parameters(points=points, weights=weights, knots=knots, multiplicities=mults, degree=2)

    @classmethod
    def from_line(cls, line):
        """Construct a NURBS curve from a line.

        Parameters
        ----------
        line : :class:`~compas.geometry.Line`

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`

        """
        return cls.from_parameters(
            points=[line.start, line.end],
            weights=[1.0, 1.0],
            knots=[0.0, 1.0],
            multiplicities=[2, 2],
            degree=1,
        )

    # ==============================================================================
    # Conversions
    # ==============================================================================

    # ==============================================================================
    # Methods
    # ==============================================================================

    def copy(self):
        """Make an independent copy of the current curve.

        Returns
        -------
        :class:`~compas.geometry.NurbsCurve`

        """
        return NurbsCurve.from_parameters(
            self.points,
            self.weights,
            self.knots,
            self.multiplicities,
            self.degree,
            self.is_periodic,
        )
