#!/bin/env python
# -*- coding: iso-8859-15 -*-

import os, sys

haveMatplotLib = True
haveMlab = True
haveWx = True
haveQt = True
    
globalWxApp = None
globalQtApp = None
globalWindows = []

try:
    from PyQt4 import QtGui
    from PyQt4.QtOpenGL import *
    from pycalfem_classes_qt4 import ElementView
    globalQtApp = QtGui.QApplication(["PyCalfem"])
except:
    haveQt = False    
      
if not haveQt:
    try:
        import wx
        from pycalfem_classes import ElementView
        globalWxApp = wx.App(0)
    except: 
        haveWx = False
      
from pycalfem import *

def readInt(f):
    """
    Read a row from file, f, and return a list of integers.
    """
    return list(map(int, f.readline().split()))
    
def readFloat(f):
    """
    Read a row from file, f, and return a list of floats.
    """
    return list(map(float, f.readline().split()))
    
def readSingleInt(f):
    """
    Read a single integer from a row in file f. All other values on row are discarded.
    """
    return readInt(f)[0] 

def readSingleFloat(f):
    """
    Read a single float from a row in file f. All other values on row are discarded.
    """
    return readFloat(f)[0]
    
def writeSingleFloat(f, floatValue):
    f.write("%g\n" % floatValue)
    
def writeSingleInt(f, intValue):
    f.write("%d\n" % intValue)

def writeFloatList(f, floatList):
    for floatValue in floatList:
        f.write("%g " % floatValue)
    f.write("\n")
    
def writeIntList(f, intList):
    for intValue in intList:
        f.write("%d " % intValue)
    f.write("\n")
    
    

def which(filename):
    """
    Return complete path to executable given by filename.
    """
    if not ('PATH' in os.environ) or os.environ['PATH'] == '':
        p = os.defpath
    else:
        p = os.environ['PATH']
                
    pathlist = p.split (os.pathsep)
    pathlist.append(".")
    
    for path in pathlist:
        f = os.path.join(path, filename)
        if os.access(f, os.X_OK):
            return f
    return None

def applybc(boundaryDofs, bcPrescr, bcVal, marker, value=0.0, dimension=0):
    """
    Apply boundary condition to bcPresc and bcVal matrices.
    
    Parameters:
    
        boundaryDofs        Dictionary with boundary dofs.
        bcPresc             1-dim integer array containing prescribed dofs.
        bcVal               1-dim float array containing prescribed values.
        marker              Boundary marker to assign boundary condition.
        value               Value to assign boundary condition.
                            If not giben 0.0 is assigned.
        dimension           dimension to apply bc. 0 - all, 1 - x, 2 - y
                            
    """

    if marker in boundaryDofs:
        if (dimension==0):
            bcAdd = array(boundaryDofs[marker])
            bcAddVal = ones([size(bcAdd)])*value
        elif (dimension==1):
            bcAdd = boundaryDofs[marker][(dimension-1)::2]
            bcAddVal = ones([size(bcAdd)])*value
        else:
            bcAdd = boundaryDofs[marker][(dimension-1)::2]
            bcAddVal = ones([size(bcAdd)])*value

        return hstack([bcPrescr,bcAdd]), hstack([bcVal,bcAddVal])
    else:
        print("Error: Boundary marker", marker, "does not exist.")
        
def applybcnode(nodeIdx, dofs, bcPrescr, bcVal, value=0.0, dimension=0):
    
    if (dimension==0):
        bcAdd = asarray(dofs[nodeIdx])
        bcAddVal = ones([size(bcAdd)])*value
    elif (dimension==1):
        bcAdd = asarray(dofs[nodeIdx,dimension-1])
        bcAddVal = ones([size(bcAdd)])*value
    else:
        bcAdd = asarray(dofs[nodeIdx,dimension-1])
        bcAddVal = ones([size(bcAdd)])*value

    return hstack([bcPrescr,bcAdd]), hstack([bcVal,bcAddVal])
    

def applyforcenode(nodeIdx, dofs, f, value=0.0, dimension=0):

    if (dimension==0):
        f[dofs[nodeIdx]]+=value
    elif (dimension==1):
        f[dofs[nodeIdx,dimension-1]]+=value
    else:
        f[dofs[nodeIdx,dimension-1]]+=value
        
def applyforce(boundaryDofs, f, marker, value=0.0, dimension=0):
    """
    Apply boundary condition to bcPresc and bcVal matrices.
    
    Parameters:
    
        boundaryDofs        Dictionary with boundary dofs.
        f                   force matrix.
        marker              Boundary marker to assign boundary condition.
        value               Value to assign boundary condition.
                            If not giben 0.0 is assigned.
        dimension           dimension to apply force. 0 - all, 1 - x, 2 - y
                            
    """

    if marker in boundaryDofs:
        if dimension == 0:
            f[asarray(boundaryDofs[marker])-1] += value
        elif dimension == 1:
            f[asarray(boundaryDofs[marker][(dimension-1)::2])-1] += value
        elif dimension == 2:
            f[asarray(boundaryDofs[marker][(dimension-1)::2])-1] += value            
    else:
        print("Error: Boundary marker", marker, "does not exist.")
    

def trimesh2d(vertices, segments = None, holes = None, maxArea=None, quality=True, dofsPerNode=1, logFilename="tri.log", triangleExecutablePath=None):
    """
    Triangulates an area described by a number vertices (vertices) and a set
    of segments that describes a closed polygon. 
    
    Parameters:
    
        vertices            array [nVertices x 2] with vertices describing the geometry.
        
                            [[v0_x, v0_y],
                             [   ...    ],
                             [vn_x, vn_y]]
                             
        segments            array [nSegments x 3] with segments describing the geometry.
                            
                            [[s0_v0, s0_v1,marker],
                             [        ...        ],
                             [sn_v0, sn_v1,marker]]
                             
        holes               [Not currently used]
        
        maxArea             Maximum area for triangle. (None)
        
        quality             If true, triangles are prevented having angles < 30 degrees. (True)
        
        dofsPerNode         Number of degrees of freedom per node.
        
        logFilename         Filename for triangle output ("tri.log")
        
    Returns:
    
        coords              Node coordinates
        
                            [[n0_x, n0_y],
                             [   ...    ],
                             [nn_x, nn_y]]
                             
        edof                Element topology
        
                            [[el0_dof1, ..., el0_dofn],
                             [          ...          ],
                             [eln_dof1, ..., eln_dofn]]
                             
        dofs                Node dofs
        
                            [[n0_dof1, ..., n0_dofn],
                             [         ...         ],
                             [nn_dof1, ..., nn_dofn]]
                             
        bdofs               Boundary dofs. Dictionary containing lists of dofs for
                            each boundary marker. Dictionary key = marker id.
        
    """
    
    # Check for triangle executable
    
    triangleExecutable = triangleExecutablePath
    
    if triangleExecutable == None:    
        triangleExecutable = ""
        if sys.platform == "win32":
            triangleExecutable = which("triangle.exe")
        else:
            triangleExecutable = which("triangle")
    else:
        if not os.path.exists(triangleExecutable):
            triangleExecutable = None
            
    if triangleExecutable==None:
        print("Error: Could not find triangle. Please make sure that the \ntriangle executable is available on the search path (PATH).")
        return None, None, None, None
    
    # Create triangle options
    
    options = ""
    
    if maxArea!=None:
        options += "-a%f " % maxArea + " "
    if quality:
        options += "-q"
        
    # Set initial variables
    
    nSegments = 0
    nHoles = 0
    nAttribs = 0
    nBoundaryMarkers = 1
    nVertices = len(vertices)
    
    # All files are created as temporary files
    
    if not os.path.exists("./trimesh.temp"):
        os.mkdir("./trimesh.temp")
        
    filename = "./trimesh.temp/polyfile.poly"
    
    if not segments is None:
        nSegments = len(segments)
    
    if not holes is None:
        nHoles = len(holes)
    
    # Create a .poly file
    
    polyFile = open(filename, "w")
    polyFile.write("%d 2 %d \n" % (nVertices, nAttribs))
    
    i = 0
    
    for vertex in vertices:
        polyFile.write("%d %g %g\n" % (i, vertex[0], vertex[1])) 
        i = i + 1
        
    polyFile.write("%d %d \n" % (nSegments, nBoundaryMarkers))
        
    i = 0
        
    for segment in segments:
        polyFile.write("%d %d %d %d\n" % (i, segment[0], segment[1], segment[2]))
        i = i + 1
        
    polyFile.write("0\n")
    
    polyFile.close()

    # Execute triangle
    
    os.system("%s %s %s > tri.log" % (triangleExecutable, options, filename))
    
    # Read results from triangle
    
    strippedName = os.path.splitext(filename)[0]
    
    nodeFilename = "%s.1.node" % strippedName
    elementFilename = "%s.1.ele" % strippedName
    polyFilename = "%s.1.poly" % strippedName
    
    # Read vertices
    
    allVertices = None
    boundaryVertices = {}
    
    if os.path.exists(nodeFilename):
        nodeFile = open(nodeFilename, "r")
        nodeInfo = list(map(int, nodeFile.readline().split()))
        
        nNodes = nodeInfo[0]
        
        allVertices = zeros([nNodes,2], 'd')
        
        for i in range(nNodes):
            vertexRow = list(map(float, nodeFile.readline().split()))
            
            boundaryMarker = int(vertexRow[3])
            
            if not (boundaryMarker in boundaryVertices):
                boundaryVertices[boundaryMarker] = []
            
            allVertices[i,:] = [vertexRow[1], vertexRow[2]]
            boundaryVertices[boundaryMarker].append(i+1)
            
        nodeFile.close()
               
    # Read elements
            
    elements = []
        
    if os.path.exists(elementFilename):
        elementFile = open(elementFilename, "r")
        elementInfo = list(map(int, elementFile.readline().split()))
        
        nElements = elementInfo[0]
        
        elements = zeros([nElements,3],'i')
        
        for i in range(nElements):
            elementRow = list(map(int, elementFile.readline().split()))
            elements[i,:] = [elementRow[1]+1, elementRow[2]+1, elementRow[3]+1]
            
        elementFile.close()
            
    # Clean up
    
    try:
        pass
        #os.remove(filename)
        #os.remove(nodeFilename)
        #os.remove(elementFilename)
        #os.remove(polyFilename)
    except:
        pass
    
    # Add dofs in edof and bcVerts
    
    dofs = createdofs(size(allVertices,0),dofsPerNode)
    
    if dofsPerNode>1:
        expandedElements = zeros((size(elements,0),3*dofsPerNode),'i')
        dofs = createdofs(size(allVertices,0),dofsPerNode)
        
        elIdx = 0
        
        for elementTopo in elements:        
            for i in range(3):
                expandedElements[elIdx,i*dofsPerNode:(i*dofsPerNode+dofsPerNode)] = dofs[elementTopo[i]-1,:]
            elIdx += 1
            
        for bVertIdx in boundaryVertices.keys():
            bVert = boundaryVertices[bVertIdx]
            bVertNew = []
            for i in range(len(bVert)):
                for j in range(dofsPerNode):
                    bVertNew.append(dofs[bVert[i]-1][j])
                    
            boundaryVertices[bVertIdx] = bVertNew
            
        return allVertices, asarray(expandedElements), dofs, boundaryVertices
        
    
    return allVertices, elements, dofs, boundaryVertices

def eldraw2(ex, ey):
    """
    Draw elements in 2d.
    
    Parameters:
    
        ex, ey          Element coordinates
        plotpar         (not implemented yet)
    
    """
    #if not haveWx:
    #    print("wxPython not installed.")
    #    return
    
    #class ElDispApp(wx.App):
    #    def OnInit(self):
    #        wx.InitAllImageHandlers()
    #        mainWindow = ElementView(None, -1, "")
    #        mainWindow.ex = ex
    #        mainWindow.ey = ey
    #        mainWindow.showNodalValues = False
    #        self.SetTopWindow(mainWindow)
    #        mainWindow.Show()
    #        return 1
    #
    #app = ElDispApp(0)
    #app.MainLoop()
    mainWindow = ElementView(None, -1, "")
    mainWindow.ex = ex
    mainWindow.ey = ey
    mainWindow.showNodalValues = False
    mainWindow.Show()   
    globalWindows.append(mainWindow)
    
def eliso2(ex, ey, ed, showMesh=False):
    """
    Draw nodal values in 2d.
    
    Parameters:
    
        ex, ey          Element coordinates
        ed              Element nodal values
        plotpar         (not implemented yet)
    
    """
    #if not haveWx:
    #    print("wxPython not installed.")
    #    return
    
    #class ElDispApp(wx.App):
    #    def OnInit(self):
    #        wx.InitAllImageHandlers()
    #        mainWindow = ElementView(None, -1, "")
    #        mainWindow.ex = ex
    #        mainWindow.ey = ey
    #        mainWindow.ed = ed
    #        mainWindow.showMesh = showMesh
    #        mainWindow.showNodalValues = True
    #        self.SetTopWindow(mainWindow)
    #        mainWindow.Show()
    #        return 1   
    #
    #app = ElDispApp(0)
    #app.MainLoop()
    mainWindow = ElementView(None, -1, "")
    mainWindow.ex = ex
    mainWindow.ey = ey
    mainWindow.ed = ed
    mainWindow.showMesh = showMesh
    mainWindow.showNodalValues = True
    mainWindow.Show()
    globalWindows.append(mainWindow)
    
def elval2(ex, ey, ev, showMesh=False):
    """
    Draw elements values in 2d.
    
    Parameters:
    
        ex, ey          Element coordinates
        ev              Element values (scalar)
        plotpar         (not implemented yet)
    
    """
    #if not haveWx:
    #    print("wxPython not installed.")
    #    return
    
    mainWindow = ElementView(None, -1, "")
    mainWindow.ex = ex
    mainWindow.ey = ey
    mainWindow.ev = ev
    mainWindow.showMesh = showMesh
    mainWindow.showElementValues = True
    mainWindow.showNodalValues = False
    mainWindow.Show()
    globalWindows.append(mainWindow)
    
def eldisp2(ex, ey, ed, magnfac=0.1, showMesh=True):
    #if not haveWx:
    #    print("wxPython not installed.")
    #    return
        
    mainWindow = ElementView(None, -1, "")
    mainWindow.dofsPerNode = 2
    mainWindow.ex = ex
    mainWindow.ey = ey
    mainWindow.ed = ed
    mainWindow.showMesh = showMesh
    mainWindow.showNodalValues = False
    mainWindow.showDisplacements = True
    mainWindow.magnfac = magnfac
    mainWindow.Show()    
    globalWindows.append(mainWindow)
           
def waitDisplay():
    if haveQt:
        globalQtApp.exec_()        
    else:
        globalWxApp.MainLoop()
        

def show():
    if haveQt:
        globalQtApp.exec_()
    else:
        globalWxApp.MainLoop()

def elmargin(scale=0.2):
    a = gca()
    xlim = a.get_xlim()
    ylim = a.get_ylim()
    xs = xlim[1]-xlim[0]
    ys = ylim[1]-ylim[0]
    a.set_xlim([xlim[0]-xs*scale,xlim[1]+xs*scale])
    a.set_ylim([ylim[0]-ys*scale,ylim[1]+ys*scale])
    
def scalfact2(ex,ey,ed,rat=0.2):
    """
    Determine scale factor for drawing computational results, such as 
    displacements, section forces or flux.
    
    Parameters:
    
        ex, ey      element node coordinates
                       
        ed          element displacement matrix or section force matrix
    
        rat         relation between illustrated quantity and element size. 
                    If not specified, 0.2 is used.
        
    """

    nen = -1
    if ex.shape != ey.shape:
        print("ex and ey shapes do not match.")
        return 1.0
    
    dlmax = 0.
    edmax = 1.
    
    if rank(ex)==1:
        nen = ex.shape[0]
        nel = 1
        dxmax=ex.T.max()-ex.T.min()
        dymax=ey.T.max()-ey.T.min()
        dlmax=max(dxmax,dymax);
        edmax=abs(ed).max();
    else:
        nen = ex.shape[1]
        nel = ex.shape[0]
        dxmax=ex.T.max()-ex.T.min()
        dymax=ey.T.max()-ey.T.min()
        dlmax=max(dxmax,dymax);
        edmax=abs(ed).max();
        
    k = rat
    return k*dlmax/edmax

def elcenter2d(ex, ey):
    exm = reshape(ex.sum(1)/ex.shape[1],[ex.shape[0],1])
    eym = reshape(ey.sum(1)/ey.shape[1],[ey.shape[0],1])

    return hstack([exm,eym])