import SMESH
import GEOM
import SALOMEDS
import os,time, stat
import sys
import json
import subprocess
import salome
import math
import re
from salome.geom import geomBuilder
from salome.smesh import smeshBuilder
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
import salome_version
from pathlib import Path

if salome_version.getVersions() >= [9,0,0]:
    from ModelAPI import *
    from ParametersAPI import *
    from salome.shaper import model
    geompy = geomBuilder.New()
else:
    geompy = geomBuilder.New(salome.myStudy)

#from PyQt4.QtCore import *
#from PyQt4.QtGui import *

def printq(text):
    msgBox = QMessageBox()
    msgBox.setWindowTitle("printq")
    msgBox.setText(text)
    msgBox.exec_()

class GmeshBuild:
    GEOMETRY_FILE_NAME = 'salome_study.hdf'
    MESH_FILE_NAME = 'meshFile.msh'
    selectedGeomGroups = []
    maxGroupID = 0
    objectsForCleanup = []
    meshGroupsForCleanup = []
    # Check if geometry file exists.
    def mayWriteGeometryFile(self, dir):
        result = True
        if os.path.isfile(dir + '//geometry//' + self.GEOMETRY_FILE_NAME):
            answer = QMessageBox.question(None, 'Cenos Platform', 'Previous geometry will be overwritten. Do you want to continue?', QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel)
            if answer != QMessageBox.Yes:
                result = False
        return result

    # check if names are not in forbidden list and name is alphanumeric with underscores (r'^\w+$')
    def checkNames(self):
        with open( os.environ['CENOS_CONFIG'] + '\\jsons\\reservedNames.json') as f:
            reservedList = json.load(f)
        badNames = []
        volumeList = self.get3DGroups()
        for v in volumeList:
            v.SetName(v.GetName().replace(" ", "_"))
            v.SetName(v.GetName().replace(".", "_"))
            if v.GetName().upper() in (name.upper() for name in reservedList) or not re.match(r'^\w+$', v.GetName()):
                badNames.append(v.GetName())
            elif not v.GetName()[0].isalpha():
                badNames.append(v.GetName())
                
        areaList = self.get2DGroups()
        for a in areaList:
            a.SetName(a.GetName().replace(" ", "_"))
            a.SetName(a.GetName().replace(".", "_"))
            if a.GetName().upper() in (name.upper() for name in reservedList) or not re.match(r'^\w+$', a.GetName()):
                badNames.append(a.GetName())
            elif not a.GetName()[0].isalpha():
                badNames.append(a.GetName())

        lineList = self.get1DGroups()
        for l in lineList:
            l.SetName(l.GetName().replace(" ", "_"))
            l.SetName(l.GetName().replace(".", "_"))
            if l.GetName().upper() in (name.upper() for name in reservedList) or not re.match(r'^\w+$', l.GetName()):
                badNames.append(l.GetName())
            elif not l.GetName()[0].isalpha():
                badNames.append(l.GetName())
				
        return badNames
		
    # check if names are not in forbidden list
    def checkOverlapping(self, selectedGeomGroupList):
        badPairs = []
        volumeList = [x for x in self.get3DGroups() if x.GetName() in selectedGeomGroupList]

        badPairs.extend(["VOLUME GROUPS: " + v1.GetName() + " AND " + v2.GetName()\
        for i,v1 in enumerate(volumeList) for j,v2 in enumerate(volumeList)\
        if i > j if set(geompy.GetObjectIDs(v1)).intersection(geompy.GetObjectIDs(v2))])

        areaList = [x for x in self.get2DGroups() if x.GetName() in selectedGeomGroupList]

        badPairs.extend(["FACE GROUPS: " + f1.GetName() + " AND " + f2.GetName()\
        for i,f1 in enumerate(areaList) for j,f2 in enumerate(areaList)\
        if i > j if set(geompy.GetObjectIDs(f1)).intersection(geompy.GetObjectIDs(f2))])

        lineList = [x for x in self.get1DGroups() if x.GetName() in selectedGeomGroupList]

        badPairs.extend(["EDGE GROUPS: " + e1.GetName() + " AND " + e2.GetName()\
        for i,e1 in enumerate(lineList) for j,e2 in enumerate(lineList)\
        if i > j if set(geompy.GetObjectIDs(e1)).intersection(geompy.GetObjectIDs(e2))])

        return badPairs
		
    # Check if mesh file exists.
    def mayWriteMeshFile(self, Dir):
        result = True
        if not os.path.exists(Dir + '/geometry'):
            os.makedirs(Dir + '/geometry')
        if os.path.isfile(Dir + '/' + self.MESH_FILE_NAME):
            answer = QMessageBox.question(None, 'Cenos Platform', 'Previous mesh and geometry will be overwritten. Do you want to continue?', QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel)
            if answer != QMessageBox.Yes:
                result = False
        return result

    def WriteGeometry(self,Dir):
        if not os.path.exists(Dir + '/geometry'):
            os.makedirs(Dir + '/geometry')
        if salome_version.getVersions() >= [9,0,0]:
            if os.path.isfile(Dir + '/geometry/' + self.GEOMETRY_FILE_NAME):
                os.chmod(Dir + '/geometry/' + self.GEOMETRY_FILE_NAME, stat.S_IWRITE)
            salome.myStudy.SaveAs(Dir + '/geometry/' + self.GEOMETRY_FILE_NAME, False, False)
            os.chmod(Dir + '/geometry/' + self.GEOMETRY_FILE_NAME, stat.S_IWRITE)
        else:
            if os.path.isfile(Dir + '/geometry/' + self.GEOMETRY_FILE_NAME):
                os.chmod(Dir + '/geometry/' + self.GEOMETRY_FILE_NAME, stat.S_IWRITE)
            salome.myStudyManager.SaveAs(Dir + '/geometry/' + self.GEOMETRY_FILE_NAME, salome.myStudy, False)
            os.chmod(Dir + '/geometry/' + self.GEOMETRY_FILE_NAME, stat.S_IWRITE)

			
    def getMeshDimensionality(self, mesh):
        if not mesh:
            return 0

        # Get dimensionality of mesh
        nrOfEdges= mesh.NbEdges()
        nrOfFaces= mesh.NbTriangles()+mesh.NbQuadrangles()
        nrOfVolumes = mesh.NbTetras()+ mesh.NbPrisms() + mesh.NbHexas() + mesh.NbPyramids()
        if nrOfEdges > 0 :
            dim = 1
        if nrOfFaces > 0 :
            dim = 2
        if nrOfVolumes > 0 :
            dim = 3
        return dim
			
	# write mesh in gui mode
    def WriteMesh(self, Dir, mode = "GUI", mesh = None, meshFileName = None):
        model.begin()
        model.moduleDocument()

        if not os.path.exists(Dir + '//geometry'):
            os.makedirs(Dir + '//geometry')

        if (mode == "GUI"):
            numTasks = 100
            progress = QProgressDialog()
            progress.setCancelButtonText("&Cancel")
            progress.setRange(0, numTasks)
            progress.setWindowTitle("Mesh writing in progress...")
            File = Dir + '//geometry//' + self.MESH_FILE_NAME
			# Find mesh.
            mesh = self.findSelectedMesh()
            if not mesh:
                return "Mesh is not selected"
        else:
            File = Dir + '//geometry//' + meshFileName
            progress = None

        # Get dimensionality of mesh
        dim = self.getMeshDimensionality(mesh)
		
        self.findGroups(mesh)

        self.writeGeometryData(Dir, mesh)
	
		# find groups again to renumber them after adding undefined BCs
        self.findGroups(mesh)
		
		# write header
        self.writeHead(File)
        self.writeMeshName(File, mesh)
		
        if (mode == "GUI"):
            progress.setValue(1)
            if (progress.wasCanceled()):
                return
            QApplication.processEvents()
			
			# dumps necessary only in GUI mode
            self.dumpShaper(Dir + '//geometry//shaperDump.py')
            salome.myStudy.DumpStudy(Dir + '//geometry//' , "dump", True, False)
        
		# write entity names
        self.writeGeometryInformation(Dir, mesh)
        self.writeNames(File, mesh)
		
        if (mode == "GUI"):
            progress.setValue(4)
            if (progress.wasCanceled()):
                return
            QApplication.processEvents()

        #get mesh stats
        nrOfNodes = mesh.NbNodes()
        nrOfEdges= mesh.NbEdges()
        nrOfFaces= mesh.NbTriangles()+mesh.NbQuadrangles()
        nrOfVolumes = mesh.NbTetras()+ mesh.NbPrisms() + mesh.NbHexas() + mesh.NbPyramids()
        entryNr = (nrOfNodes*3 + (nrOfEdges + nrOfFaces + nrOfVolumes)*12 )

		# write nodes
        self.writeNodes(File, mesh, progress, entryNr)
		
        if mode == "GUI" and progress.wasCanceled():
                return

		# write elements
        if (not self.writeElements(File, mesh, progress, entryNr)):
            return
			
        if mode == "GUI" and progress.wasCanceled():
                return

        self.cleanup()
        if mode !="BATCH_NO_RESPONSE":
            self.sendGeometryData(Dir, File)
        
        if mode != "GUI":
            jsondata = json.loads("{}")
            jsondata['status'] = "success"
            with open(Dir + "//geometry//batch_response.json", "w") as write_file :
                json.dump(jsondata, write_file)
				
        if (mode == "GUI"):
            progress.setValue(100)

        #cleanup mesh groups
        for gr in mesh.GetGroups():
            if gr.GetName() in self.meshGroupsForCleanup:
                mesh.RemoveGroup(gr)
        salome.sg.updateObjBrowser()
        model.do()
        model.end()
        
        # for now save only in gui mode. Will fix this with issue CP-1113
        if (mode == "GUI"):
            self.WriteGeometry(Dir)

        return
		
		
    def cleanup(self):
        sbuilder = salome.myStudy.NewBuilder()
        for obj in self.objectsForCleanup:
            SO = salome.myStudy.FindObjectIOR(salome.myStudy.ConvertObjectToIOR(obj))
            if SO: sbuilder.RemoveObjectWithChildren(SO)
        salome.sg.updateObjBrowser()
		
    def getShaperVariables(self):
        vars = {}
        vars['variables'] = []
        allFeats = model.moduleDocument().allFeatures()
        for ft in allFeats:
            if ft.getKind() == "Parameter":
                param = ParametersAPI_Parameter(ft)
                var = {'name': param.name().value(), 'value': param.value()}
                vars['variables'].append(var)
            elif ft.getKind() == "Part":
                part = modelAPI_ResultPart(ft.results()[0])
                if not part.isActivated():
                    part.activate()
                for subFt in part.partDoc().allFeatures():
                    if subFt.getKind() == "Parameter":
                        param = ParametersAPI_Parameter(subFt)
                        var = {'name': param.name().value(), 'value': param.value()}
                        vars['variables'].append(var)
        return vars


    def dumpShaper(self, dumpFileName):
        model.begin()
        dump=model.moduleDocument().addFeature("Dump")
        dump.string("file_path").setValue(dumpFileName)
        dump.string("file_format").setValue("py")
        dump.boolean("topological_naming").setValue(True)
        dump.boolean("geometric_selection").setValue(False)
        dump.boolean("weak_naming").setValue(False)
        model.do()
        model.end()
  
    def sendGeometryData(self, Dir, File, writeDataOnly = False):
        import json
        with open(os.environ['TEMP'] + '\\cenos\\' + os.environ['SESSION_GUID']  + '.json') as json_file:
            jsondata = json_file.read()
            confdata = json.loads(jsondata)

        port = confdata['port']
        jsondata = json.loads("{}")
        jsondata['guid'] = os.environ['SESSION_GUID']
        jsondata['sendTo'] = 'backend'
        jsondata['module'] = 'topology'
        jsondata['command'] = 'getGeometryData'
        jsondata['port'] = port
        argsdata = json.loads("{}")
        argsdata['caseDir'] = Dir
        argsdata['geometryDataFile'] = Dir + "//geometry//raw//RawGeometryData.json"
        argsdata['meshFile'] = "geometry//" + self.MESH_FILE_NAME
        jsondata['args'] = argsdata
			
        if not writeDataOnly:
            import ctypes
            from ctypes.util import find_library
            libPath = find_library("meshSender.dll")
            path_libc = ctypes.cdll.LoadLibrary(libPath)
            path_libc.sendMessage.restype = ctypes.c_int
            path_libc.sendMessage.argtypes = [ctypes.c_int, ctypes.c_char_p]
            i = path_libc.sendMessage(int(port), ctypes.create_string_buffer(str.encode(json.dumps(jsondata))))

    # Find the first selected mesh.
    def findSelectedMesh(self):
        import libSALOME_Swig
        mesh = None
        if salome_version.getVersions() >= [9,0,0]:
            smesh = smeshBuilder.New()
        else:
            smesh = smeshBuilder.New(salome.myStudy)
            
        # smesh = smeshBuilder.New(salome.myStudy) # NB: important to place this line before checking-loop.
        sg = libSALOME_Swig.SALOMEGUI_Swig()
        nrSelected = sg.SelectedCount() # total number of selected items

        if nrSelected > 0:
            for i in range(nrSelected):
                selectedObjId = sg.getSelected(i)
                meshObj = self.getMeshFromSelfOrParent(selectedObjId)
                if meshObj != None:
                    mesh = smesh.Mesh(meshObj)
                    break
        return mesh

    # Find mesh from selected object or its parent.
    def getMeshFromSelfOrParent(self, objId):
        meshObj = None
        sObj = salome.myStudy.FindObjectID(objId)
        obj = sObj.GetObject()

        while obj.__class__ != SALOMEDS._objref_Study:
            if (obj.__class__ == SMESH._objref_SMESH_Mesh) or (obj.__class__ == salome.smesh.smeshBuilder.meshProxy):
                meshObj = obj
                break
            try:
                sObj = sObj.GetFather()
                obj = sObj.GetObject()
            except:
                return None
        return meshObj

    # currently write only with mesh!
    def writeGeometryData(self, Dir, mesh):
        geom = mesh.GetShape()
        data = {}
        if self.volGroups:
            meshDim = 3
        else:
            meshDim = 2
        data["dimensions"] = meshDim
        data["geometryUnit"] = ""
        data["hasMeshData"] = True
        data["meshFile"] = "geometry/meshFile.msh"
        data["editorFile"] = "geometry/salome_study.hdf"
        data["operate"] = []

        data["domains"] = []
        data["boundaries"] = []
        data["solids"] = []
        data["faces"] = []
        data["edges"] = []
        data["partitionShape"] = []

        edges = geompy.SubShapeAll(geom, geompy.ShapeType["EDGE"])
        faces = geompy.SubShapeAll(geom, geompy.ShapeType["FACE"])
        solids = geompy.SubShapeAll(geom, geompy.ShapeType["SOLID"])

        if meshDim == 3:
            domain_type = geompy.ShapeType["SOLID"]
            boundary_type = geompy.ShapeType["FACE"]
            domain_entities = geompy.SubShapeAll(geom, domain_type)
            boundary_entities = geompy.SubShapeAll(geom, boundary_type)
            domain_type_name = "Solid"
            boundary_type_name = "Face"
        elif meshDim == 2:
            domain_type = geompy.ShapeType["FACE"]
            boundary_type = geompy.ShapeType["EDGE"]
            domain_entities = geompy.SubShapeAll(geom, domain_type)
            boundary_entities = geompy.SubShapeAll(geom, boundary_type)
            domain_type_name = "Face"
            boundary_type_name = "Edge"

        # add domain and boundary information to data
        for group in self.selectedGeomGroups:
            entitity_ids_in_group = geompy.GetObjectIDs(group)
			
            # find out shape_type
            # GetShapeType()will not work, as type can be compound
            entities_in_group = geompy.SubShapeAll(group, domain_type)
            if len(entities_in_group) > 0:
                shape_type = domain_type
            else:
                shape_type = boundary_type


            if shape_type == domain_type:
                group_entities = []
                for dom in domain_entities:
                    dom_id = geompy.GetSubShapeID(geom, dom)
                    if dom_id in entitity_ids_in_group:
                        group_entities.append(domain_type_name + str(dom_id))

                data["domains"].append({
                                    "name": group.GetName(),
                                    "label": group.GetName(),
                                    "entities": group_entities,
                                    "role": ''})

            elif shape_type == boundary_type:
                group_entities = []
                for bnd in boundary_entities:
                    bnd_id = geompy.GetSubShapeID(geom, bnd)
                    if bnd_id in entitity_ids_in_group:
                        group_entities.append(boundary_type_name + str(bnd_id))

                data["boundaries"].append({
                                    "name": group.GetName(),
                                    "label": group.GetName(),
                                    "entities": group_entities,
                                    "role": ''})

        geom_path = os.path.join(Dir, "geometry\\raw")
        if not os.path.exists(geom_path):
            try:
                Path(geom_path).mkdir(parents=True, exist_ok=True)
            except:
                #TO DO make message box
                print("\nPlease make sure the geometry/geometry_to_cenos folder is not open")
                return

        fname = os.path.join(geom_path, "partition.brep")
        geompy.ExportBREP(geom, fname)
        data["partitionShape"] = {"name": "partitionShape", "id": 0,
                                "path": "geometry/raw/partition.brep"}
        for sol in solids:
            sol_id = geompy.GetSubShapeID(geom, sol)
            file_name = "Solid" + str(sol_id) + ".brep"
            geompy.ExportBREP(sol, os.path.join(geom_path, file_name).replace("\\","/"))
            data["solids"].append({"id": sol_id, "name": "Solid"+str(sol_id), "path": "geometry/raw/" + "Solid" + str(sol_id) + ".brep"})

        for face in faces:
            face_id = geompy.GetSubShapeID(geom, face)
            file_name = "Face" + str(face_id) + ".brep"
            geompy.ExportBREP(face, os.path.join(geom_path, file_name).replace("\\","/"))
            data["faces"].append({"id": face_id, "name": "Face"+str(face_id), "path": "geometry/raw/" + "Face" + str(face_id) + ".brep"})

        for edge in edges:
            edge_id = geompy.GetSubShapeID(geom, edge)
            file_name = "Edge" + str(edge_id) + ".brep"
            geompy.ExportBREP(edge, os.path.join(geom_path, file_name).replace("\\","/"))
            data["edges"].append({"id": edge_id, "name": "Edge"+str(edge_id), "path": "geometry/raw/" + "Edge" + str(edge_id) + ".brep"})

        if self.selectedGeomGroups:
            data["groupMode"] = "groupsFromPreprocessor"
        else:
            data["groupMode"] = "groupsFromAllEntities"

        varNames = salome.myStudy.GetVariableNames()
        data['variables'] = self.getShaperVariables()['variables']
        for varName in varNames:
            if salome.myStudy.IsInteger(varName):
                var = {'name': varName, 'value': salome.myStudy.GetInteger(varName)}
                jsonVar = json.dumps(var)
                data['variables'].append(json.loads(jsonVar))

            elif salome.myStudy.IsReal(varName):
                var = {'name': varName, 'value': salome.myStudy.GetReal(varName)}
                jsonVar = json.dumps(var)
                data['variables'].append(json.loads(jsonVar))
				
        with open(os.path.join(Dir, "geometry/raw/RawGeometryData.json"), 'w') as outfile:
            json.dump(data, outfile)
        return
	
	
    def readNames(self):
        foo=self
        go = salome.myStudy.FindObjectByPath("/Mesh")
        it = salome.myStudy.NewChildIterator( go )
        it.InitEx(True)
        while it.More():
            so = it.Value()
            it.Next()
            go = so.GetObject()
            if go:
                exec( "foo.%s = go"%so.GetName() )

    def getMeshShapeTopologyInfo(self, mesh):
        geomInfo = geompy.WhatIs(mesh.GetShape())
        geomInfoJson = {}
        checkShapes = ["VERTEX", "EDGE", "WIRE", "FACE", "SHELL", "SOLID", "COMPSOLID", "COMPOUND", "SHAPE"]
        for line in geomInfo.splitlines():
            for shape in checkShapes:
                if shape in line:
                    shapeNr = 0
                    for nr in line.split():
                        if nr.isdigit():
                            shapeNr = int(nr) 
                    geomInfoJson[shape] = shapeNr
        return geomInfoJson
		
	#write topology information of the geometry (nr of edges, vertices, etc..)
    def writeGeometryInformation(self, Dir, mesh):
        geomInfoJson = self.getMeshShapeTopologyInfo(mesh)

        with open(Dir+ "//geometry//geomInfo.json", "w") as write_file:
            json.dump(geomInfoJson, write_file)

    def isSameTopology(self, Dir, mesh):
        print(Dir)
        geomInfoJson = self.getMeshShapeTopologyInfo(mesh)
        if os.path.isfile(Dir+ "//geometry//geomInfo.json"):
            with open(Dir+ "//geometry//geomInfo.json", "r") as read_file:
                oldGeomInfoJson = json.load(read_file)
            if oldGeomInfoJson == geomInfoJson:
                return True
            else: 
                return False
        else:
            return True

#*******************************************
#      Writing mesh components
#*******************************************
    def writeHead(self,File):
        if os.access(File, os.F_OK):
            os.remove(File)
        fo = open(File, "w")
        fo.write("$MeshFormat\n")
        fo.write("2 0 8\n")
        fo.write("$EndMeshFormat\n")
        fo.close()
		
    def writeMeshName(self,File, mesh):
        fo = open(File, "a")
        fo.write("$SalomeMeshName\n")
        fo.write("%s\n" % (mesh.GetName()) )
        fo.write("$EndSalomeMeshName\n")
        fo.close()
			
    def writeNames(self, File, mesh):
        fo = open(File, "a")
        fo.write("$PhysicalNames\n")
        fo.write("%s \n" % (mesh.NbGroups()))
		
        groups = self.getSortedGroups(mesh)
        for group_i in range(len(groups)):
            for egr in self.edgeGroups:
                if egr[0].GetName() == groups[group_i].GetName():
                    fo.write("%s %s\n" % (egr[1], egr[0].GetName()) )
            for agr in self.areaGroups:
                if agr[0].GetName() == groups[group_i].GetName():
                    fo.write("%s %s\n" % (agr[1], agr[0].GetName()) )
            for vgr in self.volGroups:
                if vgr[0].GetName() == groups[group_i].GetName():
                    fo.write("%s %s\n" % (vgr[1], vgr[0].GetName()) )

        fo.write("$EndPhysicalNames\n")
		
# Start writing nodes
    def writeNodes(self,File, mesh, progress, entryNr):
        if progress is not None:
            initial = progress.value()
        fo = open(File, "a")
        fo.write("$Nodes\n")
        self.NrOfNodes = mesh.NbNodes()
        if self.NrOfNodes == 0:
            print("No nodes found!")
        else:
            nodes = mesh.GetNodesId()
            fo.write("%s \n" % (self.NrOfNodes))
            for ind in nodes:
                coord=mesh.GetNodeXYZ(ind)
                if abs(coord[0]) < 1e-12:
                    coord[0] = 0
                if abs(coord[1]) < 1e-12:
                    coord[1] = 0
                if abs(coord[2]) < 1e-12:
                    coord[2] = 0
                fo.write("%s %s %s %s \n" % (ind, coord[0], coord[1],  coord[2]) )
                if progress is not None:
                    progress.setValue(initial + math.floor(95*ind*3/entryNr))
                    QApplication.processEvents()


        fo.write("$EndNodes\n")
        fo.close()
			
# Start writing elements
# Execution will continue only if NrOfNodes>0
    def writeElements(self,File, mesh, progress, entryNr):
        if progress is not None:
            initial = progress.value()
        fo = open(File, "a")
        self.NrOfEdges= mesh.NbEdges()
        self.NrOfFaces= mesh.NbTriangles()+mesh.NbQuadrangles()
        self.NrOfVolumes = mesh.NbTetras()+ mesh.NbPrisms() + mesh.NbHexas() + mesh.NbPyramids()
        if self.NrOfEdges == 0:
            print("No edge elements found!!")
        if self.NrOfFaces == 0:
            print("No surface elements found!!")

        if self.NrOfVolumes == 0:
            print("No volume elements found!!")

        if self.NrOfNodes > 0 and self.NrOfFaces+self.NrOfVolumes>0:
            fo.write("$Elements\n")

            edgeElements=0
            for group_i in range(len(self.edgeGroups)):
                edgeElms=self.edgeGroups[group_i][0].GetListOfID()
                for edge_i in edgeElms:
                    edgeElements=edgeElements + 1

            faceElements=0
            for group_i in range(len(self.areaGroups)):
                faceElms=self.areaGroups[group_i][0].GetListOfID()
                for face_i in faceElms:
                    faceElements=faceElements + 1
					
            volElements=0
            for group_i in range(len(self.volGroups)):
                volElms=self.volGroups[group_i][0].GetListOfID()
                for vol_i in volElms:
                    volElements=volElements + 1
			
            fo.write("%s \n" % (edgeElements+faceElements+volElements))
			
# Loop through edge mesh groups
            element_i=1
            for group_i in range(len(self.edgeGroups)):
                edgeElms=self.edgeGroups[group_i][0].GetListOfID()
                for edge_i in edgeElms:
                    nb_nodes=mesh.GetElemNbNodes(edge_i)
                    fo.write("%s %s %s %s %s %s " % (element_i, 1, 3, self.edgeGroups[group_i][1], self.edgeGroups[group_i][1], 0) )
                    for node_i in range(0,nb_nodes):
                        fo.write("%s " % (mesh.GetElemNode(edge_i,node_i)) )
                    fo.write("\n" )
                    if progress is not None:
                        progress.setValue(initial + math.floor(95*element_i*12/entryNr))
                        QApplication.processEvents()
                    element_i += 1
# Loop through area mesh groups
            for group_i in range(len(self.areaGroups)):
                faceElms=self.areaGroups[group_i][0].GetListOfID()
                for face_i in faceElms:
                    nb_nodes=mesh.GetElemNbNodes(face_i)
                    if nb_nodes==3:
                        eltype=2
                    elif nb_nodes==4:
                        eltype=3
                    fo.write("%s %s %s %s %s %s " % (element_i, eltype, 3, self.areaGroups[group_i][1], self.areaGroups[group_i][1], 0) )
                    for node_i in range(0,nb_nodes):
                        fo.write("%s " % (mesh.GetElemNode(face_i,node_i)) )
                    fo.write("\n" )
                    if progress is not None:
                        progress.setValue(initial + math.floor(95*element_i*12/entryNr))
                        QApplication.processEvents()
                    element_i += 1
# Loop through volume mesh groups
            for group_i in range(len(self.volGroups)):
                volElms=self.volGroups[group_i][0].GetListOfID()
                for vol_i in volElms:
                    nb_nodes=mesh.GetElemNbNodes(vol_i)
                    if nb_nodes==4:
                        eltype=4
                    elif nb_nodes==8:
                        eltype=5
                    elif nb_nodes==6:
                        eltype=6
                    elif nb_nodes==5:
                        eltype=7
                    fo.write("%s %s %s %s %s %s " % (element_i, eltype, 3, self.volGroups[group_i][1], self.volGroups[group_i][1], 0) )
                    for node_i in range(0,nb_nodes):
                        fo.write("%s " % (mesh.GetElemNode(vol_i,node_i)) )
                    fo.write("\n" )
                    if progress is not None:
                        progress.setValue(initial + math.floor(95*element_i*12/entryNr))
                        QApplication.processEvents()
                    element_i += 1
        fo.write("$EndElements")
        fo.close()
        return True

#*******************************************
#      Methods operations with groups
#*******************************************

    # get Sorted groups by name
    def getSortedGroups(self,mesh):
        groups=mesh.GetGroups()

        groupNameTupleList = []
        groupNameList = []
        # fill groupNameTupleList with pairs groupName, group
        for gr in groups: 
            groupNameTupleList.append((gr.GetName(),gr))
            groupNameList.append(gr.GetName())

        # check for duplicates
        if len(groupNameList) != len(set(groupNameList)):
            return []

        # sort tuple list
        groupNameTupleList.sort(key=lambda tup:tup[0])
        returnGroups = []

        #fill returnGroups with groups from sorted list
        for gr in groupNameTupleList:
            returnGroups.append(gr[1])

        return returnGroups


    # Find groups
    def findGroups(self, mesh):
        groups = self.getSortedGroups(mesh)
        if groups == []:
            return "Check groups! Possibly duplicate naming."
        self.edgeGroups = []
        self.areaGroups = []
        self.volGroups = []
        groupID = 0
        for group_i in range(len(groups)):
            if groups[group_i].GetType() == SMESH.FACE:
                groupID = groupID + 1
                self.areaGroups.append([groups[group_i], groupID])
            elif groups[group_i].GetType() == SMESH.VOLUME:
                groupID = groupID + 1
                self.volGroups.append([groups[group_i], groupID])
            elif groups[group_i].GetType() == SMESH.EDGE:
                groupID = groupID + 1
                self.edgeGroups.append([groups[group_i], groupID])

        self.maxGroupID = groupID
        return ""

    def checkAllDomainsIncludedInGroups(self, mesh):
        self.NrOfVolumes = mesh.NbTetras()+ mesh.NbPrisms() + mesh.NbHexas() + mesh.NbPyramids()
        volElements=0
        for group_i in range(len(self.volGroups)):
            volElms=self.volGroups[group_i][0].GetListOfID()
            for vol_i in volElms:
                volElements=volElements + 1

        if volElements < self.NrOfVolumes:
            answer = QMessageBox.question(None, 'Cenos Platform', 'Not all volumes have assigned groups. Do you want to continue?', QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel)
            if answer != QMessageBox.Yes:
                return False
        return True
		
    def getGeometryGroups(self):
        mesh = self.findSelectedMesh()
        if mesh:
            geom = mesh.GetShape()
            groups = geompy.GetGroups(geom)

            # CP-204 Filter out empty groups on second mesh sending
            groups = [i for i in groups if i.GetName() != ""]
            return groups
        else:
            printq("no mesh selected")
            return []

    def get3DGroups(self):
        groups = self.getGeometryGroups()
        volumes = []
        for gr in groups :
            if gr.GetMinShapeType() == GEOM.SOLID:
                volumes.append(gr)
        return volumes

    def get2DGroups(self):
        groups = self.getGeometryGroups()
        faces = []
        for gr in groups :
            if gr.GetMinShapeType() == GEOM.FACE:
                faces.append(gr)
        return faces

    def get1DGroups(self):
        groups = self.getGeometryGroups()
        edges = []
        for gr in groups :
            if gr.GetMinShapeType() == GEOM.EDGE:
                edges.append(gr)
        return edges

    def updateGroups(self, selectedGeomGroups_, mesh = None):
        self.selectedGeomGroups = selectedGeomGroups_
        if salome_version.getVersions() >= [9,0,0]:
            smesh = smeshBuilder.New()
        else:
            smesh = smeshBuilder.New(salome.myStudy)
        if not mesh:
            mesh = self.findSelectedMesh()
        if mesh:
            sbuilder = salome.myStudy.NewBuilder()
            for gr in mesh.GetGroups():
                mesh.RemoveGroup(gr)
            salome.sg.updateObjBrowser()
            geom = mesh.GetShape()

            edges = geompy.SubShapeAll(geom, geompy.ShapeType["EDGE"])
            faces = geompy.SubShapeAll(geom, geompy.ShapeType["FACE"])
            solids = geompy.SubShapeAll(geom, geompy.ShapeType["SOLID"])

            for e in edges :
                edgeId = geompy.GetSubShapeID(geom, e)
                edge_name = "Edge" + str(edgeId)
                vars()[ edge_name + '_msh'] = mesh.GroupOnGeom(e, edge_name, SMESH.EDGE)
                smesh.SetName(vars()[edge_name + '_msh'], edge_name)
                self.meshGroupsForCleanup.append(edge_name)

            for f in faces :
                faceId = geompy.GetSubShapeID(geom, f)
                face_name = "Face" + str(faceId)
                vars()[ face_name + '_msh'] = mesh.GroupOnGeom(f, face_name, SMESH.FACE)
                smesh.SetName(vars()[face_name + '_msh'], face_name)
                self.meshGroupsForCleanup.append(face_name)

            for s in solids :
                solidId = geompy.GetSubShapeID(geom, s)
                solid_name = "Solid" + str(solidId)
                vars()[ solid_name + '_msh'] = mesh.GroupOnGeom(s, solid_name, SMESH.VOLUME)
                smesh.SetName(vars()[solid_name + '_msh'], solid_name)
                self.meshGroupsForCleanup.append(solid_name)

            self.objectsForCleanup.extend(edges)
            self.objectsForCleanup.extend(faces)
            self.objectsForCleanup.extend(solids)

		
		
    def getMeshGroups(self):
        groupNames = []
        mesh = self.findSelectedMesh()
        if mesh:
            groups = mesh.GetGroups()
            return groups
        else:
            return []


    def meshIsSelected(self):
        mesh = self.findSelectedMesh()
        if not mesh:
            return "Mesh is not selected"
        return
			
    def sendWarning(self, message):
        import json
        with open(os.environ['TEMP'] + '\\cenos\\' + os.environ['SESSION_GUID']  + '.json') as json_file:
            jsondata = json_file.read()
            confdata = json.loads(jsondata)

        port = confdata['port']
        data = """
        {
            "type": "log",
            "level": "warn",
            "needsResponse": true,
            "sendTo": "gui"
        }
        """
        jsondata = json.loads(data)
        jsondata['message'] =  message
        import ctypes
        from ctypes.util import find_library
        libPath = find_library("meshSender.dll")
        path_libc = ctypes.cdll.LoadLibrary(libPath)
        path_libc.sendMessage.restype = ctypes.c_int
        path_libc.sendMessage.argtypes = [ctypes.c_int, ctypes.c_char_p]
        i = path_libc.sendMessage(int(port), ctypes.create_string_buffer(str.encode(json.dumps(jsondata))))
		
        # callPath = [ "%s\\meshSenderClient.exe" %os.environ["SALOME_PLUGINS_PATH"], port, "%s//geometry//data_file.json" %os.environ['TEMP']]
        # subprocess.call(callPath)
		
#*******************************************
#      Methods for mesh quality checks
#*******************************************

    def checkAspectRatios2D(self, mesh, warnLimit, errorLimit):
        if salome_version.getVersions() >= [9,0,0]:
            smesh = smeshBuilder.New()
        else:
            smesh = smeshBuilder.New(salome.myStudy)
        warn_margin = warnLimit
        warnFilter = smesh.GetFilter(SMESH.FACE, SMESH.FT_AspectRatio, SMESH.FT_MoreThan, warn_margin)
        err_margin = errorLimit
        errFilter = smesh.GetFilter(SMESH.FACE, SMESH.FT_AspectRatio, SMESH.FT_MoreThan, err_margin)
        warnIds = mesh.GetIdsFromFilter(warnFilter) 
        errIds = mesh.GetIdsFromFilter(errFilter) 
        return (warnIds, errIds)

    def checkAspectRatios3D(self, mesh, warnLimit, errorLimit):
        if salome_version.getVersions() >= [9,0,0]:
            smesh = smeshBuilder.New()
        else:
            smesh = smeshBuilder.New(salome.myStudy)
        warn_margin = warnLimit
        warnFilter = smesh.GetFilter(SMESH.FACE, SMESH.FT_AspectRatio3D, SMESH.FT_MoreThan, warn_margin)
        err_margin = errorLimit
        errFilter = smesh.GetFilter(SMESH.FACE, SMESH.FT_AspectRatio3D, SMESH.FT_MoreThan, err_margin)
        warnIds = mesh.GetIdsFromFilter(warnFilter) 
        errIds = mesh.GetIdsFromFilter(errFilter) 
        return (warnIds, errIds)

    def checkTaperRatios(self, mesh, warnLimit, errorLimit):
        if salome_version.getVersions() >= [9,0,0]:
            smesh = smeshBuilder.New()
        else:
            smesh = smeshBuilder.New(salome.myStudy)
        warn_margin = warnLimit
        warnFilter = smesh.GetFilter(SMESH.FACE, SMESH.FT_Taper, SMESH.FT_MoreThan, warn_margin)
        err_margin = errorLimit
        errFilter = smesh.GetFilter(SMESH.FACE, SMESH.FT_Taper, SMESH.FT_MoreThan, err_margin)
        warnIds = mesh.GetIdsFromFilter(warnFilter)
        errIds = mesh.GetIdsFromFilter(errFilter)
        return (warnIds, errIds)

    # Creates groups of passed elements
    # Useful for creation of filters for bad elements
    def createMeshGroup(self, elements, name, geom_type):
        smesh = smeshBuilder.New()
        mesh = self.findSelectedMesh()
        if geom_type == "FACE":
            aGroup = mesh.CreateEmptyGroup(SMESH.FACE, name)
        if geom_type == "VOLUME":
            aGroup = mesh.CreateEmptyGroup(SMESH.VOLUME, name)
        aGroup.Add(elements)
        salome.sg.updateObjBrowser()
        return(None)
   
   
   