import System
import clr
import templatetools

clr.AddReference("Ansys.ACT.Interfaces")
import Ansys
from Ansys.ACT.Interfaces.Common import *
from Ansys.ACT.Interfaces.Geometry import *
from Ansys.ACT.Interfaces.Mesh import *


#------------------------------------------------------------
#       Scoping
#------------------------------------------------------------

class ScopingObj:

    def __init__(self, api, entity, prop):
        self.api  = api
        self.entity = entity
        self.prop = prop

    def internalonadd(self, entity, prop):
        propDefBy = prop.Properties["DefineBy"]

        if entity.GetType().Name == "MechanicalUserResult":
            selinfo = self.api.SelectionManager.CurrentSelection
            if selinfo.Ids.Count == 0 and IsInFilter(prop, 'body'):
                propDefBy.Value = "All Bodies"
                propDefBy.Controller.allowAllBodies = True
                propDefBy.Attributes['previous'] = 'All Bodies'

    def oninit(self, entity, prop):
        if prop.Attributes['_is_first_init'] != 'false':
            self.internalonadd(entity, prop)
            prop.Attributes['_is_first_init'] = 'false' 

        propDefBy = prop.Properties["DefineBy"]
        propGeo   = prop.Properties["DefineBy/Geo"]
        propNS    = prop.Properties["DefineBy/NamedSelection"]

        # Only base scoping object muste have 
        # the "default_scoping" attribute.
        propDefBy.Attributes.Remove("default_scoping")
        propGeo.Attributes.Remove("default_scoping")
        propNS.Attributes.Remove("default_scoping")

        if IsDefaultScoping(entity, prop):
            prop.Attributes['default_scoping'] = 'true'
        else:
            prop.Attributes['default_scoping'] = 'false'

        default = prop.Attributes["default_scoping"]
        if default == 'true':
            propGeo.Attributes["default_geometry"] = 'true'
            propNS.Attributes["default_component"] = 'true'

        filter = prop.Attributes["selection_filter"]
        if filter != None:
            propGeo.Attributes["selection_filter"] = filter
            propNS.Attributes["selection_filter"] = filter

        check = prop.Attributes.GetValue("check_suppressed_bodies", "true")
        propGeo.Attributes["check_suppressed_bodies"] = check

    def getvalue(self, entity, prop, val):
        try:
            propDefBy = prop.Properties["DefineBy"]

            if propDefBy.Value == "All Bodies":
                return AllBodiesSelection(self.api)
            elif propDefBy.Value == "Geometry Selection":
                return propDefBy.Properties["Geo"].Value
            else:
                return propDefBy.Properties["NamedSelection"].Value
        except:
            return self.api.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities)

    def setvalue(self, entity, prop, val):

        if val == None: return None
        propDefBy = prop.Properties["DefineBy"]

        if clr.GetClrType(ISelectionInfo).IsAssignableFrom(val.GetType()):
            propDefBy.Value = "Geometry Selection"
            propDefBy.Properties["Geo"].Value = val
        elif type(val) == int: #TODO : Accepte ns
            propDefBy.Value = "Named Selection"
            propDefBy.Properties["NamedSelection"].Value = val

        return None

    def isvalid(self, entity, prop):
        propDefBy = prop.Properties["DefineBy"]
        if propDefBy.Value == "All Bodies":
            return True
        elif propDefBy.Value == "Geometry Selection":
            propGeo = propDefBy.Properties["Geo"]
            return propGeo.Controller.isvalid(entity, propGeo, True)
        else:
            propNS = propDefBy.Properties["NamedSelection"]
            return propNS.Controller.isvalid(entity, propNS, True)

    # Public
    def updateSelection(self):
        propDefBy = prop.Properties["DefineBy"]
        if propDefBy.Value == "Geometry Selection":
            return propDefBy.Properties["Geo"].Controller.updateSelection()

#------------------------------------------------------------
#       Def By
#------------------------------------------------------------

class DefByObj:

    def __init__(self, api, entity, prop):
        self.api = api
        self.allowAllBodies = False

    def oninit(self, entity, prop):

        # Only base scoping object muste have 
        # the "default_scoping" attribute.
        prop.Attributes.Remove("default_scoping")

    def onactivate(self, entity, prop):
        prop.Options.Clear()
        prop.Options.Add('Geometry Selection')
        prop.Options.Add('Named Selection')
        if IsInFilter(prop.ParentPropertyGroup, 'body') and (self.allowAllBodies or prop.InternalValue == "All Bodies"):
            prop.Options.Add('All Bodies')

    def onvalidate(self, entity, prop):
        if prop.Value == "All Bodies":
            self.updateAllBodies(entity, prop)
        elif prop.Value == "Named Selection":
            self.updateNamedSelection(entity, prop)
            self.allowAllBodies = False
        elif prop.Value == "Geometry Selection":
            self.updateGeometry(entity, prop)
            self.allowAllBodies = False
        prop.Attributes['previous'] = prop.Value

    def updateAllBodies(self, entity, prop):
        default = prop.ParentPropertyGroup.Attributes["default_scoping"]
        if default == 'true':
            self.entity.SetDefaultSelection(None)

    def updateNamedSelection(self, entity, prop):
        prop.Properties["NamedSelection"].Controller.update()

    def updateGeometry(self, entity, prop):
        newSelection = None
        if prop.Attributes['previous'] == 'All Bodies':
            newSelection = AllBodiesSelection(self.api)
        elif prop.Attributes['previous'] == 'Named Selection':
            onchangeObj = prop.Properties['NamedSelection'].Controller.onchangeObj
            if onchangeObj!=None:
                onchangeObj.Changed -= prop.Properties['NamedSelection'].Controller.onchange
                prop.Properties['NamedSelection'].Controller.onchangeObj = None
            namedSelection = prop.Properties['NamedSelection'].Value
            if namedSelection != None: 
                newSelection = self.api.SelectionManager.CreateSelectionInfo(namedSelection.SelectionType)
                newSelection.Ids = namedSelection.Ids
            else:
                newSelection = prop.Properties['Geo'].Value
                if newSelection == None:
                    newSelection = self.api.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities)
        prop.Properties['Geo'].Controller.internalsetvalue(newSelection, False, checkvalidity=False)

#------------------------------------------------------------
#       Named Selection
#------------------------------------------------------------

class NamedSelectionObj:

    def __init__(self, api, entity, prop):
        self.api = api
        self.entity = entity
        self.prop = prop

        # Change this !
        self.needUpdate = False
        self.onchangeObj = None

    def oninit(self, entity, prop):

        # Only base scoping object muste have 
        # the "default_scoping" attribute.
        prop.Attributes.Remove("default_scoping")

    def onduplicate(self, entity, prop):
        self.needUpdate = True

    def isComponentValidById(self, id):
        selection = self.api.DataModel.ComponentById(id)
        return self.isComponentValid(selection)

    def isComponentValid(self, selection):
        return IsSelectionValid(self.api, self.prop, selection)

    def onactivate(self, entity, prop):
        prop.Options.Clear()
        comps = self.api.DataModel.Components
        for comp in comps:
            if self.isComponentValid(comp):
                prop.Options.Add(str(comp.Id))

    def onvalidate(self, entity, prop):
        self.update()
        callScopingOnValidate(prop)

    def value2string(self, entity, prop, val):
        if val == None: return ""
        if val == ""  : return ""
        comp =  self.api.DataModel.ComponentById(int(val))
        if comp == None: return ""
        return comp.Name

    def getvalue(self, entity, prop, val):
        if val == None: return None
        return  self.api.DataModel.ComponentById(int(val))

    def isvalid(self, entity, prop, callFromParent=False):
        if self.needUpdate: 
            self.update()
            self.needUpdate = False
        if not prop.Visible: 
            return True
        selection = prop.Value
        if selection!=self.onchangeObj:
            if self.onchangeObj!=None:
                self.onchangeObj.Changed -= self.onchange
                self.onchangeObj = None
            if selection!=None:
                selection.Changed += self.onchange
                self.onchangeObj = selection
        if isParentScoping(prop) and not callFromParent:
            return callScopingIsValid(prop)
        return self.isComponentValid(prop.Value)

    def onchange(self, sel, args):
        self.entity.NotifyChange()

    def setvalue(self, entity, prop, val):
        value = None
        try:
            value = int(val)
        except:
            return
        if value==None: return
        if not self.isComponentValidById(value): return
        if prop.Value!=None:
            prop.Value.Changed -= self.onchange
        selection = self.api.DataModel.ComponentById(value)
        if selection!=None:
            selection.Changed += self.onchange
        self.update(selection)
        return value

    def update(self, selection=None):
        default = self.prop.Attributes["default_component"]
        if default == "true":
            if selection == None:
                selection = self.prop.Value
            self.entity.SetDefaultSelection(selection)


#------------------------------------------------------------
#       Geometry Selection
#------------------------------------------------------------

class GeometryObj:

    def __init__(self, api, entity, prop):
        self.api = api
        self.entity = entity
        self.prop = prop

        # Change this !
        self.needUpdate = False

    def internalonadd(self, entity, prop):
        default = prop.Attributes.GetValue("default_geometry", "false")
        if default == "true":
            selection = self.api.SelectionManager.CurrentSelection
            self.internalsetvalue(selection, computeids=False, update=True)
            self.api.SelectionManager.ClearSelection()

    def oninit(self, entity, prop):
        if prop.Attributes['_is_first_init'] != "false":
            self.internalonadd(entity, prop)
            prop.Attributes['_is_first_init'] = 'false'

        # Only base scoping object muste have 
        # the "default_scoping" attribute.
        prop.Attributes.Remove("default_scoping")

        currentSelection = prop.InternalValue
        self.validSelection = currentSelection
        self.fullSelection = prop.Attributes["_full_selection"]
        if self.fullSelection == None:
            self.internalsetvalue(currentSelection)

        self.checkSuppressedBodies = True
        check = prop.Attributes.GetValue("check_suppressed_bodies", "true")
        if check == "false": self.checkSuppressedBodies = False

    def onduplicate(self, entity, prop):
        self.needUpdate = True

    def onactivate(self, entity, prop):
        currentSel = self.api.SelectionManager.CurrentSelection
        propSel = prop.Value
        if len(currentSel.Ids) == 0 and propSel != None:
            self.api.SelectionManager.AddSelection(propSel)

    def onapply(self, entity, prop):
        selection = self.api.SelectionManager.CurrentSelection
        self.internalsetvalue(selection, False)
        self.api.SelectionManager.ClearSelection()
        callScopingOnValidate(prop)

    def value2string(self, entity, prop, val):
        if val == None : return ""
        if val.SelectionType == SelectionTypeEnum.MeshNodes: 
            if self.checkSuppressedBodies: val = prop.Value
            else: val = prop.InternalValue
        if len(val.Ids) == 0: return ""

        txt = ""
        if val.SelectionType == SelectionTypeEnum.GeometryEntities:
            cell = self.api.DataModel.GeoData.GeoEntityById(val.Ids[0])
            if len(val.Ids) > 1:
                if cell.Type == GeoCellTypeEnum.GeoBody    : txt = "Bodies"
                elif cell.Type == GeoCellTypeEnum.GeoFace  : txt = "Faces"
                elif cell.Type == GeoCellTypeEnum.GeoEdge  : txt = "Edges"
                elif cell.Type == GeoCellTypeEnum.GeoVertex: txt = "Vertices"
            else:
                if cell.Type == GeoCellTypeEnum.GeoBody    : txt = "Body"
                elif cell.Type == GeoCellTypeEnum.GeoFace  : txt = "Face"
                elif cell.Type == GeoCellTypeEnum.GeoEdge  : txt = "Edge"
                elif cell.Type == GeoCellTypeEnum.GeoVertex: txt = "Vertex"

        if val.SelectionType == SelectionTypeEnum.MeshNodes:
            if len(val.Ids) > 1: txt = "Nodes"
            else: txt = "Node"

        return '{0} {1}'.format(len(val.Ids), txt)

    def isvalid(self, entity, prop, callFromParent=False):
        if self.needUpdate: 
            self.update()
            self.needUpdate = False
        if not prop.Visible: return True
        if isParentScoping(prop) and not callFromParent:
            return callScopingIsValid(prop)
        if prop.InternalValue != None and prop.InternalValue.SelectionType == SelectionTypeEnum.MeshNodes and not self.checkSuppressedBodies:
            return IsSelectionValid(self.api, prop, prop.InternalValue)
        else:
            return IsSelectionValid(self.api, prop, prop.Value)

    def internalsetvalue(self, selection, computeids=True, update=True, checkvalidity=True):
        value = self.setvalue(self.entity, self.prop, selection, computeids, update, checkvalidity)
        if value != None: self.prop.InternalValue = value

    def setvalue(self, entity, prop, value, computeids=True, update=True, checkvalidity=True):

        if value == None: return
        if not (clr.GetClrType(ISelectionInfo).IsAssignableFrom(value.GetType())): return
        if checkvalidity and not IsSelectionValid(self.api, self.prop, value): return
        selection = value

        if selection.SelectionType == SelectionTypeEnum.MeshNodes:
            computeids = False

        self.fullSelection = selection
        self.validSelection = self.api.SelectionManager.CreateSelectionInfo(selection.SelectionType)
        self.validSelection.Ids = selection.Ids

        if computeids:
            fullIds, validIds = GetGeoIds(self.api, selection)
            self.validSelection.Ids = validIds
            self.fullSelection.Ids = fullIds

        if update: self.update(self.validSelection)
        self.prop.Attributes["_full_selection"] = self.fullSelection

        return self.validSelection

    def getvalue(self, entity, prop, selection):
        if selection != None and selection.SelectionType == SelectionTypeEnum.MeshNodes:
            validSelection = self.api.SelectionManager.CreateSelectionInfo(selection.SelectionType)
            fullIds, validIds = GetGeoIds(self.api, selection)
            validSelection.Ids = validIds
            return validSelection
        return selection

    def update(self, selection=None, notify=False):
        if selection == None: selection = self.prop.Value
        default = self.prop.Attributes.GetValue("default_geometry", "false")
        if default == "true":
            if selection == None:
                selection = self.api.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities)
            self.entity.SetDefaultSelection(selection, notify)

    # GeoEvent
    def onbodysuppressstatechange(self, bodyId, suppressed):

        if self.validSelection == None: return
        if self.fullSelection == None: return
        if len(self.fullSelection.Ids) == 0: return

        body = self.api.DataModel.GeoData.GeoEntityById(bodyId)

        if self.fullSelection.SelectionType == SelectionTypeEnum.GeometryEntities:
            cellType = GetGeoCellType(self.api, self.fullSelection)

            ids = []
            if   cellType == GeoCellTypeEnum.GeoBody: ids = [body.Id]
            elif cellType == GeoCellTypeEnum.GeoFace: ids = body.FaceIds
            elif cellType == GeoCellTypeEnum.GeoEdge: ids = body.EdgeIds
            elif cellType == GeoCellTypeEnum.GeoVertex: ids = body.VertexIds
            ids = set(ids)
        else:
            return
            #mesh = self.api.DataModel.MeshDataByName('Global')
            #region = mesh.MeshRegionById(bodyId)
            #ids = set(region.NodeIds)

        if suppressed: 
            suppressedIds = set(self.validSelection.Ids).intersection(ids)
            suppressedIds = set(FilterForSuppressedBodies(self.api, suppressedIds))
            validIds = set(self.validSelection.Ids).difference(suppressedIds)
        else:
            inIds = set(self.fullSelection.Ids).intersection(ids)       
            validIds = set(self.validSelection.Ids).union(inIds)

        self.validSelection.Ids = list(validIds)

    def onaftergeometryupdate(self):
        self.updateSelection()

    # Public
    def updateSelection(self):
        """
        Force the internal selection to be updated. Recompute all 
        the ids and update the default values.
        """
        selection = self.fullSelection
        if self.fullSelection != None and self.fullSelection.SelectionType == SelectionTypeEnum.MeshNodes:
            selection = self.prop.Value
        self.internalsetvalue(selection, update=True, checkvalidity=False)


# --------------------------------------------------------
#    Scoping Tools
# --------------------------------------------------------

def IsInFilter(prop, value):
    if value == None: return False
    filters = templatetools.ParseFilter(prop.Attributes["selection_filter"])
    if filters == None: return value != 'node'
    return value in filters

def IsSelectionValid(api, prop, selection):
    """
    Check if the given selection is valid.
    """
    if selection == None: return False
    if len(selection.Ids) == 0: return False

    if not prop.Attributes["selection_filter"]: return True

    f = None
    if selection.SelectionType == SelectionTypeEnum.GeometryEntities:
        cellType = GetGeoCellType(api, selection)
        if   cellType == GeoCellTypeEnum.GeoBody  : f = 'body'
        elif cellType == GeoCellTypeEnum.GeoFace  : f = 'face'
        elif cellType == GeoCellTypeEnum.GeoEdge  : f = 'edge'
        elif cellType == GeoCellTypeEnum.GeoVertex: f = 'vertex'
    if selection.SelectionType == SelectionTypeEnum.MeshNodes:
        f = 'node'

    return IsInFilter(prop, f)

def AllBodiesSelection(api):
    """
    Return a selection of all unsuppressed bodies.
    """
    selinfo = api.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities)
    geo = api.DataModel.GeoData
    ids = []
    for assembly in geo.Assemblies:
        for part in assembly.Parts:
            ids += [body.Id for body in part.Bodies if not body.Suppressed]
    selinfo.Ids = ids
    return selinfo

def FilterForSuppressedBodies(api, ids):
    """
    Return the ids wich are realy suppressed. An entity is
    suppressed if all his parent bodies are suppressed.
    """
    suppressedIds = []
    geo = api.DataModel.GeoData
    for id in ids:
        entity = geo.GeoEntityById(id)
        if entity.Type == GeoCellTypeEnum.GeoBody:
            suppressed = entity.Suppressed
        else:
            suppressed = True
            for bodies in entity.Bodies:
                if bodies.Suppressed == False:
                    suppressed = False
                    break
        if suppressed:
            suppressedIds.append(id)

    return suppressedIds

def GetGeoIds(api, selection):
    """
    Return a tuple of lists with:
      - All the existing ids in the selection.
      - All the existing and non-suppressed ids in the selection.
    """

    if selection == None: return ([], [])
    if len(selection.Ids) == 0: return ([], [])
    
    if selection.SelectionType == SelectionTypeEnum.GeometryEntities:
        cellType = GetGeoCellType(api, selection)
        if cellType == None: return ([], [])

        allIds = []
        suppressedIds = []
        unSuppressedIds = []
        
        for part in api.DataModel.GeoData.Assemblies[0].Parts:
            for body in part.Bodies:
                ids = None
                if   cellType == GeoCellTypeEnum.GeoBody: ids = [body.Id]
                elif cellType == GeoCellTypeEnum.GeoFace: ids = body.FaceIds
                elif cellType == GeoCellTypeEnum.GeoEdge: ids = body.EdgeIds
                elif cellType == GeoCellTypeEnum.GeoVertex: ids = body.VertexIds

                if body.Suppressed: suppressedIds += ids
                else: unSuppressedIds += ids
                allIds += ids

        allSuppressedIds = set(suppressedIds).difference(set(unSuppressedIds))
        allInIds = set(selection.Ids).intersection(set(allIds))
        allValidIds = allInIds.difference(allSuppressedIds)
    else:
        allInIds = []
        allValidIds = []
        geo = api.DataModel.GeoData
        mesh = api.DataModel.MeshDataByName('Global')
        if mesh.NodeCount == 0:
            return (selection.Ids, selection.Ids)
        for id in selection.Ids:
            node = mesh.NodeById(id)
            if node == None: continue
            allInIds.append(id)

            suppressed = True
            for bodyId in node.BodyIds:
                body = geo.GeoEntityById(bodyId)
                if not body.Suppressed: 
                    suppressed = False
                    break

            if not suppressed: 
                allValidIds.append(id)

    return (list(allInIds), list(allValidIds))

def GetGeoCellType(api, selection):
    """
    Get the GeoCellType of the selection. Return None
    if the selection is not a GeometryEntitier or if 
    there is non valid entity in the selection.
    """
    if selection.SelectionType == SelectionTypeEnum.GeometryEntities:
        geo = api.DataModel.GeoData
        for id in selection.Ids:
            entity = geo.GeoEntityById(id)
            if entity != None: return entity.Type
    return None
         

def IsDefaultScoping(entity, prop):
    """
    Check if the given scoping property is a default scoping.
    """
    default_scoping = prop.Attributes['default_scoping']
    if default_scoping == 'false':
        return False

    parent = prop.ParentPropertyGroup
    while parent != None:
        if parent.Display == "worksheet":
            return False
        parent = parent.ParentPropertyGroup

    for property in entity.Properties.AllDescendants:
        if property == prop: continue
        if property.Attributes['default_scoping'] == 'true': 
            return False

    return True

# --- Scoping callbacks ---

def getParentScoping(prop):
    parent = prop.ParentPropertyGroup
    if parent != None and parent.Name == 'DefineBy':
        return parent.ParentPropertyGroup
    return None

def isParentScoping(prop):
    return getParentScoping(prop) != None

def callScopingOnValidate(prop):
    scoping = getParentScoping(prop)
    if scoping == None: return
    scoping.Validate()

def callScopingIsValid(prop):
    scoping = getParentScoping(prop)
    if scoping == None: return False
    return scoping.IsValid

