using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mafia2Tool.DataFormats;
using Mafia2Tool.DataFormats.FrameResources;

namespace Mafia2Tool.Parsed
{
    public class Model
    {
        public struct Vertex
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector3 Tangent;
            public Vector3 Binormal;

            //public Vector3 ComputedNormal;
            //public Vector3 ComputedTangent;
            //public Vector3 ComputedBinormal;

            public float[] BlendWeights;
            public byte[] BlendIndices;
            public int[] JointIndices;

            public Vector2[] UVs;
        }

        public struct ModelPart
        {
            public ulong MaterialHash;
            public int[] Indices;
        }

        public class Lod
        {
            public Vertex[] Vertices;

            public int NumUVChannels;
            public bool NormalMapInfoPresent;

            public Skeleton Skeleton;

            public ModelPart[] Parts;
        }

        public Lod[] Lods;

        public string Path;

        public Model(SingleMeshComponent sourceModel, IEnumerable<VertexBufferPool> vpools, IEnumerable<IndexBufferPool> ipools)
        {
            Build(sourceModel, vpools, ipools);
        }

        public void Build(SingleMeshComponent sourceModel, IEnumerable<VertexBufferPool> vpools, IEnumerable<IndexBufferPool> ipools)
        {
            Path = sourceModel.Path;

            MeshDataEntry mesh = sourceModel.Mesh;
            if (mesh == null) return;

            Lods = new Lod[mesh.lods.Length];

            for (int LodIndex = 0; LodIndex < mesh.lods.Length; LodIndex++)
            {
                GeometryDataEntry lod = mesh.lods[LodIndex];
                Lod l = new Lod();

                bool blendingPresent = lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.BlendData);
                if (blendingPresent)
                {
                    if (sourceModel is ModelComponent)
                    {
                        l.Skeleton = new Skeleton((ModelComponent)sourceModel, LodIndex);
                    }
                }

                VertexBufferPool.VertexBuffer vbuf = getVertexBuffer(vpools, lod.VertexBufferRef.hash);
                #region vertices

                int stride;
                Dictionary<GeometryDataEntry.VertexFlags, GeometryDataEntry.VertexOffset> vertexComponentLengths = lod.GetVertexOffsets(out stride);

                int numUVChannels = 0;
                if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.TexCoords0)) numUVChannels++;
                if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.TexCoords1)) numUVChannels++;
                if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.TexCoords2)) numUVChannels++;
                if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.TexCoords7)) numUVChannels++;
                l.NumUVChannels = numUVChannels;

                l.Vertices = new Vertex[lod.NumVerts];
                for (int i = 0; i < lod.NumVerts; i++)
                {
                    int uvChannelIndex = 0;
                    
                    Vertex vert = new Vertex();
                    vert.UVs = new Vector2[numUVChannels];

                    int offset;
                    float tangentHandedness = 1.0f;

                    if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.Position))
                    {
                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.Position].offset;

                        ushort _packedX = BitConverter.ToUInt16(vbuf.data, offset);
                        ushort _packedY = BitConverter.ToUInt16(vbuf.data, offset + 2);
                        ushort _packedZ = BitConverter.ToUInt16(vbuf.data, offset + 4);
                        _packedZ = (ushort)(_packedZ & 0x7fff);

                        Vector3 pos = new Vector3(
                            _packedX * mesh._positionFactor,
                            _packedY * mesh._positionFactor,
                            _packedZ * mesh._positionFactor);
                        pos += mesh._positionOffset;

                        vert.Position = pos;
                    }

                    if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.Tangent))
                    {
                        l.NormalMapInfoPresent = true;
                        
                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.Position].offset;

                        tangentHandedness = ((BitConverter.ToUInt16(vbuf.data, offset + 4) & 0x8000) == 0x8000) ? 1.0f : -1.0f;

                        byte tangentTmp0 = vbuf.data[offset + 6];
                        byte tangentTmp1 = vbuf.data[offset + 7];

                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.Normals].offset;

                        byte tangentTmp2 = vbuf.data[offset + 3];

                        //float _x = tangentTmp0 / 127f - 1f;
                        //float _y = tangentTmp1 / 127f - 1f;
                        //float _z = tangentTmp2 / 127f - 1f;

                        //float _x = tangentTmp0 * 0.007843138f - 1f;
                        //float _y = tangentTmp1 * 0.007843138f - 1f;
                        //float _z = tangentTmp2 * 0.007843138f - 1f;

                        float _x = tangentTmp0 - 127f;
                        float _y = tangentTmp1 - 127f;
                        float _z = tangentTmp2 - 127f;

                        vert.Tangent = new Vector3(_x, _y, _z);
                        vert.Tangent.Normalize();
                    }

                    if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.BlendData))
                    {
                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.BlendData].offset;

                        int wmax = 0;
                        int numWeights;
                        for (numWeights = 0; numWeights < 4; numWeights++)
                        {
                            if (wmax == 255) break;
                            wmax += vbuf.data[offset + numWeights];
                        }

                        vert.BlendIndices = new byte[numWeights];
                        vert.BlendWeights = new float[numWeights];
                        for (int j = 0; j < numWeights; j++)
                        {
                            vert.BlendWeights[j] = vbuf.data[offset + j] / 255f;
                            vert.BlendIndices[j] = vbuf.data[offset + 4 + j];
                        }
                    }

                    if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.Normals))
                    {
                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.Normals].offset;

                        //float _x = vbuf.data[offset] / 127f - 1f;
                        //float _y = vbuf.data[offset + 1] / 127f - 1f;
                        //float _z = vbuf.data[offset + 2] / 127f - 1f;

                        //float _x = vbuf.data[offset] * 0.007843138f - 1f;
                        //float _y = vbuf.data[offset + 1] * 0.007843138f - 1f;
                        //float _z = vbuf.data[offset + 2] * 0.007843138f - 1f;

                        float _x = vbuf.data[offset] - 127f;
                        float _y = vbuf.data[offset + 1] - 127f;
                        float _z = vbuf.data[offset + 2] - 127f;

                        vert.Normal = new Vector3(_x, _y, _z);
                        vert.Normal.Normalize();
                    }

                    if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.TexCoords0))
                    {
                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.TexCoords0].offset;

                        vert.UVs[uvChannelIndex++] = new Vector2(
                            OpenTK.Half.FromBytes(vbuf.data, offset).ToSingle(),
                            OpenTK.Half.FromBytes(vbuf.data, offset + 2).ToSingle()
                        );
                    }

                    if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.TexCoords1))
                    {
                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.TexCoords1].offset;

                        vert.UVs[uvChannelIndex++] = new Vector2(
                            OpenTK.Half.FromBytes(vbuf.data, offset).ToSingle(),
                            OpenTK.Half.FromBytes(vbuf.data, offset + 2).ToSingle()
                        );
                    }

                    if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.TexCoords2))
                    {
                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.TexCoords2].offset;

                        vert.UVs[uvChannelIndex++] = new Vector2(
                            OpenTK.Half.FromBytes(vbuf.data, offset).ToSingle(),
                            OpenTK.Half.FromBytes(vbuf.data, offset + 2).ToSingle()
                        );
                    }

                    if (lod.VertexDeclaration.HasFlag(GeometryDataEntry.VertexFlags.TexCoords7))
                    {
                        offset = i * stride + vertexComponentLengths[GeometryDataEntry.VertexFlags.TexCoords7].offset;

                        vert.UVs[uvChannelIndex++] = new Vector2(
                            OpenTK.Half.FromBytes(vbuf.data, offset).ToSingle(),
                            OpenTK.Half.FromBytes(vbuf.data, offset + 2).ToSingle()
                        );
                    }

                    if (l.NormalMapInfoPresent)
                    {
                        vert.Binormal = Vector3.CrossProduct(vert.Normal, vert.Tangent) * tangentHandedness;
                        vert.Binormal.Normalize();
                    }
                    
                    l.Vertices[i] = vert;
                }

                #endregion

                IndexBufferPool.IndexBuffer ibuf = getIndexBuffer(ipools, lod.IndexBufferRef.hash);

                SkeletonBlendInfo blendInfo = null;
                if (sourceModel is ModelComponent)
                {
                    blendInfo = ((ModelComponent)sourceModel).BlendInfo;
                }

                MaterialInfoEntry.Map[] parts = sourceModel.Material.Maps[LodIndex];

                l.Parts = new ModelPart[parts.Length];

                for (int partIndex = 0; partIndex < parts.Length; partIndex++)
                {
                    ModelPart p = new ModelPart();
                    p.MaterialHash = parts[partIndex].MaterialHash;

                    byte[] blendmap = null;
                    if (blendInfo != null)
                    {
                        byte blendmapIndex = blendInfo.BlendDataToBoneIndexMaps[LodIndex].BlendIndexRanges[partIndex * 2];
                        blendmap = blendInfo.BlendDataToBoneIndexMaps[LodIndex].BlendIndices[blendmapIndex];
                    }
                    int endIndex = parts[partIndex].startIndex + parts[partIndex].numFaces * 3;

                    List<int> tmp = new List<int>(parts[partIndex].numFaces * 3);
                    for (int i = parts[partIndex].startIndex; i < endIndex; i += 3)
                    {
                        for (int k = 0; k < 3; k++)
                        {
                            int a = ibuf.data[i + k];
                            tmp.Add(a);

                            if (blendmap != null && blendingPresent && l.Vertices[a].JointIndices == null)
                            {
                                l.Vertices[a].JointIndices = new int[l.Vertices[a].BlendIndices.Length];
                                for (int j = 0; j < l.Vertices[a].JointIndices.Length; j++)
                                {
                                    l.Vertices[a].JointIndices[j] = blendmap[l.Vertices[a].BlendIndices[j]];
                                }
                            }
                        }
                    }
                    p.Indices = tmp.ToArray();
                    l.Parts[partIndex] = p;
                }

                /*
                #region computed normal
                for (int i = 0; i < l.Vertices.Length; i++)
                {
                    l.Vertices[i].ComputedNormal = new Vector3();
                }

                for (int i = 0; i < ibuf.data.Length; i += 3)
                {
                    int i1 = ibuf.data[i];
                    int i2 = ibuf.data[i + 1];
                    int i3 = ibuf.data[i + 2];

                    Vector3 v1 = l.Vertices[i1].Position;
                    Vector3 v2 = l.Vertices[i2].Position;
                    Vector3 v3 = l.Vertices[i3].Position;

                    Vector3 v4 = v2 - v1;
                    Vector3 v5 = v3 - v1;

                    Vector3 faceNormal = Vector3.CrossProduct(v4, v5);
                    faceNormal.Normalize();

                    l.Vertices[i1].ComputedNormal += faceNormal;
                    l.Vertices[i2].ComputedNormal += faceNormal;
                    l.Vertices[i3].ComputedNormal += faceNormal;
                }

                for (int i = 0; i < l.Vertices.Length; i++)
                {
                    l.Vertices[i].ComputedNormal.Normalize();
                }
                #endregion

                #region computed tangent & binormal
                // tangent
                Vector3[] tan1 = new Vector3[l.Vertices.Length];
                Vector3[] tan2 = new Vector3[l.Vertices.Length];
                
                for (int i = 0; i < ibuf.data.Length; i += 3)
                {
                    int i1 = ibuf.data[i];
                    int i2 = ibuf.data[i + 1];
                    int i3 = ibuf.data[i + 2];

                    Vector3 v1 = l.Vertices[i1].Position;
                    Vector3 v2 = l.Vertices[i2].Position;
                    Vector3 v3 = l.Vertices[i3].Position;

                    Vector2 w1 = l.Vertices[i1].UVs[0];
                    Vector2 w2 = l.Vertices[i2].UVs[0];
                    Vector2 w3 = l.Vertices[i3].UVs[0];

                    float x1 = v2.X - v1.X;
                    float x2 = v3.X - v1.X;
                    float y1 = v2.Y - v1.Y;
                    float y2 = v3.Y - v1.Y;
                    float z1 = v2.Z - v1.Z;
                    float z2 = v3.Z - v1.Z;

                    float s1 = w2.X - w1.X;
                    float s2 = w3.X - w1.X;
                    float t1 = w2.Y - w1.Y;
                    float t2 = w3.Y - w1.Y;

                    float r = 1.0f / (s1 * t2 - s2 * t1);

                    Vector3 sdir = new Vector3(
                        (t2 * x1 - t1 * x2) * r,
                        (t2 * y1 - t1 * y2) * r,
                        (t2 * z1 - t1 * z2) * r
                    );

                    Vector3 tdir = new Vector3(
                        (s1 * x2 - s2 * x1) * r,
                        (s1 * y2 - s2 * y1) * r,
                        (s1 * z2 - s2 * z1) * r
                    );

                    tan1[i1] += sdir;
                    tan1[i2] += sdir;
                    tan1[i3] += sdir;

                    tan2[i1] += tdir;
                    tan2[i2] += tdir;
                    tan2[i3] += tdir;
                }

                for (int a = 0; a < l.Vertices.Length; a++)
                {
                    Vector3 n = l.Vertices[a].ComputedNormal;
                    Vector3 t = tan1[a];
        
                    // Gram-Schmidt orthogonalize
                    l.Vertices[a].ComputedTangent = t - n * Vector3.DotProduct(n, t);
                    l.Vertices[a].ComputedTangent.Normalize();
        
                    // Calculate handedness
                    float w = (Vector3.DotProduct(Vector3.CrossProduct(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;

                    l.Vertices[a].ComputedBinormal = (Vector3.CrossProduct(l.Vertices[a].ComputedNormal, l.Vertices[a].ComputedTangent)) * w;
                    l.Vertices[a].ComputedBinormal.Normalize();
                }

                #endregion
                */
                Lods[LodIndex] = l;
            }
        }

        private VertexBufferPool.VertexBuffer getVertexBuffer(IEnumerable<VertexBufferPool> _vertexBufferPools, ulong hash)
        {
            VertexBufferPool.VertexBuffer buf = null;

            foreach (VertexBufferPool pool in _vertexBufferPools)
            {
                buf = pool.buffers.SingleOrDefault(c => c.hash == hash);
                if (buf != null) break;
            }

            return buf;
        }

        private IndexBufferPool.IndexBuffer getIndexBuffer(IEnumerable<IndexBufferPool> _indexBufferPools, ulong hash)
        {
            IndexBufferPool.IndexBuffer buf = null;

            foreach (IndexBufferPool pool in _indexBufferPools)
            {
                buf = pool.buffers.SingleOrDefault(c => c.hash == hash);
                if (buf != null) break;
            }

            return buf;
        }
    }
}
