﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Text.RegularExpressions;
using UnityEditor;
using System.IO;

public class T1AnimMono : MonoBehaviour
{
    public T1Animation anim;

    [HideInInspector]
    public GameObject modelGO;
}

[Serializable]
public class T1Animation
{
    public int version; //always 1
    
    public T1AnimationBlock[] blocks;

    public T1Animation()
    {
        version = 1;
        blocks = new T1AnimationBlock[0];
    }

    public void Validate()
    {
        //Debug.Log("----------Starting Animation Validation----------");

        if (version != 1)
        {
            Debug.LogError("Version must be 1");
            version = 1;
        }

        for (int i = 0; i < blocks.Length; i++)
        {
            T1AnimationBlock block = blocks[i];
            string blockPrefix = "[Block " + i + "] ";
            if (block.frameCount < 2)
            {
                Debug.LogError(blockPrefix + "Frame Count must be 2 or more");
                block.frameCount = 2;
            }

            if (block.loopFrame >= block.frameCount)
            {
                Debug.LogError(blockPrefix + "Loop Frame must be " + (block.frameCount - 1) + " or lower");
                block.loopFrame = 2;
            }

            for (int j = 0; j < block.nodeIndexes.Length; j++)
            {
                T1AnimNodeIndex nodeIndex = block.nodeIndexes[j];
                if (nodeIndex.translationIndex < -1)
                {
                    Debug.LogError("[Block " + i + " Node Indexes " + j + "] Translation Index can not be a negative value");
                    nodeIndex.translationIndex = -1;
                }
                else if (nodeIndex.translationIndex >= 0 && nodeIndex.translationIndex >= block.translations.Length)
                {
                    Debug.LogError("[Block " + i + " Node Indexes " + j + "] Translation Index must be " + (block.translations.Length - 1) + " or lower");
                    nodeIndex.translationIndex = (short)(block.translations.Length - 1);
                }

                if (nodeIndex.rotationIndex < -1)
                {
                    Debug.LogError("[Block " + i + " Node Indexes " + j + "] Rotation Index can not be a negative value");
                    nodeIndex.rotationIndex = -1;
                }
                else if (nodeIndex.rotationIndex >= 0 && nodeIndex.rotationIndex >= block.rotations.Length)
                {
                    Debug.LogError("[Block " + i + " Node Indexes " + j + "] Rotation Index must be " + (block.rotations.Length - 1) + " or lower");
                    nodeIndex.rotationIndex = (short)(block.rotations.Length - 1);
                }
            }

            if (block.initalNodes.Length > block.nodeIndexes.Length)
            {
                Debug.LogError(blockPrefix + " Inital Nodes must have " + (block.nodeIndexes.Length - 1) + " or lower elements");
                Array.Resize(ref block.initalNodes, block.nodeIndexes.Length);
            }

            if (block.yawOffsets.Length != block.frameCount)
            {
                Debug.LogError("[Block " + i + "] Yaw Offets must contain " + block.frameCount + " elements");
                Array.Resize(ref block.yawOffsets, block.frameCount);
            }

            for (int j = 0; j < block.translations.Length; j++)
            {
                T1AnimTranslation translation = block.translations[j];

                bool isUsed = false;
                for (int n = 0; n < block.nodeIndexes.Length; n++)
                {
                    if (block.nodeIndexes[n].translationIndex == j)
                    {
                        isUsed = true;
                        break;
                    }
                }

                if (!isUsed)
                {
                    Debug.LogWarning("[Block " + i + " Translations " + j + "] is not used by any of the Node Indexes");
                }

                if (translation.translation.Length != block.frameCount)
                {
                    Debug.LogError("[Block " + i + " Translations " + j + "] must contain " + block.frameCount + " translations");
                    Array.Resize(ref translation.translation, block.frameCount);
                }
            }

            for (int j = 0; j < block.rotations.Length; j++)
            {
                T1AnimRotation rot = block.rotations[j];

                bool isUsed = false;
                for (int n = 0; n < block.nodeIndexes.Length; n++)
                {
                    if (block.nodeIndexes[n].rotationIndex == j)
                    {
                        isUsed = true;
                        break;
                    }
                }

                if (!isUsed)
                {
                    Debug.LogWarning("[Block " + i + " Rotations " + j + "] is not used by any of the Node Indexes");
                }

                if (rot.rotation.Length != block.frameCount)
                {
                    Debug.LogError("[Block " + i + " Rotations " + j + "] must contain " + block.frameCount + " rotations");
                    Array.Resize(ref rot.rotation, block.frameCount);
                }
            }

            for (int j = 0; j < block.keyFrameActions.Length; j++)
            {
                T1AnimKeyFrameAction keyFrame = block.keyFrameActions[j];
                string kfPrefix = "[Block " + i + " Key Frame Action " + j + "] ";
                if (keyFrame.frame < 0)
                {
                    Debug.LogError(kfPrefix + "Frame " + keyFrame.frame + " can not be a negative value");
                    keyFrame.frame = 0;
                }
                else if (keyFrame.frame >= block.frameCount)
                {
                    Debug.LogError(kfPrefix + "Frame " + keyFrame.frame + " must be " + (block.frameCount - 1) + " or lower");
                    keyFrame.frame = (uint)(block.frameCount - 1);
                }
            }
        }
        //Debug.Log("----------Finished Animation Validation----------");
    }

    public void LoadAnim(string path)
    {
        IOUtils.readData = File.ReadAllBytes(path);
        IOUtils.readByteIndex = 0;

        version = IOUtils.ReadInt32();
        int blockLength = IOUtils.ReadInt32();
        blocks = new T1AnimationBlock[blockLength];
        for (int i = 0; i < blockLength; i++)
        {
            blocks[i] = new T1AnimationBlock();
            T1AnimationBlock block = blocks[i];
            block.animID = IOUtils.ReadInt32();
            block.frameCount = IOUtils.ReadInt32();
            int nodeIndexLength = IOUtils.ReadInt32();
            int intialNodeLength = IOUtils.ReadInt32();
            int translationsLength = IOUtils.ReadInt32();
            int rotationsLength = IOUtils.ReadInt32();
            int keyFrameActionsLength = IOUtils.ReadInt32();
            block.marker = IOUtils.ReadUInt32();
            if (block.marker == 1)
            {
                block.blendLength = IOUtils.ReadUInt16();
                block.loopFrame = IOUtils.ReadUInt16();
            }

            block.nodeIndexes = new T1AnimNodeIndex[nodeIndexLength];
            for (int j = 0; j < nodeIndexLength; j++)
            {
                block.nodeIndexes[j] = new T1AnimNodeIndex();
                T1AnimNodeIndex nodeIndex = block.nodeIndexes[j];
                nodeIndex.translationIndex = IOUtils.ReadInt16();
                nodeIndex.rotationIndex = IOUtils.ReadInt16();
            }

            block.initalNodes = new T1AnimInitalNode[intialNodeLength];
            for (int j = 0; j < intialNodeLength; j++)
            {
                block.initalNodes[j] = new T1AnimInitalNode();
                T1AnimInitalNode initalNode = block.initalNodes[j];
                initalNode.translation = IOUtils.ReadVector3();
                initalNode.rotation = IOUtils.ReadVector4();
            }

            block.yawOffsets = new float[block.frameCount];
            for (int j = 0; j < block.frameCount; j++)
            {
                block.yawOffsets[j] = IOUtils.ReadFloat();
            }

            block.translations = new T1AnimTranslation[translationsLength];
            for (int j = 0; j < translationsLength; j++)
            {
                block.translations[j] = new T1AnimTranslation();
                block.translations[j].translation = new Vector3[block.frameCount];
                for (int n = 0; n < block.frameCount; n++)
                {
                    block.translations[j].translation[n] = IOUtils.ReadVector3();
                }
            }

            block.rotations = new T1AnimRotation[rotationsLength];
            for (int j = 0; j < rotationsLength; j++)
            {
                block.rotations[j] = new T1AnimRotation();
                block.rotations[j].rotation = new Vector4[block.frameCount];
                for (int n = 0; n < block.frameCount; n++)
                {
                    block.rotations[j].rotation[n] = IOUtils.ReadVector4();
                }
            }

            block.keyFrameActions = new T1AnimKeyFrameAction[keyFrameActionsLength];
            for (int j = 0; j < keyFrameActionsLength; j++)
            {
                block.keyFrameActions[j] = new T1AnimKeyFrameAction();
                T1AnimKeyFrameAction action = block.keyFrameActions[j];
                action.eventType = IOUtils.ReadUInt32();
                action.frame = IOUtils.ReadUInt32();
                action.arg0 = IOUtils.ReadFloat();
                action.arg1 = IOUtils.ReadFloat();
                action.arg2 = IOUtils.ReadFloat();
                action.arg3 = IOUtils.ReadFloat();
            }
        }
    }

    public void SaveAnim(string path)
    {
        IOUtils.writeData.Clear();

        IOUtils.WriteInt32(version);
        IOUtils.WriteInt32(blocks.Length);
        for (int i = 0; i < blocks.Length; i++)
        {
            T1AnimationBlock block = blocks[i];
            IOUtils.WriteInt32(block.animID);
            IOUtils.WriteInt32(block.frameCount);
            IOUtils.WriteInt32(block.nodeIndexes.Length);
            IOUtils.WriteInt32(block.initalNodes.Length);
            IOUtils.WriteInt32(block.translations.Length);
            IOUtils.WriteInt32(block.rotations.Length);
            IOUtils.WriteInt32(block.keyFrameActions.Length);
            IOUtils.WriteUInt32(block.marker);
            if (block.marker == 1)
            {
                IOUtils.WriteUInt16(block.blendLength);
                IOUtils.WriteUInt16(block.loopFrame);
            }

            for (int j = 0; j < block.nodeIndexes.Length; j++)
            {
                T1AnimNodeIndex nodeIndex = block.nodeIndexes[j];
                IOUtils.WriteInt16(nodeIndex.translationIndex);
                IOUtils.WriteInt16(nodeIndex.rotationIndex);
            }

            for (int j = 0; j < block.initalNodes.Length; j++)
            {
                T1AnimInitalNode initalNode = block.initalNodes[j];
                IOUtils.WriteVector3(initalNode.translation);
                IOUtils.WriteVector4(initalNode.rotation);
            }

            for (int j = 0; j < block.frameCount; j++)
            {
                IOUtils.WriteFloat(block.yawOffsets[j]);
            }

            for (int j = 0; j < block.translations.Length; j++)
            {
                for (int n = 0; n < block.translations[j].translation.Length; n++)
                {
                    IOUtils.WriteVector3(block.translations[j].translation[n]);
                }
            }

            for (int j = 0; j < block.rotations.Length; j++)
            {
                for (int n = 0; n < block.rotations[j].rotation.Length; n++)
                {
                    IOUtils.WriteVector4(block.rotations[j].rotation[n]);
                }
            }

            for (int j = 0; j < block.keyFrameActions.Length; j++)
            {
                T1AnimKeyFrameAction action = block.keyFrameActions[j];
                IOUtils.WriteUInt32(action.eventType);
                IOUtils.WriteUInt32(action.frame);
                IOUtils.WriteFloat(action.arg0);
                IOUtils.WriteFloat(action.arg1);
                IOUtils.WriteFloat(action.arg2);
                IOUtils.WriteFloat(action.arg3);
            }
        }

        File.WriteAllBytes(path, IOUtils.writeData.ToArray());
    }

    //Rotates the inital node positions and translations
    public void Rotate(float x, float y, float z)
    {
        Vector3 center = new Vector3(0.0f, 0.0f, 0.0f);
        Quaternion rotation = new Quaternion();
        rotation.eulerAngles = new Vector3(x, y, z);
        for (int i = 0; i < blocks.Length; i++)
        {
            T1AnimationBlock block = blocks[i];
            for (int j = 0; j < block.initalNodes.Length; j++)
            {
                block.initalNodes[j].translation = rotation * (block.initalNodes[j].translation - center) + center;
            }
            //Rotate Translations
            for (int j = 0; j < block.translations.Length; j++)
            {
                for (int n = 0; n < block.translations[j].translation.Length; n++)
                {
                    block.translations[j].translation[n] = rotation * (block.translations[j].translation[n] - center) + center;
                }
            }
        }
    }

    //Creates an AnimationBlock from an AnimationClip on the Root Gamobject
    public void AddBlockFromClip(AnimationClip animClip, GameObject rootGO)
    {
        //Create a new Animation Block
        T1AnimationBlock block = new T1AnimationBlock();
        ArrayUtility.Add(ref blocks, block);

        //Set AnimID by tag in the name of the AnimationClip file if theres one
        //Match match = Regex.Match(animClip.name, @"\[([0-9]+)\]");
        //if (match.Success)
        //{
        //    int result;
        //    if (int.TryParse(match.Groups[1].Value, out result))
        //        block.animID = result;
        //}

        block.frameCount = animClip.empty ? 2 : Mathf.Max(Mathf.FloorToInt(animClip.length * 15.0f) + 1, 2);
        block.marker = 1;
        block.blendLength = 0;
        block.loopFrame = 0;
        //if (animClip.wrapMode == WrapMode.Once)
        //    block.loopFrame = (ushort)(block.frameCount - 1);
        //else
        //    block.loopFrame = 0;

        Transform[] nodeTransforms = rootGO.GetComponentsInChildren<Transform>();
        int nodeLength = nodeTransforms.Length;

        //Set Initial Node Transform
        block.initalNodes = new T1AnimInitalNode[nodeLength];
        for (int i = 0; i < block.initalNodes.Length; i++)
        {
            Transform nodeTransform = nodeTransforms[i];
            block.initalNodes[i] = new T1AnimInitalNode();
            Vector3 pos = nodeTransform.localPosition;
            pos.x = -pos.x;
            block.initalNodes[i].translation = pos;
            Quaternion rot = nodeTransform.localRotation;
            block.initalNodes[i].rotation = new Vector4(rot.x, rot.y, rot.z, rot.w);
        }

        block.yawOffsets = new float[block.frameCount];

        //check the bindings and see what nodes use translations and rotations
        int[] nodeTransIndex = new int[nodeLength];
        int[] nodeRotIndex = new int[nodeLength];
        for (int i = 0; i < nodeLength; i++)
        {
            nodeTransIndex[i] = -1;
            nodeRotIndex[i] = -1;
        }
        int translationCount = 0;
        int rotationCount = 0;
        foreach (EditorCurveBinding binding in AnimationUtility.GetCurveBindings(animClip))
        {
            Transform trans = (Transform)AnimationUtility.GetAnimatedObject(rootGO, binding);
            int nodeIndex = ArrayUtility.IndexOf(nodeTransforms, trans);
            switch (binding.propertyName)
            {
                case "m_LocalPosition.x":
                case "m_LocalPosition.y":
                case "m_LocalPosition.z":
                {
                    if (nodeTransIndex[nodeIndex] == -1)
                    {
                        nodeTransIndex[nodeIndex] = translationCount;
                        translationCount++;
                    }
                    break;
                }
                case "m_LocalRotation.x":
                case "m_LocalRotation.y":
                case "m_LocalRotation.z":
                case "m_LocalRotation.w":
                {
                    if (nodeRotIndex[nodeIndex] == -1)
                    {
                        nodeRotIndex[nodeIndex] = rotationCount;
                        rotationCount++;
                    }
                    break;
                }
            }
        }
        
        //create translations and rotations
        block.translations = new T1AnimTranslation[translationCount];
        for (int i = 0; i < translationCount; i++)
        {
            block.translations[i] = new T1AnimTranslation();
            block.translations[i].translation = new Vector3[block.frameCount];
        }

        //create rotations
        block.rotations = new T1AnimRotation[rotationCount];
        for (int i = 0; i < rotationCount; i++)
        {
            block.rotations[i] = new T1AnimRotation();
            block.rotations[i].rotation = new Vector4[block.frameCount];
        }

        //Set Node Translation and Rotation Indexs
        block.nodeIndexes = new T1AnimNodeIndex[nodeLength];
        for (int i = 0; i < block.nodeIndexes.Length; i++)
        {
            block.nodeIndexes[i] = new T1AnimNodeIndex();
            block.nodeIndexes[i].translationIndex = (short)nodeTransIndex[i];
            block.nodeIndexes[i].rotationIndex = (short)nodeRotIndex[i];
        }

        //Set Translation and Rotation Tables frame values
        foreach (EditorCurveBinding binding in AnimationUtility.GetCurveBindings(animClip))
        {
            Transform trans = (Transform)AnimationUtility.GetAnimatedObject(rootGO, binding);
            int nodeIndex = ArrayUtility.IndexOf(nodeTransforms, trans);
            AnimationCurve curve = AnimationUtility.GetEditorCurve(animClip, binding);
            for (int frameIndex = 0; frameIndex < block.frameCount; frameIndex++)
            {
                float time = frameIndex / 15.0f;
                float value = curve.Evaluate(time);
                switch (binding.propertyName)
                {
                    case "m_LocalPosition.x":
                    {
                        block.translations[nodeTransIndex[nodeIndex]].translation[frameIndex].x = -value;
                        break;
                    }
                    case "m_LocalPosition.y":
                    {
                        block.translations[nodeTransIndex[nodeIndex]].translation[frameIndex].y = value;
                        break;
                    }
                    case "m_LocalPosition.z":
                    {
                        block.translations[nodeTransIndex[nodeIndex]].translation[frameIndex].z = value;
                        break;
                    }
                    case "m_LocalRotation.x":
                    {
                        block.rotations[nodeRotIndex[nodeIndex]].rotation[frameIndex].x = value;
                        break;
                    }
                    case "m_LocalRotation.y":
                    {
                        block.rotations[nodeRotIndex[nodeIndex]].rotation[frameIndex].z = -value;
                        break;
                    }
                    case "m_LocalRotation.z":
                    {
                        block.rotations[nodeRotIndex[nodeIndex]].rotation[frameIndex].y = value;
                        break;
                    }
                    case "m_LocalRotation.w":
                    {
                        block.rotations[nodeRotIndex[nodeIndex]].rotation[frameIndex].w = value;
                        break;
                    }
                }
            }
        }

        //Set keyframeactions using T1AnimEventMono Component
        T1AnimationAssistant animEvents = rootGO.GetComponent<T1AnimationAssistant>();
        if (animEvents != null)
        {
            for (int i = 0; i < animEvents.animations.Length; i++)
            {
                T1AnimEventMonoClip monoClip = animEvents.animations[i];
                if (animClip == monoClip.animClip)
                {
                    block.animID = monoClip.animID;
                    if (!monoClip.loop)
                        block.loopFrame = (ushort)(block.frameCount - 1);
                    for (int j = 0; j < monoClip.keyFrames.Length; j++)
                    {
                        T1AnimKeyFrameAction monoKeyFrame = monoClip.keyFrames[j];
                        T1AnimKeyFrameAction keyFrame = new T1AnimKeyFrameAction();
                        keyFrame.eventType = monoKeyFrame.eventType;
                        keyFrame.frame = monoKeyFrame.frame;
                        keyFrame.arg0 = monoKeyFrame.arg0;
                        keyFrame.arg1 = monoKeyFrame.arg1;
                        keyFrame.arg2 = monoKeyFrame.arg2;
                        keyFrame.arg3 = monoKeyFrame.arg3;
                        ArrayUtility.Add(ref block.keyFrameActions, keyFrame);
                    }
                    break;
                }
            }
        }
    }

    public void Scale(float scale)
    {
        for (int i = 0; i < blocks.Length; i++)
        {
            for (int j = 0; j < blocks[i].initalNodes.Length; j++)
            {
                blocks[i].initalNodes[j].translation *= scale;
            }
            for (int j = 0; j < blocks[i].translations.Length; j++)
            {
                for (int n = 0; n < blocks[i].translations[j].translation.Length; n++)
                {
                    blocks[i].translations[j].translation[n] *= scale;
                }
            }
        }
    }

    public void SortBlocks()
    {
        Array.Sort(blocks, delegate (T1AnimationBlock x, T1AnimationBlock y) { return x.animID.CompareTo(y.animID); });
    }
}

[Serializable]
public class T1AnimationBlock
{
    public int animID; //each anim ID has a special purpose and is used for special behaviors
    public int frameCount;
    public uint marker;
    public ushort blendLength; //only used if marker == 1
    public ushort loopFrame; //only used if marker == 1
    public T1AnimNodeIndex[] nodeIndexes; //should have same size as model nodes
    public T1AnimInitalNode[] initalNodes; //should have same size as model nodes
    public float[] yawOffsets; //should have same size as frameCount
    public T1AnimTranslation[] translations; //array of translations(vector3) for each frame
    public T1AnimRotation[] rotations; //array of rotations(vector4) for each frame
    public T1AnimKeyFrameAction[] keyFrameActions;

    public T1AnimationBlock()
    {
        animID = -1;
        frameCount = 2;
        marker = 1;
        blendLength = 0;
        loopFrame = 0;
        nodeIndexes = new T1AnimNodeIndex[0];
        initalNodes = new T1AnimInitalNode[0];
        yawOffsets = new float[frameCount];
        translations = new T1AnimTranslation[0];
        rotations = new T1AnimRotation[0];
        keyFrameActions = new T1AnimKeyFrameAction[0];
    }
}

[Serializable]
public class T1AnimNodeIndex
{
    public short translationIndex;
    public short rotationIndex;
    public T1AnimNodeIndex()
    {
        translationIndex = -1;
        rotationIndex = -1;
    }
}

[Serializable]
public class T1AnimInitalNode
{
    public Vector3 translation;
    public Vector4 rotation;
}

[Serializable]
public class T1AnimTranslation
{
    public Vector3[] translation; //should be the same size as frameCount
    public T1AnimTranslation()
    {
        translation = new Vector3[0];
    }
}

[Serializable]
public class T1AnimRotation
{
    public Vector4[] rotation; //should be the same size as frameCount
    public T1AnimRotation()
    {
        rotation = new Vector4[0];
    }
}

[Serializable]
public class T1AnimKeyFrameAction
{
    public uint frame; //which frame this event occurs on
    public uint eventType;
    public float arg0;
    public float arg1;
    public float arg2;
    public float arg3;
}
