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

public class T1ModelMono : MonoBehaviour
{
    [HideInInspector]
    public GameObject modelGO;

    public T1Model sModel;
}

[Serializable]
public class T1Model
{
    public int version; //always 1
    public Vector3 boundingBoxMin; //always 0 for static meshes models, if used as an actor then must be set
    public Vector3 boundingBoxMax; //always 0 for static meshes models, if used as an actor then must be set
    public T1ModelNode[] nodes;

    public T1Model()
    {
        version = 1;
        nodes = new T1ModelNode[0];
    }

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

        version = IOUtils.ReadInt32();
        boundingBoxMin = IOUtils.ReadVector3();
        boundingBoxMax = IOUtils.ReadVector3();
        int nodeLength = IOUtils.ReadInt32();
        nodes = new T1ModelNode[nodeLength];
        for (int i = 0; i < nodeLength; i++)
        {
            nodes[i] = new T1ModelNode();
            T1ModelNode node = nodes[i];
            int childNodeLength = IOUtils.ReadInt32();
            int variantLength = IOUtils.ReadInt32();
            int objectLength = IOUtils.ReadInt32();

            node.childNodes = new short[childNodeLength];
            for (int j = 0; j < childNodeLength; j++)
            {
                node.childNodes[j] = IOUtils.ReadInt16();
            }
            node.variants = new short[variantLength];
            for (int j = 0; j < variantLength; j++)
            {
                node.variants[j] = IOUtils.ReadInt16();
            }
            node.objects = new T1ModelObject[objectLength];
            for (int j = 0; j < objectLength; j++)
            {
                node.objects[j] = new T1ModelObject();
                T1ModelObject obj = node.objects[j];
                int surfaceLength = IOUtils.ReadInt32();
                obj.surfaces = new T1ModelSurface[surfaceLength];
                for (int n = 0; n < surfaceLength; n++)
                {
                    obj.surfaces[n] = new T1ModelSurface();
                    T1ModelSurface surface = obj.surfaces[n];
                    surface.materialFile = IOUtils.ReadString();
                    int triangleLength = IOUtils.ReadInt32();
                    surface.triangles = new T1ModelTriangle[triangleLength];
                    for (int m = 0; m < triangleLength; m++)
                    {
                        surface.triangles[m] = new T1ModelTriangle();
                        T1ModelTriangle triangle = surface.triangles[m];
                        triangle.indiceIndex = new short[3];
                        for (int k = 0; k < 3; k++)
                        {
                            triangle.indiceIndex[k] = IOUtils.ReadInt16();
                        }
                    }

                    int vertexLength = IOUtils.ReadInt32();
                    surface.vertices = new T1ModelVertex[vertexLength];
                    for (int m = 0; m < vertexLength; m++)
                    {
                        surface.vertices[m] = new T1ModelVertex();
                        T1ModelVertex vertex = surface.vertices[m];
                        vertex.xyz = IOUtils.ReadVector3();
                        vertex.uv = IOUtils.ReadVector2();
                        vertex.normals = IOUtils.ReadVector3();
                    }
                }
            }
        }
    }

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

        IOUtils.WriteInt32(version);
        IOUtils.WriteVector3(boundingBoxMin);
        IOUtils.WriteVector3(boundingBoxMax);
        IOUtils.WriteInt32(nodes.Length);
        for (int i = 0; i < nodes.Length; i++)
        {
            T1ModelNode node = nodes[i];
            IOUtils.WriteInt32(node.childNodes.Length);
            IOUtils.WriteInt32(node.variants.Length);
            IOUtils.WriteInt32(node.objects.Length);
            for (int j = 0; j < node.childNodes.Length; j++)
            {
                IOUtils.WriteInt16(node.childNodes[j]);
            }
            for (int j = 0; j < node.variants.Length; j++)
            {
                IOUtils.WriteInt16(node.variants[j]);
            }
            for (int j = 0; j < node.objects.Length; j++)
            {
                IOUtils.WriteInt32(node.objects[j].surfaces.Length);
                for (int n = 0; n < node.objects[j].surfaces.Length; n++)
                {
                    T1ModelSurface surface = node.objects[j].surfaces[n];
                    IOUtils.WriteString(surface.materialFile);
                    IOUtils.WriteInt32(surface.triangles.Length);
                    for (int m = 0; m < surface.triangles.Length; m++)
                    {
                        for (int k = 0; k < 3; k++)
                        {
                            IOUtils.WriteInt16(surface.triangles[m].indiceIndex[k]);
                        }
                    }
                    IOUtils.WriteInt32(surface.vertices.Length);
                    for (int m = 0; m < surface.vertices.Length; m++)
                    {
                        T1ModelVertex vertex = surface.vertices[m];
                        IOUtils.WriteVector3(vertex.xyz);
                        IOUtils.WriteVector2(vertex.uv);
                        IOUtils.WriteVector3(vertex.normals);
                    }
                }
            }
        }

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

    /// <summary>
    /// Asks where to save out kMat file using list of material names
    /// </summary>
    /// <param name="modelName">no extension</param>
    /// <param name="kmatpath">save path of kMat</param>
    /// <param name="texPath">example value: "textures/"</param>
    /// <param name="mats">all materials used by all objects</param>
    public void SaveKMat(string modelName, string kmatpath, string texPath, Material[] mats)
    {
        //make sure list of mats doesn't have duplicates
        List<string> matNames = new List<string>();
        List<Material> validMats = new List<Material>();
        for (int i = 0; i < mats.Length; i++)
        {
            if (mats[i] == null)
                continue;

            if (mats[i].mainTexture != null)
            {
                string matName = mats[i].mainTexture.name;
                if (!matNames.Contains(matName))
                {
                    matNames.Add(matName);
                    validMats.Add(mats[i]);
                }
            }
            else
            {
                Debug.LogError("Material " + mats[i].name + " doesn't have a main texture. Ignoring.", mats[i]);
            }
        }

        string contents = "";
        for (int i = 0; i < validMats.Count; i++)
        {
            string texAssetPath = AssetDatabase.GetAssetPath(validMats[i].mainTexture);
            bool hasAlpha = false;
            TextureImporter ti = (TextureImporter)TextureImporter.GetAtPath(texAssetPath);
            if (ti != null)
            {
                switch (ti.GetAutomaticFormat("Standalone"))
                {
                    case TextureImporterFormat.Alpha8:
                    case TextureImporterFormat.ARGB16:
                    case TextureImporterFormat.ARGB32:
                    case TextureImporterFormat.RGBA16:
                    case TextureImporterFormat.RGBA32:
                    case TextureImporterFormat.RGBAHalf:
                    case TextureImporterFormat.PVRTC_RGBA2:
                    case TextureImporterFormat.PVRTC_RGBA4:
                    case TextureImporterFormat.ETC2_RGBA8:
                    case TextureImporterFormat.DXT5:
                    {
                        hasAlpha = true;
                        break;
                    }
                }
            }
            if (hasAlpha)
            {
                contents += "material " + modelName + "_" + matNames[i] + " {\n\tshader \"progs/world\"\n\tblend\n\talphatest\n\talphafunc gequal\n\talphamask 0.6525\n\tsort masked\n\tdepthtest\n\tcolor_diffuse \"1.000000 1.000000 1.000000 1.000000\"\n\n\tsampler \"diffuse\" 0 \"" + texPath + matNames[i] + ".png" + "\" {\n\t\tfilter      linear\n\t\twrap        repeat\n\t}\n}\n\n";
            }
            else
            {
                contents += "material " + modelName + "_" + matNames[i] + " {\n\tshader \"progs/world\"\n\tsort default\n\tcull back\n\tdepthtest\n\tcolor_diffuse \"1.000000 1.000000 1.000000 1.000000\"\n\n\tsampler \"diffuse\" 0 \"" + texPath + matNames[i] + ".png" + "\" {\n\t\tfilter      linear\n\t\twrap        repeat\n\t}\n}\n\n";
            }
        }
        File.WriteAllText(kmatpath, contents);
    }

    //create nodes and child nodes for the Transform (no variants, only 1 object)
    public void ProcessTransformNode(string modelName, Transform nodeTrans)
    {
        //add a new node
        T1ModelNode node = new T1ModelNode();
        ArrayUtility.Add(ref nodes, node);

        T1ModelObject modelObject = new T1ModelObject();
        ArrayUtility.Add(ref node.objects, modelObject);
        int objectIndex = node.objects.Length - 1;

        MeshFilter mf = nodeTrans.GetComponent<MeshFilter>();
        if (mf != null)
        {
            //Add a new object and set the object with the mesh
            Mesh mesh = mf.sharedMesh;
            MeshRenderer meshRenderer = nodeTrans.GetComponent<MeshRenderer>();
            if (mesh != null && meshRenderer != null)
            {
                Material[] mats = meshRenderer.sharedMaterials;
                modelObject.SetFromMesh(modelName, objectIndex, mesh, mats);
            }
        }

        //Process child nodes
        foreach (Transform child in nodeTrans.transform)
        {
            ArrayUtility.Add<short>(ref node.childNodes, (short)nodes.Length);
            ProcessTransformNode(modelName, child);
        }
    }

    //public void CalculateBounds(Transform transform)
    //{
    //    Bounds bounds = new Bounds(Vector3.zero, Vector3.one);
    //    MeshFilter[] meshFilters = transform.GetComponentsInChildren<MeshFilter>();
    //    foreach (MeshFilter mf in meshFilters)
    //    {
    //        bounds.Encapsulate(mf.sharedMesh.bounds);
    //    }
    //    boundingBoxMin = bounds.min * 2.0f;
    //    boundingBoxMax = bounds.max * 2.0f;
    //}

    public void ClearBounds()
    {
        boundingBoxMin = new Vector3(0.0f, 0.0f, 0.0f);
        boundingBoxMax = new Vector3(0.0f, 0.0f, 0.0f);
    }

    public void CalculateBounds()
    {
        Vector3 min = new Vector3(999999.0f, 999999.0f, 999999.0f);
        Vector3 max = new Vector3(-999999.0f, -999999.0f, -999999.0f);
        for (int i = 0; i < nodes.Length; i++)
        {
            for (int j = 0; j < nodes[i].objects.Length; j++)
            {
                for (int n = 0; n < nodes[i].objects[j].surfaces.Length; n++)
                {
                    T1ModelSurface surface = nodes[i].objects[j].surfaces[n];
                    for (int m = 0; m < surface.vertices.Length; m++)
                    {
                        Vector3 p = surface.vertices[m].xyz;
                        if (p.x < min.x) min.x = p.x;
                        if (p.y < min.y) min.y = p.y;
                        if (p.z < min.z) min.z = p.z;
                        if (p.x > max.x) max.x = p.x;
                        if (p.y > max.y) max.y = p.y;
                        if (p.z > max.z) max.z = p.z;
                    }
                }
            }
        }
        boundingBoxMin = min * 2.0f;
        boundingBoxMax = max * 2.0f;
    }

    public bool Validate()
    {
        bool error = false;

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

        for (int i = nodes.Length - 1; i >= 0; i--)
        {
            if (nodes[i].objects.Length == 0)
            {
                error = true;
                Debug.Log("Node " + i + " must have at least 1 Object.");
                ArrayUtility.RemoveAt(ref nodes, i);
                continue;
            }

            for (int j = 0; j < nodes[i].objects.Length; j++)
            {
                T1ModelObject modelObject = nodes[i].objects[j];
                for (int n = modelObject.surfaces.Length - 1; n >= 0; n--)
                {
                    T1ModelSurface surface = modelObject.surfaces[n];
                    if (surface.triangles.Length == 0)
                    {
                        error = true;
                        Debug.Log("Node " + i + " Object " + j + " Surface " + n + " (" + Path.GetFileName(surface.materialFile) + ") has no triangles");
                        ArrayUtility.RemoveAt(ref modelObject.surfaces, n);
                        continue;
                    }
                    if (surface.vertices.Length == 0)
                    {
                        error = true;
                        Debug.Log("Node " + i + " Object " + j + " Surface " + n + " (" + Path.GetFileName(surface.materialFile) + ") has no vertices");
                        ArrayUtility.RemoveAt(ref modelObject.surfaces, n);
                        continue;
                    }
                }
            }
        }

        return error;
    }

    public void Scale(float scale)
    {
        boundingBoxMax *= scale;
        boundingBoxMin *= scale;
        for (int i = 0; i < nodes.Length; i++)
        {
            for (int j = 0; j < nodes[i].objects.Length; j++)
            {
                for (int n = 0; n < nodes[i].objects[j].surfaces.Length; n++)
                {
                    T1ModelSurface surface = nodes[i].objects[j].surfaces[n];
                    for (int m = 0; m < surface.vertices.Length; m++)
                    {
                        surface.vertices[m].xyz *= scale;
                    }
                }
            }
        }
    }

    public void FlipVertsX()
    {
        for (int i = 0; i < nodes.Length; i++)
        {
            for (int j = 0; j < nodes[i].objects.Length; j++)
            {
                for (int n = 0; n < nodes[i].objects[j].surfaces.Length; n++)
                {
                    T1ModelSurface surface = nodes[i].objects[j].surfaces[n];
                    for (int m = 0; m < surface.vertices.Length; m++)
                    {
                        Vector3 v = surface.vertices[m].xyz;
                        v.x = -v.x;
                        surface.vertices[m].xyz = v;
                    }
                }
            }
        }
    }

    public void FlipWindingOrder()
    {
        for (int i = 0; i < nodes.Length; i++)
        {
            for (int j = 0; j < nodes[i].objects.Length; j++)
            {
                for (int n = 0; n < nodes[i].objects[j].surfaces.Length; n++)
                {
                    T1ModelSurface surface = nodes[i].objects[j].surfaces[n];
                    for (int m = 0; m < surface.triangles.Length; m++)
                    {
                        short tmp = surface.triangles[m].indiceIndex[0];
                        surface.triangles[m].indiceIndex[0] = surface.triangles[m].indiceIndex[1];
                        surface.triangles[m].indiceIndex[1] = tmp;
                    }
                    for (int m = 0; m < surface.vertices.Length; m++)
                    {
                        surface.vertices[m].normals = -surface.vertices[m].normals;
                    }
                }
            }
        }
    }

    //Rotates the vertices in all node objects
    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 < nodes.Length; i++)
        {
            for (int j = 0; j < nodes[i].objects.Length; j++)
            {
                for (int n = 0; n < nodes[i].objects[j].surfaces.Length; n++)
                {
                    T1ModelSurface surface = nodes[i].objects[j].surfaces[n];
                    for (int m = 0; m < surface.vertices.Length; m++)
                    {
                        surface.vertices[m].xyz = rotation * (surface.vertices[m].xyz - center) + center;
                    }
                }
            }
        }
    }
}


[Serializable]
public class T1ModelNode
{
    public short[] childNodes;
    public short[] variants;
    public T1ModelObject[] objects;
    public T1ModelNode()
    {
        childNodes = new short[0];
        variants = new short[1];
        objects = new T1ModelObject[0];
    }
}

[Serializable]
public class T1ModelObject
{
    public T1ModelSurface[] surfaces;

    public T1ModelObject()
    {
        surfaces = new T1ModelSurface[0];
    }

    public void SetFromMesh(string modelName, int objectIndex, Mesh mesh, Material[] mats)
    {
        Vector3[] vertices = mesh.vertices;
        Vector3[] normals = mesh.normals;
        Vector2[] uv = mesh.uv;

        Array.Resize(ref surfaces, mats.Length);
        for (int n = 0; n < mats.Length; n++)
        {
            surfaces[n] = new T1ModelSurface();
            //string matPath = "materials/" + modelName + "/" + modelName + "_" + objectIndex + "_" + n;
            string matPath = "_default";
            if (mats[n].mainTexture != null)
            {
                matPath = "materials/" + modelName + "/" + modelName + "_" + mats[n].mainTexture.name;
            }
            else
            {
                Debug.LogError("Material " + n + ": " + mats[n].name + " doesn't have a main texture. Assigning _default texture.");
            }
            surfaces[n].materialFile = matPath;

            int[] triangles = mesh.GetTriangles(n);

            Dictionary<int, int> dictTriIndexs = new Dictionary<int, int>();
            List<T1ModelVertex> modelVerts = new List<T1ModelVertex>();
            int triCount = triangles.Length / 3;
            Array.Resize(ref surfaces[n].triangles, triCount);
            for (int m = 0; m < triCount; m++)
            {
                int triIndex = m * 3;
                surfaces[n].triangles[m] = new T1ModelTriangle();
                for (int k = 0; k < 3; k++)
                {
                    int oldTriIndex = triangles[triIndex + k];
                    int newTriIndex;
                    if (!dictTriIndexs.TryGetValue(oldTriIndex, out newTriIndex))
                    {
                        newTriIndex = modelVerts.Count;
                        T1ModelVertex modelVert = new T1ModelVertex();
                        modelVert.xyz = vertices[oldTriIndex];
                        modelVert.uv = uv[oldTriIndex];
                        modelVert.normals = normals[oldTriIndex];
                        modelVerts.Add(modelVert);

                        dictTriIndexs.Add(oldTriIndex, newTriIndex);
                    }
                    surfaces[n].triangles[m].indiceIndex[k] = (short)newTriIndex;
                }
            }

            surfaces[n].vertices = modelVerts.ToArray();
        }
    }
}

[Serializable]
public class T1ModelSurface
{
    public string materialFile; //(zero terminated)
    public T1ModelTriangle[] triangles;
    public T1ModelVertex[] vertices;
    public T1ModelSurface()
    {
        materialFile = "";
        triangles = new T1ModelTriangle[0];
        vertices = new T1ModelVertex[0];
    }
}

[Serializable]
public class T1ModelTriangle
{
    public short[] indiceIndex;
    public T1ModelTriangle()
    {
        indiceIndex = new short[3];
    }
}

[Serializable]
public class T1ModelVertex
{
    public Vector3 xyz;
    public Vector2 uv; //when read in TurokEX, the y coordinate is always read as 1.0f - UV.y
    public Vector3 normals;
}
