# -*- coding: utf-8 -*-

############################################################################
#
#   Copyright (C) 2008-2015
#    Christian Kohlöffel
#    Vinzenz Schulz
#
#   This file is part of DXF2GCODE.
#
#   DXF2GCODE is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   DXF2GCODE is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with DXF2GCODE.  If not, see <http://www.gnu.org/licenses/>.
#
############################################################################

from __future__ import absolute_import
from __future__ import division

from math import sqrt, sin, cos, atan2, degrees, pi

from dxf2gcode.core.point import Point
from dxf2gcode.dxfimport.biarc import BiarcClass
from dxf2gcode.dxfimport.classes import PointsClass, ContourClass
from dxf2gcode.globals.helperfunctions import a2u

import dxf2gcode.globals.globals as g


class GeoentEllipse(object):
    """
    GeoentEllipse()
    """
    def __init__(self, Nr=0, caller=None):
        self.Typ = 'Ellipse'
        self.Nr = Nr
        # Initialisieren der Werte
        # Initialise the values
        self.Layer_Nr = 0
        self.center = Point(0, 0)  # Centre of the geometry
        self.vector = Point(1, 0)  #  Vector A = semi-major axis.
                                   #  a = rotation of the ellipse
                                   #  http://de.wikipedia.org/wiki/Gro%C3%9Fe_Halbachse
        self.ratio = 1             # Verh�ltnis der kleinen zur gro�en Halbachse (b/a)
                                   # Ratio of the minor to major axis (b/a)
        # self.AngS = 0            # Startwinkel beim zeichnen eines Ellipsensegments
                                   # Starting angle when drawing an ellipse segment
        # self.AngE = radians(360) # Endwinkel (Winkel im DXF als Radians!)
                                   # End angle (angle in radians as DXF!)
        # Die folgenden Grundwerte werden sp�ter ein mal berechnet
        # The following limits are calculated later

        self.length = 0
        self.Points = []
        self.Points.append(self.center)
        # Lesen der Geometrie / Read the geometry
        self.Read(caller)

        # Zuweisen der Toleranz f�rs Fitting / Assign the tolerance for fitting
        tol = g.config.fitting_tolerance

        # Errechnen der Ellipse / Calculate the ellipse
        self.Ellipse_Grundwerte()
        self.Ellipse_2_Arcs(tol)

    def __repr__(self):
        # how to print the object
        # As elegant as printf in C or Matlab etc. see the first line!
        return "\nEllipse:" +\
               "\n\tNr:     " + str(self.Nr) +\
               "\n\tLayer:  " + str(self.Layer_Nr) +\
               "\n\tcenter: " + str(self.center) +\
               "\n\tvector: " + str(self.vector) +\
               "\n\tratio:  " + str(self.ratio) +\
               "\n\tangles: " + str(degrees(self.AngS)) + " -> " + str(degrees(self.AngE)) +\
               "\n\textend: " + str(degrees(self.ext)) +\
               "\n\ta:      " + str(self.a) +\
               "\n\tb:      " + str(self.b) +\
               "\n\tlength: " + str(self.length) +\
               "\n\tNarcs:  " % str(len(self.geo))

    def reverse(self):
        """
        reverse()
        """
        self.geo.reverse()
        for geo in self.geo:
            geo.reverse()

    def App_Cont_or_Calc_IntPts(self, cont, points, i, tol):
        """
        App_Cont_or_Calc_IntPts()
        """
        # Hinzuf�gen falls es keine geschlossene Polyline ist
        # Add if it is not a closed polyline
        if self.geo[0].Ps.within_tol(self.geo[-1].Pe, tol):
            self.analyse_and_opt()
            cont.append(ContourClass(len(cont), 1, [[i, 0]], self.length))
        else:
            points.append(PointsClass(point_nr=len(points), geo_nr=i,
                                      Layer_Nr=self.Layer_Nr,
                                      be=self.geo[0].Ps,
                                      en=self.geo[-1].Pe, be_cp=[], en_cp=[]))
        return True

    def Read(self, caller):
        """
        Read()
        """
        # Assign short name
        lp = caller.line_pairs
        e = lp.index_code(0, caller.start + 1)

        # Assign Layer
        s = lp.index_code(8, caller.start + 1)
        self.Layer_Nr = caller.Get_Layer_Nr(a2u(lp.line_pair[s].value))

        # Centre X value, Y value
        s = lp.index_code(10, s + 1)
        x0 = float(lp.line_pair[s].value)
        s = lp.index_code(20, s + 1)
        y0 = float(lp.line_pair[s].value)
        self.center = Point(x0, y0)
        # XWert, YWert. Vektor, relativ zum Zentrum, Gro�e Halbachse
        # X value, Y value. Vector relative to the center, Semi-major axis
        s = lp.index_code(11, s + 1)
        x1 = float(lp.line_pair[s].value)
        s = lp.index_code(21, s + 1)
        y1 = float(lp.line_pair[s].value)
        self.vector = Point(x1, y1)
        # Ratio minor to major axis
        s = lp.index_code(40, s + 1)
        self.ratio = float(lp.line_pair[s].value)
        # Start Winkel - Achtung, ist als rad (0-2pi) im dxf
        # Start angle - Note in radian (0-2pi) per dxf
        s = lp.index_code(41, s + 1)
        self.AngS = float(lp.line_pair[s].value)
        # End Winkel - Achtung, ist als rad (0-2pi) im dxf
        # End angle - Note in radian (0-2pi) per dxf
        s = lp.index_code(42, s + 1)
        self.AngE = float(lp.line_pair[s].value)
        # Neuen Startwert f�r die n�chste Geometrie zur�ckgeben
        # New starting value for the next geometry return
        caller.start = e

    def analyse_and_opt(self):
        """
        analyse_and_opt()
        """
        # Richtung in welcher der Anfang liegen soll (unten links)
        # Direction of top (lower left) ???
        Popt = Point(-1e3, -1e6)

        # Suchen des kleinsten Startpunkts von unten Links X zuerst (Muss neue Schleife sein!)
        # Find the smallest starting point from bottom left X (Must be new loop!)
        min_distance = self.geo[0].Ps.distance(Popt)
        min_geo_nr = 0
        for geo_nr in range(1, len(self.geo)):
            if self.geo[geo_nr].Ps.distance(Popt) < min_distance:
                min_distance = self.geo[geo_nr].Ps.distance(Popt)
                min_geo_nr = geo_nr

        # Kontur so anordnen das neuer Startpunkt am Anfang liegt
        # Contour so the new starting point is at the start order
        self.geo = self.geo[min_geo_nr:len(self.geo)] + self.geo[0:min_geo_nr]

    def get_start_end_points(self, direction=0):
        """
        get_start_end_points()
        """
        if not direction :
            punkt, angle = self.geo[0].get_start_end_points(direction)
        else:
            punkt, angle = self.geo[-1].get_start_end_points(direction)
        return punkt, angle

    def Ellipse_2_Arcs(self, tol):
        """
        Ellipse_2_Arcs()
        """
        # Anfangswert f�r Anzahl Elemente
        # Initial value for number of elements
        num_elements = 2
        intol = False

        # print degrees(self.AngS)
        # print tol

        while not intol:
            intol = True

            # Anfangswete Ausrechnen
            # Calculate Anfangswete ???
            angle = self.AngS
            Ps = self.Ellipse_Point(angle)
            tana = self.Ellipse_Tangent(angle)

            self.geo = []
            self.PtsVec = []
            self.PtsVec.append([Ps, tana])

            for sec in range(num_elements):
                # Calculate Increment
                step = self.ext / num_elements
                # print degrees(step)

                # Calculate final values
                Pb = self.Ellipse_Point(angle + step)
                tanb = self.Ellipse_Tangent(angle + step)

                # Biarc erstellen und an geo anh�ngen
                # Biarc create and attach them ???
                biarcs = BiarcClass(Ps, tana, Pb, tanb, tol / 100)
                self.geo += biarcs.geos[:]

                # Last value = Start value
                Ps = Pb
                tana = tanb

                self.PtsVec.append([Ps, tana])

                if not(self.check_ellipse_fitting_tolerance(biarcs, tol, angle, angle + step)):
                    intol = False
                    num_elements += 1
                    break

                # Calculate new angle
                angle += step
        # print degrees(angle)
        # print self

    def check_ellipse_fitting_tolerance(self, biarc, tol, ang0, ang1):
        """
        check_ellipse_fitting_tolerance()
        """
        check_step = (ang1 - ang0) / 4
        check_ang = []
        check_Pts = []
        fit_error = []

        for i in range(1, 4):
            check_ang.append(ang0 + check_step * i)
            check_Pts.append(self.Ellipse_Point(check_ang[-1]))
            fit_error.append(biarc.get_biarc_fitting_error(check_Pts[-1]))

        if max(fit_error) >= tol:
            return 0
        else:
            return 1

    def Ellipse_Grundwerte(self):
        """
        Ellipse_Grundwerte()
        """
        # Other values of the ellipse that are calculated only once
        self.rotation = atan2(self.vector.y, self.vector.x)
        self.a = sqrt(self.vector.x ** 2 + self.vector.y ** 2)
        self.b = self.a * self.ratio

        # Calculate angle to extend
        self.ext = self.AngE - self.AngS
        # self.ext=self.ext%(-2*pi)
        # self.ext-=floor(self.ext/(2*pi))*(2*pi)

    def Ellipse_Point(self, alpha=0):  # Point(0,0)
        """
        Ellipse_Point()
        """
        # gro�e Halbachse, kleine Halbachse, rotation der Ellipse (rad), Winkel des Punkts in der Ellipse (rad)
        # Semi-major axis, minor axis, rotation of the ellipse (rad), the point in the ellipse angle (rad) ???
        Ex = self.a * cos(alpha) * cos(self.rotation) - self.b * sin(alpha) * sin(self.rotation)
        Ey = self.a * cos(alpha) * sin(self.rotation) + self.b * sin(alpha) * cos(self.rotation)
        return Point(self.center.x + Ex, self.center.y + Ey)

    def Ellipse_Tangent(self, alpha=0):  # Point(0,0)
        """
        Ellipse_Tanget()
        """
        # gro�e Halbachse, kleine Halbachse, rotation der Ellipse (rad), Winkel des Punkts in der Ellipse (rad)
        # Semi-major axis, minor axis, rotation of the ellipse (rad), the point in the ellipse angle (rad) ???
        phi = atan2(self.a * sin(alpha), self.b * cos(alpha)) + self.rotation + pi / 2
        return phi
