# coding: utf-8
#
# Copyright © 2020 MSC.Software Corporation
#  
# Permission to use Scripts
#  
# Permission is granted to MSC.Software customers with valid licenses of the MSC Apex™ software 
# to use script files posted on this page (collectively “Scripts”) solely for customers’ own authorized 
# internal use in conjunction with licensed use of the MSC Apex software.  
# No other use is permitted.  No license to MSC Apex or other software is granted under this Permission.
#  
# SCRIPTS ARE PROVIDED ON AN "AS-IS" BASIS, WITH NO WARRANTIES OF ANY KIND.  USE OF THE SCRIPTS IS AT CUSTOMER'S SOLE RISK.
#  
# IN NO EVENT SHALL MSC.SOFTWARE CORPORATION, ITS AFFILIATES, SUBSIDIARIES, SUPPLIERS, AGENTS, REPRESENTATIVES OR EMPLOYEES BE LIABLE FOR ANY 
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, 
# STRICT LIABILITY, OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION USE OF THE SCRIPTS, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
#  
# Customer shall indemnify, defend and hold MSC.Software Corporation, its affiliates, subsidiaries, suppliers, agents, 
# employees or representatives (collectively, “Indemnified Parties”) harmless from and against any third-party claims 
# brought against Indemnified Parties rising out of or in connection with Customer’s use of the Scripts.
#

import apex
from apex.construct import Point3D, Point2D

import math


def calcNodeDist(n1, n2):
    n1Loc = n1.getLocation()
    n2Loc = n2.getLocation()

    dx = n2Loc.getX() - n1Loc.getX()
    dy = n2Loc.getY() - n1Loc.getY()
    dz = n2Loc.getZ() - n1Loc.getZ()

    dist = dx**2 + dy**2 + dz**2
    if dist > 0.0:
        dist = math.sqrt(dist)

    return dist


def processEdges(tol, _selected):
    nodes = apex.entityCollection()
    for sel in _selected:
        if sel.getEntityType() != apex.EntityType.Edge: continue

        nodes += sel.getNodes()

    print(nodes)

    return nodes


def main(dict={}):
    unitSystem = r'''in-slinch-s-lbf-degF-deg'''
    print("Using unit system: "+unitSystem)
    myModel = apex.currentModel()

    tol = float(dict["tolerance"])
    print("Tolerance: "+str(tol))

    _selected = apex.selection.getCurrentSelection()

    if _selected.len() == 0:
        print("Nothing selected?")
        return

    if _selected[0].getEntityType() != apex.EntityType.Node:
        nodes = processEdges(tol, _selected)
        selectedNodes = nodes
    else:
        selectedNodes = _selected

    print("Selected nodes: "+str(len(selectedNodes)))
    if selectedNodes.len() == 0:
        print("No nodes were selected?")
        return

    allParts = myModel.getParts(True)
    meshBodies = apex.entityCollection()
    for part in allParts:
        meshBodies += part.getMeshes()
    print("Total mesh bodies: "+str(len(meshBodies)))
    if meshBodies.len() == 0:
        print("There are no mesh bodies in the database?")
        return

    # this is a work-around for beta-2
    allNodes = {}
    for mesh in meshBodies:
        nn = mesh.getNodes()
        nodes = []
        for n in nn:
            nodes.append(n.getId())
        allNodes[mesh.getParent().getName()+"-"+mesh.getName()] = nodes

    keys = allNodes.keys()

    selectedMeshes = []
    selectedData   = {}
    for node in selectedNodes:
        for mesh in meshBodies:
            key = mesh.getParent().getName()+"-"+mesh.getName()
            nodes = allNodes[key]

            breakFlag = False
            if node.getId() in nodes:
                if (selectedData.get(key)):
                    selectedData[key].append(node)
                else:
                    selectedData[key] = [node]
                breakFlag = True
            if breakFlag: break

    keys = selectedData.keys()
    numKeys = len(keys)
    print("Mesh Bodies: "+str(numKeys))
    if numKeys < 2:
        print("Nodes must come from at least 2 mesh bodies!")
        print(keys)
        return
    if numKeys > 2:
        print("At this time, only 2 mesh bodies allowed!")
        print(keys)
        return

    #keyList = []
    #for key in keys:
    #	keyList.append(key)
    #	nodes = selectedData[key]
    #	nodeLocs = apex.IPhysicalCollection()
    #	for node in nodes:
    #		nodeLocs.append(node)
    #	mySearch = apex.utility.ProximitySearch()
    #	mySearch.insertCollection(nodeLocs)
    #
    #	nearObjects = mySearch.findNearestObjects(nodeLocs, 3)

    #below is inefficient but it will do for now
    #the mesh body with the max minDist will be the reference body
    meshData = {}
    keyList = []
    nodeDist = {}
    for key in keys:
        keyList.append(key)
        nodes = selectedData[key]
        minDist = 999999999.0
        for n1 in nodes:
            n1Id = n1.getId()
            for n2 in nodes:
                if n1Id == n2.getId(): continue
                dist = calcNodeDist(n1, n2)
                #nodeDist[str(n1Id)].append([n2.getId(), dist])
                if dist == 0.0: continue
                if dist < minDist: minDist = dist
        meshData[key] = minDist

    if meshData[keyList[0]] > meshData[keyList[1]]:
        maxDist  = meshData[keyList[0]]
        refKey   = keyList[0]
        indepKey = keyList[1]
    else:
        maxDist  = meshData[keyList[1]]
        refKey   = keyList[1]
        indepKey = keyList[0]

    maxDist = maxDist*0.99

    #
    # a better distance might be variable, based on the distances to the 2 closest ref nodes
    #

    print("Reference body:   "+refKey)
    print("Reference nodes selected: "+str(len(selectedData[refKey])))
    print("Independent body: "+indepKey)
    print("Independent nodes selected: "+str(len(selectedData[indepKey])))

    #refKey is pointing to the reference body
    #indepKey is pointing to body with nodes to be connected

    allLocs = apex.IPhysicalCollection()
    for node in selectedData[indepKey]:
        allLocs.append(node.asNode())

    mySearch = apex.utility.ProximitySearch()
    mySearch.insertCollection(allLocs)

    print("Max dist: "+str(maxDist))
    refNodesUsed = []
    indepNodesUsed = []
    for node in selectedData[refKey]:
        nearByObjects = mySearch.findObjectsWithinDistance(location=node.getLocation(), distance = maxDist)

        nearObjects = nearByObjects.foundObjects()
        nearDist    = nearByObjects.getDistances()
        nearVectors = nearByObjects.getVectors()

        if nearObjects.len() == 0: continue

        if node.getId() not in refNodesUsed:
            refNodesUsed.append(node.getId())

        _referenceRegion = node

        _attachmentRegion = apex.EntityCollection()
        for obj in nearObjects:
            _attachmentRegion.append(obj)
            if obj.asNode().getId() not in indepNodesUsed:
                indepNodesUsed.append(obj.asNode().getId())

        _dofAndFactors = {}
        k = -1
        for nAttach in nearObjects:
            k += 1
            # I am just setting the factors to unit for the moment
            _factors = {}
            _factors["1"]=1.0
            _factors["2"]=1.0
            _factors["3"]=1.0
            #if nearVectors[k].getX() != 0.0:
            #	_factors["1"] = nearVectors[k].getX()
            #if nearVectors[k].getY() != 0.0:
            #	_factors["2"] = nearVectors[k].getY()
            #if nearVectors[k].getZ() != 0.0:
            #	_factors["3"] = nearVectors[k].getZ()

            _dofAndFactors[mesh.getParent().getName()+":"+str(nAttach.getId())] = _factors

        # nodeTie = apex.attribute.createNodeTie(
            # name = "Node Tie",
            # distributionType   = apex.attribute.DistributionType.Compliant,
            # distributionMode   = apex.attribute.DistributionMode.Custom,
            # referencePoint     = _referenceRegion,
            # attachmentRegions  = _attachmentRegion,
            # dependentDoF       = "123456", 
            # DOFAndWeightFactors = _dofAndFactors,
            # description         = "Elvis has left the building ...")
        nodetie_1 = apex.attribute.createNodeTie(
            name = "Node Tie",
            distributionType  = apex.attribute.DistributionType.Rigid,
            distributionMode  = apex.attribute.DistributionMode.Auto,
            dofDefinitionMode  = apex.attribute.DOFDefinitionMode.All,
            referencePoint  = _referenceRegion,
            attachmentRegions  = _attachmentRegion,
            description         = "Elvis has left the building ...")

    print("Reference nodes used: "+str(len(refNodesUsed)))
    print("Independent nodes used: "+str(len(indepNodesUsed)))






