# ***************************************************************************
# *   Copyright (c) 2014 Juergen Riegel <FreeCAD@juergen-riegel.net>        *
# *                                                                         *
# *   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.                                 *
# *                                                                         *
# *   This program 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 this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************

import pathlib
import FreeCAD, Part
from PySide import QtCore
from fcgear import involute
from fcgear import fcgear

if FreeCAD.GuiUp:
    import FreeCADGui
    from PySide import QtGui

__title__ = "PartDesign InvoluteGearObject management"
__author__ = "Juergen Riegel"
__url__ = "https://www.freecad.org"


def makeInvoluteGear(name):
    """makeInvoluteGear(name): makes an InvoluteGear"""
    obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython", name)
    _InvoluteGear(obj)
    if FreeCAD.GuiUp:
        _ViewProviderInvoluteGear(obj.ViewObject)
    # FreeCAD.ActiveDocument.recompute()
    if FreeCAD.GuiUp:
        body = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody")
        part = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part")
        if body:
            body.Group = body.Group + [obj]
        elif part:
            part.Group = part.Group + [obj]
    return obj


class CommandInvoluteGear:
    "GUI command to create an InvoluteGear"

    def GetResources(self):
        return {
            "Pixmap": "PartDesign_InternalExternalGear",
            "MenuText": QtCore.QT_TRANSLATE_NOOP("PartDesign_InvoluteGear", "Involute Gear"),
            "Accel": "",
            "ToolTip": QtCore.QT_TRANSLATE_NOOP(
                "PartDesign_InvoluteGear", "Creates or edits the involute gear definition"
            ),
        }

    def Activated(self):
        FreeCAD.ActiveDocument.openTransaction("Create involute gear")
        FreeCADGui.addModule("InvoluteGearFeature")
        FreeCADGui.doCommand("InvoluteGearFeature.makeInvoluteGear('InvoluteGear')")
        FreeCADGui.doCommand("Gui.activeDocument().setEdit(App.ActiveDocument.ActiveObject.Name,0)")

    def IsActive(self):
        if FreeCAD.ActiveDocument:
            return True
        else:
            return False


class _InvoluteGear:
    "The InvoluteGear object"

    def __init__(self, obj):
        self.Type = "InvoluteGear"
        self._ensure_properties(obj, is_restore=False)
        obj.Proxy = self

    def onDocumentRestored(self, obj):
        """hook used to migrate older versions of this object"""
        self._ensure_properties(obj, is_restore=True)

    def _ensure_properties(self, obj, is_restore):
        def ensure_property(type_, name, doc, default):
            if not hasattr(obj, name):
                obj.addProperty(type_, name, "Gear", doc, locked=True)
                if callable(default):
                    setattr(obj, name, default())
                else:
                    setattr(obj, name, default)

        # for details about the property's docstring translation,
        # see https://tracker.freecad.org/view.php?id=2524
        ensure_property(
            "App::PropertyInteger",
            "NumberOfTeeth",
            doc=QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of gear teeth"),
            default=26,
        )
        ensure_property(
            "App::PropertyLength",
            "Modules",
            doc=QtCore.QT_TRANSLATE_NOOP("App::Property", "Module of the gear"),
            default="2.5 mm",
        )
        ensure_property(
            "App::PropertyAngle",
            "PressureAngle",
            doc=QtCore.QT_TRANSLATE_NOOP("App::Property", "Pressure angle of gear teeth"),
            default="20 deg",
        )
        ensure_property(
            "App::PropertyBool",
            "HighPrecision",
            doc=QtCore.QT_TRANSLATE_NOOP(
                "App::Property",
                "True=2 curves with each 3 control points, False=1 curve with 4 control points.",
            ),
            default=True,
        )
        ensure_property(
            "App::PropertyBool",
            "ExternalGear",
            doc=QtCore.QT_TRANSLATE_NOOP(
                "App::Property", "True=external Gear, False=internal Gear"
            ),
            default=True,
        )
        ensure_property(
            "App::PropertyFloat",
            "AddendumCoefficient",
            doc=QtCore.QT_TRANSLATE_NOOP(
                "App::Property",
                "The height of the tooth from the pitch circle up to its tip, normalized by the module.",
            ),
            default=lambda: 1.0 if obj.ExternalGear else 0.6,
        )
        ensure_property(
            "App::PropertyFloat",
            "DedendumCoefficient",
            doc=QtCore.QT_TRANSLATE_NOOP(
                "App::Property",
                "The height of the tooth from the pitch circle down to its root, normalized by the module.",
            ),
            default=1.25,
        )
        ensure_property(
            "App::PropertyFloat",
            "RootFilletCoefficient",
            doc=QtCore.QT_TRANSLATE_NOOP(
                "App::Property",
                "The radius of the fillet at the root of the tooth, normalized by the module.",
            ),
            default=lambda: 0.375 if is_restore else 0.38,
        )
        ensure_property(
            "App::PropertyFloat",
            "ProfileShiftCoefficient",
            doc=QtCore.QT_TRANSLATE_NOOP(
                "App::Property",
                "The distance by which the reference profile is shifted outwards, normalized by the module.",
            ),
            default=0.0,
        )

    def execute(self, obj):
        w = fcgear.FCWireBuilder()
        generator_func = (
            involute.CreateExternalGear if obj.ExternalGear else involute.CreateInternalGear
        )
        generator_func(
            w,
            obj.Modules.Value,
            obj.NumberOfTeeth,
            obj.PressureAngle.Value,
            split=obj.HighPrecision,
            addCoeff=obj.AddendumCoefficient,
            dedCoeff=obj.DedendumCoefficient,
            filletCoeff=obj.RootFilletCoefficient,
            shiftCoeff=obj.ProfileShiftCoefficient,
        )
        gearw = Part.Wire([o.toShape() for o in w.wire])
        obj.Shape = gearw
        obj.positionBySupport()
        return


class _ViewProviderInvoluteGear:
    "A View Provider for the InvoluteGear object"

    def __init__(self, vobj):
        vobj.Proxy = self

    def getIcon(self):
        return ":/icons/PartDesign_InternalExternalGear.svg"

    def attach(self, vobj):
        self.ViewObject = vobj
        self.Object = vobj.Object

    def setEdit(self, vobj, mode):
        taskd = _InvoluteGearTaskPanel(self.Object, mode)
        taskd.obj = vobj.Object
        taskd.update()
        FreeCADGui.Control.showDialog(taskd)
        return True

    def unsetEdit(self, vobj, mode):
        FreeCADGui.Control.closeDialog()
        return

    def dumps(self):
        return None

    def loads(self, state):
        return None


class _InvoluteGearTaskPanel:
    """The editmode TaskPanel for InvoluteGear objects"""

    def __init__(self, obj, mode):
        self.obj = obj

        self.form = FreeCADGui.PySideUic.loadUi(str(pathlib.Path(__file__).with_suffix(".ui")))
        self.form.setWindowIcon(QtGui.QIcon(":/icons/PartDesign_InternalExternalGear.svg"))
        self.assignToolTipsFromPropertyDocs()

        def assignValue(property_name, fitView=False):
            """Returns a function that takes a single value and assigns it to the given property"""

            def assigner(value):
                setattr(self.obj, property_name, value)
                self.obj.Proxy.execute(self.obj)
                if fitView:
                    FreeCAD.Gui.SendMsgToActiveView("ViewFit")

            return assigner

        def assignIndexAsBool(property_name):
            """Variant of assignValue that transforms the index of a Yes/No Combobox to a bool."""
            assigner = assignValue(property_name)

            def transformingAssigner(value):
                assigner(True if value == 0 else False)

            return transformingAssigner

        self.form.Quantity_Modules.valueChanged.connect(assignValue("Modules", fitView=True))
        self.form.Quantity_PressureAngle.valueChanged.connect(assignValue("PressureAngle"))
        self.form.spinBox_NumberOfTeeth.valueChanged.connect(
            assignValue("NumberOfTeeth", fitView=True)
        )
        self.form.comboBox_HighPrecision.currentIndexChanged.connect(
            assignIndexAsBool("HighPrecision")
        )
        self.form.comboBox_ExternalGear.currentIndexChanged.connect(
            assignIndexAsBool("ExternalGear")
        )
        self.form.doubleSpinBox_Addendum.valueChanged.connect(assignValue("AddendumCoefficient"))
        self.form.doubleSpinBox_Dedendum.valueChanged.connect(assignValue("DedendumCoefficient"))
        self.form.doubleSpinBox_RootFillet.valueChanged.connect(
            assignValue("RootFilletCoefficient")
        )
        self.form.doubleSpinBox_ProfileShift.valueChanged.connect(
            assignValue("ProfileShiftCoefficient")
        )

        self.update()

        if mode == 0:  # fresh created
            self.obj.Proxy.execute(self.obj)  # calculate once
            FreeCAD.Gui.SendMsgToActiveView("ViewFit")

    def assignToolTipsFromPropertyDocs(self):
        def assign(property_name, *widgets):
            doc = self.obj.getDocumentationOfProperty(property_name)
            translated_doc = QtGui.QApplication.translate("App::Property", doc)
            for w in widgets:
                w.setToolTip(translated_doc)

        # we assign the tool tip to both, the label and the input field, for user convenience
        assign("Modules", self.form.Quantity_Modules, self.form.label_Modules)
        assign("PressureAngle", self.form.Quantity_PressureAngle, self.form.label_PressureAngle)
        assign("NumberOfTeeth", self.form.spinBox_NumberOfTeeth, self.form.label_NumberOfTeeth)
        assign("HighPrecision", self.form.comboBox_HighPrecision, self.form.label_HighPrecision)
        assign("ExternalGear", self.form.comboBox_ExternalGear, self.form.label_ExternalGear)
        assign("AddendumCoefficient", self.form.doubleSpinBox_Addendum, self.form.label_Addendum)
        assign("DedendumCoefficient", self.form.doubleSpinBox_Dedendum, self.form.label_Dedendum)
        assign(
            "RootFilletCoefficient", self.form.doubleSpinBox_RootFillet, self.form.label_RootFillet
        )
        assign(
            "ProfileShiftCoefficient",
            self.form.doubleSpinBox_ProfileShift,
            self.form.label_ProfileShift,
        )

    def changeEvent(self, event):
        if event == QtCore.QEvent.LanguageChange:
            self.assignToolTipsFromPropertyDocs()

    def transferTo(self):
        "Transfer from the dialog to the object"
        self.obj.NumberOfTeeth = self.form.spinBox_NumberOfTeeth.value()
        self.obj.Modules = self.form.Quantity_Modules.text()
        self.obj.PressureAngle = self.form.Quantity_PressureAngle.text()
        self.obj.HighPrecision = (
            True if self.form.comboBox_HighPrecision.currentIndex() == 0 else False
        )
        self.obj.ExternalGear = (
            True if self.form.comboBox_ExternalGear.currentIndex() == 0 else False
        )
        self.obj.AddendumCoefficient = self.form.doubleSpinBox_Addendum.value()
        self.obj.DedendumCoefficient = self.form.doubleSpinBox_Dedendum.value()
        self.obj.RootFilletCoefficient = self.form.doubleSpinBox_RootFillet.value()
        self.obj.ProfileShiftCoefficient = self.form.doubleSpinBox_ProfileShift.value()

    def transferFrom(self):
        "Transfer from the object to the dialog"
        self.form.spinBox_NumberOfTeeth.setValue(self.obj.NumberOfTeeth)
        self.form.Quantity_Modules.setText(self.obj.Modules.UserString)
        self.form.Quantity_PressureAngle.setText(self.obj.PressureAngle.UserString)
        self.form.comboBox_HighPrecision.setCurrentIndex(0 if self.obj.HighPrecision else 1)
        self.form.comboBox_ExternalGear.setCurrentIndex(0 if self.obj.ExternalGear else 1)
        self.form.doubleSpinBox_Addendum.setValue(self.obj.AddendumCoefficient)
        self.form.doubleSpinBox_Dedendum.setValue(self.obj.DedendumCoefficient)
        self.form.doubleSpinBox_RootFillet.setValue(self.obj.RootFilletCoefficient)
        self.form.doubleSpinBox_ProfileShift.setValue(self.obj.ProfileShiftCoefficient)

    def getStandardButtons(self):
        return (
            QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Apply
        )

    def clicked(self, button):
        if button == QtGui.QDialogButtonBox.Apply:
            self.transferTo()
            self.obj.Proxy.execute(self.obj)

    def update(self):
        "fills the widgets"
        self.transferFrom()

    def accept(self):
        self.transferTo()
        FreeCAD.ActiveDocument.recompute()
        FreeCADGui.ActiveDocument.resetEdit()

    def reject(self):
        FreeCADGui.ActiveDocument.resetEdit()
        FreeCAD.ActiveDocument.abortTransaction()
