using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Mafia2Tool.DataFormats.FrameResources
{
    public class GeometryDataEntry
    {
        [Flags]
        public enum VertexFlags : uint
        {
            Position = 0x00000001,          // 16:16:15 expanded and offset
            Normals = 0x00000004,           // byte[4] mapped to -1 ... 1
            Tangent = 0x00000010,           // ??? maybe w component of position...
            BlendData = 0x00000040,         // byte[4] mapped to 0 ... 1 and byte[4] blendindices
            flag_0x80 = 0x00000080,         // color ?
            
            TexCoords0 = 0x00000100,        // Vector2 halffloat
            TexCoords1 = 0x00000200,        // Vector2 halffloat
            TexCoords2 = 0x00000400,        // Vector2 halffloat
            // 0x800 3
            // 0x1000 4
            // 0x2000 5
            // 0x4000 6
            TexCoords7 = 0x00008000,        // Vector2 halffloat
            
            flag_0x20000 = 0x00020000,      // ???
            flag_0x40000 = 0x00040000,      // Vector3
            flag_0x100000 = 0x00100000,
        }

        class unk_struct1
        {
            class unk_struct1_1
            {
                class unk_struct1_1_1
                {
                    int unknown_00_int;

                    PackedVector3 expandVector1;
                    PackedVector3 expandVector2;

                    int unknown_07_short;

                    public unk_struct1_1_1(BinaryReader r, Vector3 expV1, Vector3 expV2)
                    {
                        Read(r, expV1, expV2);
                    }

                    public void Read(BinaryReader r, Vector3 expV1, Vector3 expV2)
                    {
                        unknown_00_int = r.ReadInt32();

                        expandVector1 = new PackedVector3(r);
                        expandVector1.Expand(expV1);

                        expandVector2 = new PackedVector3(r);
                        expandVector2.Expand(expV2);

                        unknown_07_short = r.ReadInt32();
                    }

                    public override string ToString()
                    {
                        return string.Format("{0,10} [{1}] [{2}] {3,10}", unknown_00_int, expandVector1, expandVector2, unknown_07_short);
                    }
                }

                Vector3 unknown_00_vector;
                Vector3 unknown_01_vector;
                int unknown_01_int;
                unk_struct1_1_1[] unknown_02_list;

                bool unknown_03_bool;
                int unknown_04_int;
                int[] unknown_05_ints;

                bool unknown_06_bool;
                int[] unknown_07_ints;

                public unk_struct1_1(BinaryReader r)
                {
                    Read(r);
                }

                public void Read(BinaryReader r)
                {
                    unknown_00_vector = new Vector3(r);
                    unknown_01_vector = new Vector3(r);

                    int count = r.ReadInt32();

                    unknown_01_int = r.ReadInt32(); // 1, 3, 5, 7, 9, 11, 13, 19, 20

                    unknown_02_list = new unk_struct1_1_1[count];
                    for (int i = 0; i < count; i++)
                    {
                        unknown_02_list[i] = new unk_struct1_1_1(r, unknown_00_vector, unknown_01_vector);
                    }

                    unknown_03_bool = r.ReadBoolean();

                    unknown_04_int = r.ReadInt32();

                    unknown_05_ints = new int[count];
                    for (int i = 0; i < count; i++)
                    {
                        unknown_05_ints[i] = r.ReadInt32();
                    }

                    count = r.ReadInt32();

                    unknown_06_bool = r.ReadBoolean();

                    unknown_07_ints = new int[count];
                    for (int i = 0; i < count; i++)
                    {
                        unknown_07_ints[i] = r.ReadInt32();
                    }
                }
            }

            int unknown_00_size;
            int unknown_01_size;

            int unknown_02_int;

            unk_struct1_1 unknown_03_list;

            int unknown_04_int;
            int unknown_05_int;
            int NumVerts;
            int NumFaces;
            int unknown_06_int;
            int unknown_07_int;

            public unk_struct1(BinaryReader r)
            {
                Read(r);
            }

            public void Read(BinaryReader r)
            {
                unknown_00_size = r.ReadInt32();
                unknown_01_size = r.ReadInt32(); // == unknown_00_size
                
                unknown_02_int = r.ReadInt32(); // 3 or 4
                // 3 => unknown_00_size = 68
                // 4 => unknown_00_size = struct len - 1

                ////////////////// 3
                if (r.ReadBoolean()) 
                {
                    unknown_03_list = new unk_struct1_1(r);
                }
                ////////////////// 4
                else
                {
                    byte[] murks = r.ReadBytes(10); // always 01 00 00 00 00 00 00 00 00 00, could be a hash => $BaseRef bone
                }

                unknown_04_int = r.ReadInt32(); // 12
                unknown_05_int = r.ReadInt32(); // 2
                NumVerts = r.ReadInt32();
                NumFaces = r.ReadInt32();
                unknown_06_int = r.ReadInt32(); // 0
                unknown_07_int = r.ReadInt32(); // 12
            }
        }

        class unk_struct2
        {
            class unk_struct2_1
            {
                public class unk_struct2_1_1
                {
                    public unk_struct2_1 parent;
                    public bool topLevel;

                    short u1;
                    short u2;
                    short u3;
                    short u4;
                    short u5;
                    short u6;
                    short startFace;
                    short endFace;
                    public short leftIndex; // index to other unk_struct2_1_1
                    public short rightIndex; // index to other unk_struct2_1_1

                    public unk_struct2_1_1 LeftNode
                    {
                        get
                        {
                            if (parent != null && leftIndex >= 0 && leftIndex < parent.data.Length) return parent.data[leftIndex];
                            return null;
                        }
                    }

                    public unk_struct2_1_1 RightNode
                    {
                        get
                        {
                            if (parent != null && rightIndex >= 0 && rightIndex < parent.data.Length) return parent.data[rightIndex];
                            return null;
                        }
                    }

                    public unk_struct2_1_1(BinaryReader r)
                    {
                        Read(r);
                    }

                    public void Read(BinaryReader r)
                    {
                        topLevel = true;
                        u1 = r.ReadInt16();
                        u2 = r.ReadInt16();
                        u3 = r.ReadInt16();
                        u4 = r.ReadInt16();
                        u5 = r.ReadInt16();
                        u6 = r.ReadInt16();
                        startFace = r.ReadInt16();
                        endFace = r.ReadInt16();
                        leftIndex = r.ReadInt16();
                        rightIndex = r.ReadInt16();
                    }

                    public override string ToString()
                    {
                        return string.Format("{0,6} {1,6} {2,6} {3,6} {4,6} {5,6} [{6} - {7}] l:{8} r:{9}", u1, u2, u3, u4, u5, u6, startFace, endFace, leftIndex, rightIndex);
                    }
                }

                int startIndex; // triangle index

                unk_struct2_1_1[] data;

                public List<unk_struct2_1_1> topLevel
                {
                    get
                    {
                        return new List<unk_struct2_1_1>(data.Where(c => c.topLevel));
                    }
                }

                public unk_struct2_1(BinaryReader r, unk_struct2_1_1[] _data)
                {
                    Read(r, _data);
                }

                public void Read(BinaryReader r, unk_struct2_1_1[] _data)
                {
                    int start = r.ReadInt32();
                    int count = r.ReadInt32();
                    startIndex = r.ReadInt32();
                    this.data = new unk_struct2_1_1[count];
                    Array.Copy(_data, start, data, 0, count);
                    for (int i = 0; i < count; i++)
                    {
                        this.data[i].parent = this;
                        if (this.data[i].leftIndex != -1)
                            this.data[this.data[i].leftIndex].topLevel = false;
                        
                        if (this.data[i].rightIndex != -1)
                            this.data[this.data[i].rightIndex].topLevel = false;
                    }
                }

                public override string ToString()
                {
                    return string.Format("{0,6} {1,6} {2,6} {3}", startIndex, data.Length);
                }
            }

            bool unknown_01_bool; // PC = true, XBOX360 = false
            int unknown_02_int;
            int unknown_03_int;
            int unknown_04_int;

            long unknown_05_long;

            unk_struct2_1[] materialLodRelated;
            
            public unk_struct2(BinaryReader r)
            {
                Read(r);
            }

            public void Read(BinaryReader r)
            {
                unknown_01_bool = r.ReadBoolean();
                unknown_02_int = r.ReadInt32(); // 1
                unknown_03_int = r.ReadInt32(); // 20
                unknown_04_int = r.ReadInt32(); // 12

                int count1 = r.ReadInt32();
                int count2 = r.ReadInt32();

                unknown_05_long = r.ReadInt64();

                unk_struct2_1.unk_struct2_1_1[] _data = new unk_struct2_1.unk_struct2_1_1[count1];
                for (int i = 0; i < count1; i++)
                {
                    _data[i] = new unk_struct2_1.unk_struct2_1_1(r);
                }

                materialLodRelated = new unk_struct2_1[count2];
                for (int i = 0; i < count2; i++)
                {
                    materialLodRelated[i] = new unk_struct2_1(r, _data);
                }
            }
        }
        
        public float LodDistance;
        public Hash IndexBufferRef;
        public VertexFlags VertexDeclaration;
        public Hash VertexBufferRef;
        public int NumVerts;

        int unkown_01_int;
        unk_struct1 us1;
        unk_struct2 us2;
        int unkown_02_int;

        public GeometryDataEntry(BinaryReader r)
        {
            Read(r);
        }

        public void Read(BinaryReader r)
        {
            LodDistance = r.ReadSingle();

            IndexBufferRef = new Hash(r);
            VertexDeclaration = (VertexFlags)r.ReadUInt32();
            VertexBufferRef = new Hash(r);

            NumVerts = r.ReadInt32();

            unkown_01_int = r.ReadInt32(); // 0

            if (r.ReadInt32() != 0)
            {
                us1 = new unk_struct1(r);
            }
            
            if (r.ReadInt32() != 0)
            {
                us2 = new unk_struct2(r);
            }

            unkown_02_int = r.ReadInt32();
        }

        #region export stuff...

        public static VertexFlags[] VertexFlagOrder = new VertexFlags[] {
                VertexFlags.Position,
                VertexFlags.Normals,
                VertexFlags.BlendData,
				VertexFlags.flag_0x80,
                VertexFlags.flag_0x20000,
                VertexFlags.flag_0x100000,
                VertexFlags.TexCoords0,
                VertexFlags.TexCoords1,
                VertexFlags.TexCoords2,
                VertexFlags.TexCoords7,
                VertexFlags.flag_0x40000,

                VertexFlags.Tangent, // ???
            };

        public struct VertexOffset
        {
            public int offset;
            public int length;
        }

        public Dictionary<GeometryDataEntry.VertexFlags, VertexOffset> GetVertexOffsets(out int stride)
        {
            Dictionary<GeometryDataEntry.VertexFlags, VertexOffset> vertexComponentLengths = new Dictionary<GeometryDataEntry.VertexFlags, VertexOffset>();
            int _stride = 0;
            foreach (var f in GeometryDataEntry.VertexFlagOrder)
            {
                if (VertexDeclaration.HasFlag(f))
                {
                    int len = getVertexComponentLength(f);
                    if (len > 0)
                    {
                        VertexOffset off = new VertexOffset() { offset = _stride, length = len };
                        vertexComponentLengths.Add(f, off);
                        _stride += len;
                    }
                }
            }

            stride = _stride;
            return vertexComponentLengths;
        }

        private static int getVertexComponentLength(GeometryDataEntry.VertexFlags flag)
        {
            switch (flag)
            {
                case GeometryDataEntry.VertexFlags.Position:
                case GeometryDataEntry.VertexFlags.BlendData: return 8; // byte[4] blendweights, byte[4] blendindices
                case GeometryDataEntry.VertexFlags.Normals:
                case GeometryDataEntry.VertexFlags.flag_0x80:
                case GeometryDataEntry.VertexFlags.TexCoords0:
                case GeometryDataEntry.VertexFlags.TexCoords1:
                case GeometryDataEntry.VertexFlags.TexCoords2:
                case GeometryDataEntry.VertexFlags.TexCoords7:
                case GeometryDataEntry.VertexFlags.flag_0x20000:
                case GeometryDataEntry.VertexFlags.flag_0x100000: return 4;
                case GeometryDataEntry.VertexFlags.flag_0x40000: return 12;
                case GeometryDataEntry.VertexFlags.Tangent: return 0; // ???
            }

            return -1;
        }

        #endregion
    }
}
