# exports blender meshs to .antmesh files

import bpy
import bmesh
import struct
import mathutils
import os
import random
import datetime

f = None
buf_indices = []
buf_vertices = []
buf_dict = {}
buf_objects = []
materials_dict = {}
out_materials = []

MatIdentity = mathutils.Matrix(([ 1, 0, 0, 0],
                                [ 0, 1, 0, 0],
                                [ 0, 0, 1, 0],
                                [ 0, 0, 0, 1]))
MatGL = mathutils.Matrix(([-1, 0, 0, 0],
                          [ 0, 0, 1, 0],
                          [ 0, 1, 0, 0],
                          [ 0, 0, 0, 1]))
InvMatGL = MatGL.inverted()

def log(*args, **kwargs):
    # Prints to python console, not the system console
    for a in bpy.context.screen.areas:
        if a.type == 'CONSOLE':
            c = {}
            c['area'] = a
            c['space_data'] = a.spaces.active
            c['region'] = a.regions[-1]
            c['window'] = bpy.context.window
            c['screen'] = bpy.context.screen
            s = " ".join([str(arg) for arg in args])
            for line in s.split("\n"):
                bpy.ops.console.scrollback_append(c, text=line)
                
def remember_pos(): return f.tell()
def save_uint8(val): f.write(struct.pack('<B', val))
def save_uint16(val): f.write(struct.pack('<H', val))
def save_uint32(val): f.write(struct.pack('<I', val))
def save_float(val): f.write(struct.pack('f', val))
def save_double(val): f.write(struct.pack('d', val))
def fix_uint32(pos, newval):
    whence_end = 2
    f.seek(pos)
    save_uint32(newval)
    f.seek(0, whence_end)

def save_string_raw(text):
    encoded_text = text.encode('utf-8')
    f.write(encoded_text)

def save_string(text):
    encoded_text = text.encode('utf-8')
    save_uint32(len(encoded_text))
    f.write(encoded_text)
    
def on_mesh(mesh):
    log('MESH ', mesh)
    bm = bmesh.new()
    bm.from_mesh(mesh.data)
    bmesh.ops.triangulate(bm, faces=bm.faces[:])
    #log('Faces: ', len(bm.faces))
    for face in bm.faces:
        material_index = face.material_index
        #log("FACE with ", len(face.verts), " vertices")
        for vert in face.verts:
            pos = vert.co
            normal = vert.normal
            #log(material_index, pos.x, pos.y, pos.z, normal.x, normal.y, normal.z)
    triMesh = bpy.data.meshes.new(name = '~temp~')
    bm.to_mesh(triMesh)
    triMesh.calc_loop_triangles()
    bm.free()
    
    buf_objects.append({
        "ref": mesh,
        "type": 1,
        "name": mesh.name,
        "index": len(buf_indices),
        "count": 0,
        "properties": [],
        "matrix": mesh.matrix_local,
        "material": obj.data.materials.values()[0].name if len(obj.data.materials.values()) > 0 else "",
    })
    # uvs = triMesh.tessface_uv_textures
    try:
        uv_layer_0 = triMesh.uv_layers.values()[0]
        uv_layer_0_name = triMesh.uv_layers.keys()[0]
    except IndexError:
        uv_layer_0 = None
        uv_layer_0_name = ""
    for f in triMesh.loop_triangles:
        for index in range(3):
            vertex = triMesh.vertices[f.vertices[index]]
            i = f.loops[index]
            # vc = MatGL * vertex.co
            # vn = MatGL * vertex.normal
            vc = vertex.co
            vn = vertex.normal
            # vc = MatGL @ vc
            # vn = MatGL @ vn
            if uv_layer_0:
                t0 = uv_layer_0.data[i].uv
            else:
                t0 = [0, 1.0]
            
            vp = ( vc.x, vc.y, vc.z,
                    vn.x, vn.y, vn.z,
                    t0[0], 1.0 - t0[1],
                    t1[0], 1.0 - t1[1]
                    )
            index = buf_dict.setdefault(vp, len(buf_dict))
            buf_indices.append(index)
            if index >= len(buf_vertices):
                buf_vertices.append(vp)
                if mesh.name == "monster_move00":
                    if uv_layer_0:
                        t0[1] = 1.0 - t0[1]
                        log(mesh.name, " ", uv_layer_0_name, " ", t0)
                    log(vp)
    buf_objects[-1]["count"] = len(buf_indices) - buf_objects[-1]["index"]
            
    bpy.data.meshes.remove(triMesh)
    
    for key, value in mesh.items():
        if isinstance(value, str):
            log(key, ' = ', value)
        elif isinstance(value, int):
            log(key, ' = ', value)
            
    for slot in mesh.material_slots:
        if slot.material:
            log(mesh.name, " HAS MATERIAL ", slot.material.name)
    
def on_light(light):
    log('Ignore LIGHT ', light)
    
def on_camera(camera):
    buf_objects.append({
        "ref": camera,
        "type": 2,
        "name": camera.name,
        "index": 0,
        "count": 0,
        "properties": [],
        "matrix": camera.matrix_local,
        "material": "",
    })
    
def on_unknown_object(obj):
    log('Ignore ', obj.type, ' ', obj)

log('')
log('Exporting...')

pre, ext = os.path.splitext(bpy.data.filepath)
oytlvl_file_name = pre + '.oytlvl'
f = open(oytlvl_file_name, 'wb')

save_string_raw('oytlvl1 ')
save_uint32(0)

for obj in bpy.data.objects:
    if obj.type == "MESH" and len(obj.data.materials.values()) > 0:
        on_material(obj.data.materials.values()[0])

for obj in bpy.data.objects:
    if obj.type == 'MESH':
        on_mesh(obj)
    elif obj.type == 'LIGHT':
        on_light(obj)
    elif obj.type == 'CAMERA':
        on_camera(obj)
    else:
        on_unknown_object(obj)
        
# vertices and faces
save_uint32(len(buf_indices))
for index in buf_indices:
    save_uint16(index)
save_uint32(len(buf_vertices))
for vertex in buf_vertices:
    for i in range(10):
        save_float(vertex[i])
        
# materials
save_uint32(len(out_materials))
for material in out_materials:
    pos = remember_pos()
    save_uint32(0)
    save_string(material["name"])
    save_string(material["diffusetex"])
    save_string(material["lightmaptex"])
    for i in range(4):
        save_float(material["color"][i])
    #fix_uint32(pos, remember_pos() - pos)
    
# objects        
save_uint32(len(buf_objects))
for obj in buf_objects:
    save_uint8(obj["type"])
    save_uint32(obj["index"])
    save_uint32(obj["count"])
    m = obj["matrix"]
    # m = MatIdentity
    # m = MatGL @ m @ InvMatGL
    # m.transpose()
    for i in range(4):
        for j in range(4):
            save_float(float(m[j][i]))
    save_string(obj["name"])
    save_string(obj["material"]) # TODO use resolved index instead of name
    save_uint32(0) # TODO properties
        
f.close()      
        
log('EXPOERTED ', datetime.datetime.now())
