#!/usr/bin/env python3
# coding: utf-8
#
# LGPL
# Copyright HUBERT Zoltán
#
# placeDatumCmd.py

import os

from PySide import QtGui, QtCore
import FreeCADGui as Gui
import FreeCAD as App
from FreeCAD import Console as FCC

import libAsm4 as Asm4




"""
    +-----------------------------------------------+
    |               Helper functions                |
    +-----------------------------------------------+
"""
def getSelection():    
    selectedObj = None
    # check that there is an App::Part called 'Model'
    if App.ActiveDocument.getObject('Model') and App.ActiveDocument.getObject('Model').TypeId=='App::Part' :
        # check that something is selected
        if len(Gui.Selection.getSelection())==1:
            selection = Gui.Selection.getSelection()[0]
            if selection.TypeId in datumTypes:
                selectedObj = selection
    # now we should be safe
    return selectedObj


# Types of objects to import
datumTypes = [  'PartDesign::CoordinateSystem', \
                'PartDesign::Plane',            \
                'PartDesign::Line',             \
                'PartDesign::Point']


# icon to show in the Menu, toolbar and widget window
iconFile = os.path.join( Asm4.iconPath , 'Place_Datum.svg')



"""
    +-----------------------------------------------+
    |                  The command                  |
    +-----------------------------------------------+
"""
class placeDatumCmd():
    def __init__(self):
        super(placeDatumCmd,self).__init__()

    def GetResources(self):
        return {"MenuText": "Edit Placement of a Datum object",
                "ToolTip": "Attach a Datum object in the assembly to a Datum in a linked Part",
                "Pixmap" : iconFile
                }

    def IsActive(self):
        if App.ActiveDocument and getSelection():
            return True
        return False

    def Activated(self):
        selectedDatum = getSelection()
        # check if the datum object is already mapped to something
        if selectedDatum.MapMode != 'Deactivated':
            message = 'This datum object \"'+selectedDatum.Label+'\" is mapped to some geometry. Attaching-it with Assembly4 will loose this mapping.'
            if Asm4.confirmBox(message):
                # unset MappingMode
                selectedDatum.Support = None
                selectedDatum.MapMode = 'Deactivated'
                Gui.Control.showDialog( placeDatumUI() )
            else:
                # don't do anything and ...
                return
        else:
            Gui.Control.showDialog( placeDatumUI() )



"""
    +-----------------------------------------------+
    |    The UI and functions in the Task panel     |
    +-----------------------------------------------+
"""
class placeDatumUI():
    def __init__(self):
        self.base = QtGui.QWidget()
        self.form = self.base        
        self.form.setWindowIcon(QtGui.QIcon( iconFile ))
        self.form.setWindowTitle('Place a Datum object in the assembly')

        # get the current active document to avoid errors if user changes tab
        self.activeDoc = App.activeDocument()
        # the parent (top-level) assembly is the App::Part called Model (hard-coded)
        self.parentAssembly = self.activeDoc.Model

        # check that we have selected a PartDesign::CoordinateSystem
        self.selectedDatum = getSelection()
  
        # Now we can draw the UI
        self.drawUI()
        self.initUI()
        # now self.parentList and self.parentTable are available

        # find all the linked parts in the assembly
        for objName in self.parentAssembly.getSubObjects():
            # remove the trailing .
            obj = self.activeDoc.getObject(objName[0:-1])
            if obj.TypeId=='App::Link':
                # add it to our list if it's a link to an App::Part ...
                if hasattr(obj.LinkedObject,'isDerivedFrom'):
                    linkedObj = obj.LinkedObject
                    if linkedObj.isDerivedFrom('App::Part') or linkedObj.isDerivedFrom('PartDesign::Body'):
                        # add to the object table holding the objects ...
                        self.parentTable.append( obj )
                        # ... and add to the drop-down combo box with the assembly tree's parts
                        objIcon = obj.LinkedObject.ViewObject.Icon
                        objText = Asm4.nameLabel(obj)
                        self.parentList.addItem( objIcon, objText, obj)

        self.old_EE = ' '
        # get and store the Placement's current ExpressionEngine:
        self.old_EE = Asm4.placementEE(self.selectedDatum.ExpressionEngine)

        # decode the old ExpressionEngine
        # if the decode is unsuccessful, old_Expression is set to False
        # and old_attPart and old_attLCS are set to 'None'
        old_Parent = ''
        old_ParentPart = ''
        old_attLCS = ''
        ( old_Parent, old_ParentPart, old_attLCS ) = Asm4.splitExpressionDatum( self.old_EE )

        # find the oldPart in the current part list...
        oldPart = self.parentList.findText( old_Parent )
        # if not found
        if oldPart == -1:
            # set to initial "Select linked Part" entry
            self.parentList.setCurrentIndex( 0 )
        else:
            self.parentList.setCurrentIndex( oldPart )

        parent_found = False
        parent_index = 1
        for item in self.parentTable[1:]:
            if item.Name == old_Parent:
                parent_found = True
                break
            else:
                parent_index += 1
        if not parent_found:
            parent_index = 0
        self.parentList.setCurrentIndex( parent_index )
        # this should have triggered self.getPartLCS() to fill the LCS list


        # find the old attachment Datum in the list of the Datums in the linked part...
        # lcs_found = []
        lcs_found = self.attLCSlist.findItems( old_attLCS, QtCore.Qt.MatchExactly )
        if not lcs_found:
            lcs_found = self.attLCSlist.findItems( old_attLCS+' (', QtCore.Qt.MatchContains )
        if lcs_found:
            # ... and select it
            self.attLCSlist.setCurrentItem( lcs_found[0] )


    # this is the end ...
    def finish(self):
        Gui.Control.closeDialog()

    # standard FreeCAD Task panel buttons
    def getStandardButtons(self):
        return int(QtGui.QDialogButtonBox.Cancel
                   | QtGui.QDialogButtonBox.Ok
                   | QtGui.QDialogButtonBox.Ignore)

    # Ignore
    def clicked(self, bt):
        if bt == QtGui.QDialogButtonBox.Ignore:
            # ask for confirmation before resetting everything
            msgName = Asm4.nameLabel(self.selectedDatum)
            # see whether the ExpressionEngine field is filled
            if self.selectedDatum.ExpressionEngine :
                # if yes, then ask for confirmation
                confirmed = Asm4.confirmBox('This command will release all attachments on '+msgName+' and set it to manual positioning in its current location.')
                # if not, then it's useless to bother the user
            else:
                confirmed = True
            if confirmed:
                self.selectedDatum.setExpression('Placement', None)
            self.finish()

    # OK
    def accept(self):
        self.onApply()
        if self.selectedDatum:
            self.selectedDatum.ViewObject.ShowLabel = False
        self.finish()

    # Cancel
    def reject(self):
        # restore previous expression if it existed
        if self.old_EE != None:
            self.selectedDatum.setExpression('Placement', self.old_EE )
        if self.selectedDatum:
            self.selectedDatum.ViewObject.ShowLabel = False
        self.selectedDatum.recompute()
        # highlight the selected LCS in its new position
        Gui.Selection.clearSelection()
        Gui.Selection.addSelection( self.activeDoc.Name, 'Model', self.selectedDatum.Name +'.')
        self.finish()



    """
    +-----------------------------------------------+
    | check that all necessary things are selected, |
    |   populate the expression with the selected   |
    |    elements, put them into the constraint     |
    |   and trigger the recomputation of the part   |
    +-----------------------------------------------+
    """
    def onApply(self):
        # get the name of the part to attach to:
        # it's either the top level part name ('Model')
        # or the provided link's name.
        if self.parentList.currentIndex() > 0:
            parent = self.parentTable[ self.parentList.currentIndex() ]
            a_Link = parent.Name
            a_Part = parent.LinkedObject.Document.Name
        else:
            a_Link = None
            a_Part = None

        # the attachment LCS's name in the parent
        # check that something is selected in the QlistWidget
        if self.attLCSlist.selectedItems():
            a_LCS = self.attLCStable[ self.attLCSlist.currentRow() ].Name
        else:
            a_LCS = None

        # check that all of them have something in
        # constrName has been checked at the beginning
        if not a_Part :
            FCC.PrintWarning("Problem in selections (no a_Part)\n")
        elif not a_LCS :
            FCC.PrintWarning("Problem in selections (no a_LCS)\n")
        else:
            # don't forget the last '.' !!!
            # <<LinkName>>.Placement.multiply( <<LinkName>>.<<LCS.>>.Placement )
            # expr = '<<'+ a_Part +'>>.Placement.multiply( <<'+ a_Part +'>>.<<'+ a_LCS +'.>>.Placement )'
            expr = Asm4.makeExpressionDatum( a_Link, a_Part, a_LCS )
            # load the built expression into the Expression field of the constraint
            self.activeDoc.getObject( self.selectedDatum.Name ).setExpression( 'Placement', expr )
            # recompute the object to apply the placement:
            self.selectedDatum.ViewObject.ShowLabel = True
            self.selectedDatum.ViewObject.FontSize = 20
            self.selectedDatum.recompute()
            # highlight the selected LCS in its new position
            Gui.Selection.clearSelection()
            Gui.Selection.addSelection( self.activeDoc.Name, 'Model', self.selectedDatum.Name +'.')
        return

    '''
    # get all datums in a part
    def getPartLCS( self, part ):
        partLCS = [ ]
        # parse all objects in the part (they return strings)
        for objName in part.getSubObjects(1):
            # get the proper objects
            # all object names end with a "." , this needs to be removed
            obj = part.getObject( objName[0:-1] )
            if obj.TypeId in datumTypes:
                partLCS.append( obj )
            elif obj.TypeId == 'App::DocumentObjectGroup':
                datums = self.getPartLCS(obj)
                for datum in datums:
                    partLCS.append(datum)
        return partLCS
    '''


    # fill the LCS list when changing the parent
    def onParentSelected(self):
        # clear the selection in the GUI window
        Gui.Selection.clearSelection()
        # build the LCS table
        self.attLCStable = []
        # the current text in the combo-box is the link's name...
        if self.parentList.currentIndex() > 0:
            parentName = self.parentTable[ self.parentList.currentIndex() ].Name
            parentPart = self.activeDoc.getObject( parentName )
            if parentPart:
                # we get the LCS from the linked part
                self.attLCStable = Asm4.getPartLCS( parentPart.LinkedObject )
                # linked part & doc
                dText = parentPart.LinkedObject.Document.Name +'#'
                # if the linked part has been renamed by the user
                pText = Asm4.nameLabel( parentPart.LinkedObject )
                self.parentDoc.setText( dText + pText )
                # highlight the selected part:
                Gui.Selection.addSelection( parentPart.Document.Name, 'Model', parentPart.Name+'.' )
        # something wrong
        else:
            return
        
        # build the list
        self.attLCSlist.clear()
        for lcs in self.attLCStable:
            newItem = QtGui.QListWidgetItem()
            newItem.setText(Asm4.nameLabel(lcs))
            newItem.setIcon( lcs.ViewObject.Icon )
            self.attLCSlist.addItem( newItem )
            #self.attLCStable.append(lcs)
        return



    # A target Datum has been clicked in the list
    def onDatumSelected( self ):
        # clear the selection in the GUI window
        Gui.Selection.clearSelection()
        # check that something is selected
        if self.attLCSlist.selectedItems():
            # get the linked part where the selected LCS is
            a_Link = self.parentTable[ self.parentList.currentIndex() ].Name
            # LCS in the linked part
            a_LCS = self.attLCStable[ self.attLCSlist.currentRow() ].Name
            Gui.Selection.addSelection( self.activeDoc.Name, 'Model', a_Link+'.'+a_LCS+'.')
        self.onApply()
        return


    # Rotataions
    def rotAxis( self, addRotation ):
        self.selectedDatum.AttachmentOffset = self.selectedDatum.AttachmentOffset.multiply(addRotation) 
        self.selectedDatum.recompute()

    def onRotX(self):
        self.rotAxis(Asm4.rotX)

    def onRotY(self):
        self.rotAxis(Asm4.rotY)

    def onRotZ(self):
        self.rotAxis(Asm4.rotZ)


    # Initialises the UI once the widget has stared
    def initUI(self):
        self.lscName.setText( Asm4.nameLabel(self.selectedDatum) )
        self.parentDoc.clear()
        self.attLCSlist.clear()
        # list and table containing the available parents in the assembly to choose from
        self.parentList.clear()
        self.parentTable = []
        self.parentList.addItem( 'Please select' )
        self.parentTable.append( [] )


    # defines the UI, only static elements
    def drawUI(self):
        # the layout for the main window is vertical (top to down)
        self.mainLayout = QtGui.QVBoxLayout(self.form)
        
        # Selected Datum
        self.mainLayout.addWidget(QtGui.QLabel('Selected Datum :'))
        self.lscName = QtGui.QLineEdit()
        self.lscName.setReadOnly(True)
        self.mainLayout.addWidget(self.lscName)

        # combobox showing all available App::Link
        self.mainLayout.addWidget(QtGui.QLabel('Attach to :'))
        self.parentList = QtGui.QComboBox()
        self.mainLayout.addWidget(self.parentList)
        # the document containing the linked object
        self.parentDoc = QtGui.QLineEdit()
        self.parentDoc.setReadOnly(True)
        self.mainLayout.addWidget(self.parentDoc)

        # The list of all attachment LCS in the assembly is a QListWidget
        # it is populated only when the parent combo-box is activated
        self.mainLayout.addWidget(QtGui.QLabel('Select LCS in Parent :'))
        self.attLCSlist = QtGui.QListWidget()
        self.mainLayout.addWidget(self.attLCSlist)

        # Rot Buttons
        self.rotButtonsLayout = QtGui.QHBoxLayout()
        # RotX button
        self.RotXButton = QtGui.QPushButton('Rot X')
        self.RotXButton.setToolTip("Rotate the Datum around the X axis by 90deg")
        # RotY button
        self.RotYButton = QtGui.QPushButton('Rot Y')
        self.RotYButton.setToolTip("Rotate the Datum around the Y axis by 90deg")
        # RotZ button
        self.RotZButton = QtGui.QPushButton('Rot Z')
        self.RotZButton.setToolTip("Rotate the Datum around the Z axis by 90deg")
        # add the buttons
        self.rotButtonsLayout.addStretch()
        self.rotButtonsLayout.addWidget(self.RotXButton)
        self.rotButtonsLayout.addWidget(self.RotYButton)
        self.rotButtonsLayout.addWidget(self.RotZButton)
        self.rotButtonsLayout.addStretch()
        self.mainLayout.addLayout(self.rotButtonsLayout)

        # apply the layout to the main window
        self.form.setLayout(self.mainLayout)

        # Actions
        self.parentList.currentIndexChanged.connect( self.onParentSelected )
        self.attLCSlist.currentItemChanged.connect( self.onDatumSelected )
        self.attLCSlist.itemClicked.connect( self.onDatumSelected )
        self.RotXButton.clicked.connect( self.onRotX )
        self.RotYButton.clicked.connect( self.onRotY )
        self.RotZButton.clicked.connect( self.onRotZ)




"""
    +-----------------------------------------------+
    |       add the command to the workbench        |
    +-----------------------------------------------+
"""
Gui.addCommand( 'Asm4_placeDatum', placeDatumCmd() )