# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#

import os
import bpy
from mathutils import Vector
from mathutils import Quaternion
from mathutils import Euler
from . import kex_utils
from . import turok_object_panel
from bpy_extras.wm_utils.progress_report import (
    ProgressReport,
    ProgressReportSubstep,
)

# -----------------------------------------------------------------------------
#
def save(context,
         filepath,
         *,
         use_selection=True,
         global_matrix=None,
         path_mode='AUTO',
         animNLATracks=False
         ):
    return export_anim(filepath, context, animNLATracks)
#end func
# -----------------------------------------------------------------------------
#
def export_anim(filepath, context, animNLATracks):
    obj = context.object
    #check obj parents for an armature object and use that instead if found
    while True:
        if obj.parent is None or obj.parent.type != "ARMATURE":
            break
        obj = obj.parent
    #end while
    
    validateResults = kex_utils.T1ValidateModel(context, obj, animNLATracks)
    if len(validateResults["errors"]) > 0:
        return {'CANCELLED'}
    #end if
    
    actions = validateResults["actions"]
    scene = context.scene
    framesPerGameFrame = int(scene.render.fps / kex_utils.turok1FPS)
    nodeCount = len(obj.data.bones)
    with ProgressReport(context.window_manager) as progress:
        progress.enter_substeps(1)
        print("Exporting Turok Anim %r ..." % filepath)

        actions.sort(key=lambda x: x.t1.animID)    
        
        #get list of armature bones and sort them by Turok boneIndex
        bones = []
        for bone in obj.data.bones:
            bones.append(bone)
        bones.sort(key=lambda x: x.t1.boneIndex)
            
        #Save the anim .bin file
        with open(filepath, 'wb') as data:
            data.seek(0)
            kex_utils.write32(data, 1, False) #version
            kex_utils.write32(data, len(actions), False) #animCount
            progress.enter_substeps(len(actions))
            for action in actions:
                frameStart, frameEnd = action.frame_range
                frameStart = int(frameStart)
                frameEnd = int(frameEnd)
                frameCount = int((frameEnd - frameStart) / framesPerGameFrame) + 1
                boneLocs = [] * nodeCount
                boneRots = [] * nodeCount
                boneRotsEuler = [] * nodeCount
                boneRotsAA = [] * nodeCount
                boneRotsMode = [] * nodeCount
                boneTransIndex = [-1] * nodeCount
                boneRotsIndex = [-1] * nodeCount
                for i in range(nodeCount):
                    boneLocs.append([])
                    boneRots.append([])
                    boneRotsEuler.append([])
                    boneRotsAA.append([])
                    boneRotsMode.append([])
                    for i2 in range(frameCount):
                        boneLocs[i].append(Vector((0.0, 0.0, 0.0)))
                        boneRots[i].append(Quaternion((1.0, 0.0, 0.0, 0.0)))
                        boneRotsEuler[i].append(Euler((0.0, 0.0, 0.0), 'XYZ'))
                        boneRotsAA[i].append(Quaternion((1.0, 0.0, 0.0, 0.0)))
                        boneRotsMode[i].append(-1)
                    #end for
                #end for
                yawOffsets = [0.0] * frameCount
                for frame in range(frameCount):
                    realFrame = frameStart + (frame * framesPerGameFrame)
                    hasRotQuat = [False] * 4
                    hasRotEuler = [False] * 3
                    hasRotAA = [False] * 4
                    for f in action.fcurves:
                        propObjPath, propDot, propType = f.data_path.rpartition('.')
                        propObj = obj.path_resolve(propObjPath)
                        if type(propObj) is bpy.types.PoseBone:
                            bone = obj.data.bones[propObj.name]
                            boneIndex = kex_utils.index_of(bones, bone)
                            if propType == "location":
                                boneLocs[boneIndex][frame][f.array_index] = f.evaluate(realFrame)
                            elif propType == "rotation_quaternion":
                                boneRots[boneIndex][frame][f.array_index] = f.evaluate(realFrame)
                                hasRotQuat[f.array_index] = True
                                if hasRotQuat[0] and hasRotQuat[1] and hasRotQuat[2] and hasRotQuat[3]:
                                    boneRotsMode[boneIndex][frame] = 0
                            elif propType == "rotation_euler":
                                boneRotsEuler[boneIndex][frame][f.array_index] = f.evaluate(realFrame)
                                if boneRotsMode[boneIndex][frame] == -1 or boneRotsMode[boneIndex][frame] > 1:
                                    hasRotEuler[f.array_index] = True
                                    if hasRotEuler[0] and hasRotEuler[1] and hasRotEuler[2]:
                                        boneRotsMode[boneIndex][frame] = 1
                            elif propType == "rotation_axis_angle":
                                boneRotsAA[boneIndex][frame][f.array_index] = f.evaluate(realFrame)
                                if boneRotsMode[boneIndex][frame] == -1:
                                    hasRotAA[f.array_index] = True
                                    if hasRotAA[0] and hasRotAA[1] and hasRotAA[2] and hasRotAA[3]:
                                        boneRotsMode[boneIndex][frame] = 2
                            #end if
                        elif type(propObj) is turok_object_panel.TurokObjectSettings:
                            if propType == "yawOffset":
                                yawOffsets[frame] = f.evaluate(realFrame)
                            #end if
                        #end if
                    #end for
                #end for
                
                #check if need to use euler or axis angle over Quaternion
                for i in range(nodeCount):
                    for frame in range(frameCount):
                        if boneRotsMode[i][frame] == 1: #Euler
                            boneRots[i][frame] = boneRotsEuler[i][frame].to_quaternion()
                        elif boneRotsMode[i][frame] == 2: #Axis Angle
                            q = boneRotsAA[i][frame]
                            boneRots[i][frame] = Quaternion(Vector((q[1], q[2], q[3])), q[0])
                        #end if
                    #end for
                #end for
                
                transIndexs = [] #values are nodeID
                rotsIndexs = [] #values are nodeID
                #check each bone loc/rot and if they are ever different in any frame then use the next transIndex/rotIndex
                for i in range(nodeCount):
                    #check if the bones Locs are exactly the same as another transIndex
                    for i2 in range(len(transIndexs)):
                        transNodeIndex = transIndexs[i2]
                        if boneLocs[i] == boneLocs[transNodeIndex]:
                            boneTransIndex[i] = i2
                            break
                        #end if
                    #end for
                    if boneTransIndex[i] == -1:
                        for frame in range(1, frameCount):
                            if boneLocs[i][frame] != boneLocs[i][0]:
                                boneTransIndex[i] = len(transIndexs)
                                transIndexs.append(i)
                                break
                            #end if
                        #end for
                    #end if
                    
                    #check if the bones Rots are exactly the same as another rotsIndex
                    for i2 in range(len(rotsIndexs)):
                        rotsNodeIndex = rotsIndexs[i2]
                        if boneRots[i] == boneRots[rotsNodeIndex]:
                            boneRotsIndex[i] = i2
                            break
                        #end if
                    #end for
                    if boneRotsIndex[i] == -1:
                        for frame in range(1, frameCount):
                            if boneRots[i][frame] != boneRots[i][0]:
                                boneRotsIndex[i] = len(rotsIndexs)
                                rotsIndexs.append(i)
                                break
                            #end if
                        #end for
                    #end if
                #end for
                
                transCount = len(transIndexs) #check if key frames are different for each bone if it's the same from start to finish then use that as initial position
                rotCount = len(rotsIndexs) #check if key frames are different for each bone if it's the same from start to finish then use that as initial position
                
                kex_utils.write32(data, action.t1.animID, True) #animID
                kex_utils.write32(data, frameCount, False) #frameCount
                kex_utils.write32(data, nodeCount, False) #nodeIndexes (trans/rot indexes)
                kex_utils.write32(data, nodeCount, False) #initalNodes (inital translation/rotation)
                kex_utils.write32(data, transCount, False) #translations for each frame
                kex_utils.write32(data, rotCount, False) #rotations for each frame
                kex_utils.write32(data, len(action.t1.keyFrames), False) #KeyframeAction Count
                kex_utils.write32(data, 1 if action.t1.marker else 0, False) #marker
                if action.t1.marker:
                    kex_utils.write16(data, action.t1.blend, False) #blend
                    kex_utils.write16(data, action.t1.loopFrame, False) #loopFrame
                #end if
                for i in range(nodeCount):
                    kex_utils.write16(data, boneTransIndex[i], True) #translation index
                    kex_utils.write16(data, boneRotsIndex[i], True) #rotation index
                #end for
                for i in range(nodeCount):
                    kex_utils.writeVector(data, boneLocs[i][0]) #initial translation
                    kex_utils.writeQuat(data, boneRots[i][0]) #initial rotation
                #end for
                for i in range(frameCount):
                    kex_utils.writeFloat(data, yawOffsets[i]) #yaw offset
                #end for
                for i in range(transCount):
                    for i2 in range(frameCount):
                        kex_utils.writeVector(data, boneLocs[transIndexs[i]][i2])
                    #end for
                #end for
                for i in range(rotCount):
                    for i2 in range(frameCount):
                        kex_utils.writeQuat(data, boneRots[rotsIndexs[i]][i2])
                    #end for
                #end for
                for kfa in action.t1.keyFrames:
                    kex_utils.write32(data, kfa.event, True)
                    kex_utils.write32(data, kfa.frame, False)
                    kex_utils.writeFloat(data, kfa.arg1)
                    kex_utils.writeFloat(data, kfa.arg2)
                    kex_utils.writeFloat(data, kfa.arg3)
                    kex_utils.writeFloat(data, kfa.arg4)
                #end for
                progress.step()
            #end for
        #end with
        progress.leave_substeps("Animation Successfully Exported! (Animations: %i)" % (len(actions)))
    #end with
    
    return {'FINISHED'}
#end func