from assembly2.core import *
from PySide import QtGui, QtCore
import traceback
from assembly2.solvers.dof_reduction_solver import degreesOfFreedom

moduleVars = {}

class AnimateCommand:
    def Activated(self):
        from assembly2.solvers import solveConstraints
        constraintSystem = solveConstraints(FreeCAD.ActiveDocument)
        self.taskPanel = AnimateDegreesOfFreedomTaskPanel( constraintSystem )
        FreeCADGui.Control.showDialog( self.taskPanel )
    def GetResources(self): 
        msg = 'Animate degrees of freedom'
        return {
            'Pixmap' : ':/assembly2/icons/degreesOfFreedomAnimation.svg', 
            'MenuText': msg, 
            'ToolTip':  msg
            } 
animateCommand = AnimateCommand()
FreeCADGui.addCommand('assembly2_degreesOfFreedomAnimation', animateCommand)


class AnimateDegreesOfFreedomTaskPanel:
    def __init__(self, constraintSystem):
        self.constraintSystem = constraintSystem
        self.form = FreeCADGui.PySideUic.loadUi( ':/assembly2/ui/degreesOfFreedomAnimation.ui' )
        self.form.setWindowIcon(QtGui.QIcon( ':/assembly2/icons/degreesOfFreedomAnimation.svg' ) )
        self.form.groupBox_DOF.setTitle('%i Degrees-of-freedom:' % len(constraintSystem.degreesOfFreedom))
        for i, d in enumerate(constraintSystem.degreesOfFreedom):
            item = QtGui.QListWidgetItem('%i. %s' % (i+1, str(d)[1:-1].replace('DegreeOfFreedom ','')), self.form.listWidget_DOF)
            if i == 0: item.setSelected(True)
        self.form.pushButton_animateSelected.clicked.connect(self.animateSelected)
        self.form.pushButton_animateAll.clicked.connect(self.animateAll)
        self.form.pushButton_set_as_default.clicked.connect( self.setDefaults )
        self.setIntialValues()
    

    def _startAnimation(self, degreesOfFreedomToAnimate):
        frames_per_DOF =  self.form.spinBox_frames_per_DOF.value()
        ms_per_frame = self.form.spinBox_ms_per_frame.value()
        rotationAmplification = self.form.doubleSpinBox_rotMag.value()
        linearDispAmplification = self.form.doubleSpinBox_linMag.value()
        if len(self.constraintSystem.degreesOfFreedom) > 0:
            moduleVars['animation'] = AnimateDOF(self.constraintSystem, degreesOfFreedomToAnimate, ms_per_frame, frames_per_DOF, rotationAmplification, linearDispAmplification)
            #moduleVars['animation'] assignment required to protect the QTimer from the garbage collector
        else:
            FreeCAD.Console.PrintError('Aborting Animation! Constraint system has no degrees of freedom.')
            FreeCADGui.Control.closeDialog()

    def setIntialValues(self):
        parms = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Assembly2/degreeOfFreedomAnimation")
        form = self.form      
        form.spinBox_frames_per_DOF.setValue(    parms.GetFloat('frames_per_DOF', 42) )
        form.spinBox_ms_per_frame.setValue(      parms.GetFloat('ms_per_frame', 50) )
        form.doubleSpinBox_rotMag.setValue(      parms.GetFloat('rotation_magnitude', 1.0) )
        form.doubleSpinBox_linMag.setValue(      parms.GetFloat('linear_magnitude', 1.0) )
    def setDefaults(self):
        parms = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Assembly2/degreeOfFreedomAnimation")
        form = self.form        
        parms.SetFloat('frames_per_DOF',       form.spinBox_frames_per_DOF.value()       )
        parms.SetFloat('ms_per_frame',         form.spinBox_ms_per_frame.value()       )
        parms.SetFloat('rotation_magnitude',   form.doubleSpinBox_rotMag.value()       )
        parms.SetFloat('linear_magnitude',     form.doubleSpinBox_linMag.value()       )

    def animateSelected(self):
        debugPrint(4,'pushButton_animateSelected has been clicked')
        D_to_animate = []
        for index, d in enumerate( self.constraintSystem.degreesOfFreedom ):
            #debugPrint(4,'getting item at index %i' % index)
            item = self.form.listWidget_DOF.item(index)
            #debugPrint(4,'checking if %s is selected' % d.str())
            if item.isSelected():
                D_to_animate.append( d )
        if len(D_to_animate) > 0:
            self._startAnimation( D_to_animate )
    def animateAll(self):
         self._startAnimation( [d for d in self.constraintSystem.degreesOfFreedom] )

    def reject(self): #or more correctly close, given the button settings
        if  'animation' in moduleVars:
            moduleVars['animation'].timer.stop()
            self.constraintSystem.variableManager.updateFreeCADValues(moduleVars['animation'].X_before_animation)
            del moduleVars['animation']
        FreeCADGui.Control.closeDialog()

    def getStandardButtons(self): #http://forum.freecadweb.org/viewtopic.php?f=10&t=11801
        return 0x00200000




class AnimateDOF(object):
    'based on http://freecad-tutorial.blogspot.com/2014/06/piston-conrod-animation.html'
    def __init__(self, constraintSystem, degreesOfFreedomToAnimate, tick=50, framesPerDOF=40, rotationAmplification=1.0, linearDispAmplification=1.0):
        self.constraintSystem = constraintSystem
        self.degreesOfFreedomToAnimate = degreesOfFreedomToAnimate
        self.Y0 = numpy.array([ d.getValue() for d in degreesOfFreedomToAnimate] )
        self.X_before_animation = constraintSystem.variableManager.X.copy()
        self.framesPerDOF = framesPerDOF
        self.rotationAmplification = rotationAmplification
        self.linearDispAmplification = linearDispAmplification
        debugPrint(2,'beginning degrees of freedom animation')
        self.count = 0
        self.dof_count = 0
        self.updateAmplitude()
        self.timer = QtCore.QTimer()
        QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.renderFrame)
        self.timer.start( tick )

    def updateAmplitude( self) :
        D = self.degreesOfFreedomToAnimate
        if D[self.dof_count].rotational():
            self.amplitude = 1.0 * self.rotationAmplification
        else:
            obj = FreeCAD.ActiveDocument.getObject( D[self.dof_count].objName )
            self.amplitude = obj.Shape.BoundBox.DiagonalLength / 2 * self.linearDispAmplification

    def renderFrame(self):
        try:
            debugPrint(5,'timer loop running')
            self.count = self.count + 1
            D = self.degreesOfFreedomToAnimate
            if self.count > self.framesPerDOF: #placed here so that if is error timer still exits
                self.count = 0
                self.dof_count = self.dof_count + 1
                if base_rotation_dof( D[self.dof_count-1] ):
                    self.dof_count = self.dof_count + 2
                if self.dof_count + 1 > len( D ):
                    self.timer.stop()
                    self.constraintSystem.variableManager.updateFreeCADValues(self.X_before_animation)
                    return
                self.updateAmplitude()
                
            if self.count == 1: debugPrint(3,'animating %s' % D[self.dof_count])
            debugPrint(4,'dof %i, dof frame %i' % (self.dof_count, self.count))
            Y = self.Y0.copy()
            r = 2*numpy.pi*( 1.0*self.count/self.framesPerDOF)
            Y[self.dof_count] = self.Y0[self.dof_count] + self.amplitude * numpy.sin(r)
            if self.dof_count + 2 < len(D):
                if base_rotation_dof( D[self.dof_count] ) and base_rotation_dof( D[self.dof_count+1] ) and base_rotation_dof( D[self.dof_count+2] ): #then also adjust other base rotation degrees of freedom so that rotation is always visible
                    Y[self.dof_count+1] = self.Y0[self.dof_count+1] +self.amplitude*numpy.sin(r)
                    Y[self.dof_count+2] = self.Y0[self.dof_count+2] +self.amplitude*numpy.sin(r)
            debugPrint(5,'Y frame %s, sin(r) %1.2f' % (Y,numpy.sin(r)))
        except:
            self.timer.stop()
            App.Console.PrintError(traceback.format_exc())
            App.Console.PrintError('Aborting animation')
        try:
            for d,y in zip( D, Y):
                d.setValue(y)
                self.constraintSystem.update()
            self.constraintSystem.variableManager.updateFreeCADValues( self.constraintSystem.variableManager.X )
            debugPrint(5,'updated assembly')
        except:
            FreeCAD.Console.PrintError('AnimateDegreeOfFreedom (dof %i, dof frame %i) unable to update constraint system\n'  % (self.dof_count, self.count))
            FreeCAD.Console.PrintError(traceback.format_exc())
        debugPrint(5,'finished timer loop')

def base_rotation_dof(d):
    if isinstance(d,degreesOfFreedom.PlacementDegreeOfFreedom) and  hasattr(d, 'ind'):
        return d.ind % 6 > 2
    return False