from struct import pack
from .vector import *
from .formatdecimal import decimal
from numbers import Number 
import os
import sys

try:
    basestring
except:
    basestring = str

def isColorTriangleList(polys):
    return isinstance(polys[0][1][0][0], Number)
    
def toPolyhedra(polys):
    if isColorTriangleList(polys):
        return [ (polys[0][0], list(face for rgb,face in polys)) ]
    else:
        return polys
        
def toMesh(polys):
    if isColorTriangleList(polys):
        return polys
    else:
        output = []
        for rgb,polyhedron in polys:
            for face in polyhedron:
                output.append((rgb,face))
        return output

def describeColor(c):
    if c is None:
        return "undef";
    elif isinstance(c, str):
        return c
    else:
        return "[%s,%s,%s]" % tuple(decimal(component) for component in c)

def toSCADModule(polys, moduleName, digitsAfterDecimal=9, colorOverride=None):
    """
    INPUT:
    polys: list of (color,polyhedra) pairs (counterclockwise triangles), or a list of (color,triangle) pairs (TODO: currently uses first color for all in latter case)
    moduleName: OpenSCAD module name
    
    OUTPUT: string with OpenSCAD code implementing the polys
    """
    
    polys = toPolyhedra(polys)
    
    scad = []
    scad.append("module " +moduleName+ "() {")
    for rgb,poly in polys:
        if colorOverride != "" and (colorOverride or rgb):
            line = "  color(%s) " % describeColor(colorOverride if colorOverride else tuple(min(max(c,0.),1.0) for c in rgb))
        else:
            line = "  "
        pointsDict = {}
        i = 0
        line += "polyhedron(points=["
        points = []
        for face in poly:
            for v in reversed(face):
                if tuple(v) not in pointsDict:
                    pointsDict[tuple(v)] = i
                    points.append( ("[%s,%s,%s]") % tuple(decimal(x,digitsAfterDecimal) for x in v) )
                    i += 1
        line += ",".join(points)
        line += "], faces=["
        line += ",".join( "[" + ",".join(str(pointsDict[tuple(v)]) for v in reversed(face)) + "]" for face in poly ) + "]"
        line += ");"
        scad.append(line)
    scad.append("}\n")
    return "\n".join(scad)

def saveSCAD(filename, polys, moduleName="object1", quiet=False):
    """
    filename: filename to write OpenSCAD file
    polys: list of (color,polyhedra) pairs (counterclockwise triangles)
    moduleName: OpenSCAD module name
    quiet: give no status message if set
    """
    if not quiet: sys.stderr.write("Saving %s\n" % filename)
    if filename:
        with open(filename, "w") as f:
            f.write(toSCADModule(polys, moduleName))
            f.write("\n" + moduleName + "();\n")
    else:
        sys.stdout.write(toSCADModule(polys, moduleName))
        sys.stdout.write("\n" + moduleName + "();\n")

def saveSTL(filename, mesh, swapYZ=False, quiet=False):
    """
    filename: filename to save STL file
    mesh: list of (color,triangle) pairs (counterclockwise)
    swapYZ: should Y/Z axes be swapped?
    quiet: give no status message if set
    """
    
    mesh = toMesh(mesh)
    
    if not quiet: sys.stderr.write("Saving %s\n" % filename)
    minY = float("inf")
    minVector = Vector(float("inf"),float("inf"),float("inf"))
    numTriangles = 0
    if swapYZ:
        matrix = Matrix( (1,0,0), (0,0,-1), (0,1,0) )
    else:
        matrix = Matrix.identity(3)
        
    mono = True
    for rgb,triangle in mesh:
        if rgb is not None:
            mono = False
        numTriangles += 1
        for vertex in triangle:
            vertex = matrix*vertex
            minVector = Vector(min(minVector[i], vertex[i]) for i in range(3))
    minVector -= Vector(0.001,0.001,0.001) # make sure all STL coordinates are strictly positive as per Wikipedia
    
    def writeSTL(write):
        write(pack("80s",b''))
        write(pack("<I",numTriangles))
        for rgb,tri in mesh:
            if mono:
                color = 0
            else:
                if rgb is None:
                    rgb = (255,255,255)
                else:
                    rgb = tuple(min(255,max(0,int(0.5 + 255 * comp))) for comp in rgb)
                color = 0x8000 | ( (rgb[0] >> 3) << 10 ) | ( (rgb[1] >> 3) << 5 ) | ( (rgb[2] >> 3) << 0 )
            normal = (Vector(tri[1])-Vector(tri[0])).cross(Vector(tri[2])-Vector(tri[0])).normalize()
            write(pack("<3f", *(matrix*normal)))
            for vertex in tri:
                write(pack("<3f", *(matrix*(vertex-minVector))))
            write(pack("<H", color))            

    if filename:
        with open(filename, "wb") as f:
            writeSTL(f.write)
    else:
        if sys.platform == "win32":
            import msvcrt
            msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
        writeSTL(lambda data : os.write(sys.stdout.fileno(), data))
            
[((83, 18, 83, 60), 'sys.stderr.write', 'sys.stderr.write', ({(83, 35, 83, 59): "('Saving %s\\n' % filename)"}, {}), "('Saving %s\\n' % filename)", False, 'import sys\n'), ((90, 8, 90, 53), 'sys.stdout.write', 'sys.stdout.write', ({(90, 25, 90, 52): "('\\n' + moduleName + '();\\n')"}, {}), "('\\n' + moduleName + '();\\n')", False, 'import sys\n'), ((102, 18, 102, 60), 'sys.stderr.write', 'sys.stderr.write', ({(102, 35, 102, 59): "('Saving %s\\n' % filename)"}, {}), "('Saving %s\\n' % filename)", False, 'import sys\n'), ((122, 14, 122, 29), 'struct.pack', 'pack', ({(122, 19, 122, 24): '"""80s"""', (122, 25, 122, 28): "b''"}, {}), "('80s', b'')", False, 'from struct import pack\n'), ((123, 14, 123, 37), 'struct.pack', 'pack', ({(123, 19, 123, 23): '"""<I"""', (123, 24, 123, 36): 'numTriangles'}, {}), "('<I', numTriangles)", False, 'from struct import pack\n'), ((134, 18, 134, 47), 'struct.pack', 'pack', ({(134, 23, 134, 28): '"""<3f"""', (134, 30, 134, 45): '*(matrix * normal)'}, {}), "('<3f', *(matrix * normal))", False, 'from struct import pack\n'), ((137, 18, 137, 35), 'struct.pack', 'pack', ({(137, 23, 137, 27): '"""<H"""', (137, 29, 137, 34): 'color'}, {}), "('<H', color)", False, 'from struct import pack\n'), ((145, 27, 145, 46), 'sys.stdout.fileno', 'sys.stdout.fileno', ({}, {}), '()', False, 'import sys\n'), ((136, 22, 136, 63), 'struct.pack', 'pack', ({(136, 27, 136, 32): '"""<3f"""', (136, 34, 136, 61): '*(matrix * (vertex - minVector))'}, {}), "('<3f', *(matrix * (vertex - minVector)))", False, 'from struct import pack\n'), ((146, 40, 146, 59), 'sys.stdout.fileno', 'sys.stdout.fileno', ({}, {}), '()', False, 'import sys\n')]