# (c) 2014 David Douard <david.douard@gmail.com>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU Lesser General Public License (LGPL)
#   as published by the Free Software Foundation; either version 2 of
#   the License, or (at your option) any later version.
#   for detail see the LICENCE text file.
#
#   FCGear 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 Library General Public License for more details.
#
#   You should have received a copy of the GNU Library General Public
#   License along with FCGear; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307

from math import acos

import FreeCAD, Part
from FreeCAD import Base
from . import sprocket

rotate = sprocket.rotate


def makeSprocket(P, N, Dr):
    if FreeCAD.ActiveDocument is None:
        FreeCAD.newDocument("Sprocket")
    doc = FreeCAD.ActiveDocument
    w = FCWireBuilder()
    sprocket.CreateSprocket(w, P, N, Dr)
    sprocketw = Part.Wire([o.toShape() for o in w.wire])
    sprocketf = doc.addObject("Part::Feature", "Sprocket")
    sprocketf.Shape = sprocketw
    return sprocketf


class FCWireBuilder(object):
    """A helper class to prepare a Part.Wire object"""

    def __init__(self):
        self.pos = None
        self.theta = 0.0
        self.wire = []

    def move(self, p):
        """set current position"""
        self.pos = Base.Vector(*p)

    def line(self, p):
        """Add a segment between self.pos and p"""
        p = rotate(p, self.theta)
        end = Base.Vector(*p)
        self.wire.append(Part.LineSegment(self.pos, end))
        self.pos = end

    def arc(self, p, r, sweep):
        """ "Add an arc from self.pos to p which radius is r
        sweep (0 or 1) determine the orientation of the arc
        """
        p = rotate(p, self.theta)
        end = Base.Vector(*p)
        mid = Base.Vector(*(midpoints(p, self.pos, r)[sweep]))
        self.wire.append(Part.Arc(self.pos, mid, end))
        self.pos = end

    def curve(self, *points):
        """Add a Bezier curve from self.pos to points[-1]
        every other points are the control points of the Bezier curve (which
        will thus be of degree len(points) )
        """
        points = [Base.Vector(*rotate(p, self.theta)) for p in points]
        bz = Part.BezierCurve()
        bz.setPoles([self.pos] + points)
        self.wire.append(bz)
        self.pos = points[-1]

    def close(self):
        pass


def midpoints(p1, p2, r):
    """A very ugly function that returns the midpoint of a p1 and p2
    on the circle which radius is r and which pass through p1 and
    p2

    Return the 2 possible solutions
    """
    vx, vy = p2[0] - p1[0], p2[1] - p1[1]
    b = (vx**2 + vy**2) ** 0.5
    v = (vx / b, vy / b)
    cosA = b**2 / (2 * b * r)
    A = acos(cosA)

    vx, vy = rotate(v, A)
    c1 = (p1[0] + r * vx, p1[1] + r * vy)
    m1x, m1y = ((p1[0] + p2[0]) / 2 - c1[0], (p1[1] + p2[1]) / 2 - c1[1])
    dm1 = (m1x**2 + m1y**2) ** 0.5
    m1x, m1y = (c1[0] + r * m1x / dm1, c1[1] + r * m1y / dm1)
    m1 = (m1x, m1y)

    vx, vy = rotate(v, -A)
    c2 = (p1[0] + r * vx, p1[1] + r * vy)
    m2x, m2y = ((p1[0] + p2[0]) / 2 - c2[0], (p1[1] + p2[1]) / 2 - c2[1])
    dm2 = (m2x**2 + m2y**2) ** 0.5
    m2x, m2y = (c2[0] + r * m2x / dm2, c2[1] + r * m2y / dm2)
    m2 = (m2x, m2y)

    return m1, m2
