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

namespace Ext.FileFormats
{
    public class SBShaderCache
    {
        public class Technique
        {
            private SBShaderCache _cache;

            public int Index;

            public byte unknown_00_byte1; // 0,1,2,3,4,5,6
            public byte unknown_00_byte2; // 0,1
            public byte unknown_00_byte3; // 0,1
            public byte unknown_00_byte4; // 0,1,2,3

            public byte unknown_01_byte1; // 0,1
            public byte unknown_01_byte2; // 0,1
            public byte unknown_01_byte3; // 0,1
            public byte unknown_01_byte4; // 0

            // union
            public ulong HashComplete; // related to material.ShaderHash
            public uint HashPart1;
            public uint HashPart2;

            public bool IsMaterialEffect
            {
                get { return HashPart1 > 1; }
            }

            public byte unknown_04_byte1; // 0,1
            public byte unknown_04_byte2; // 0,1
            public byte unknown_04_byte3; // 0,1
            public byte unknown_04_byte4; // 0

            public byte unknown_05_byte1; // 0
            public byte unknown_05_byte2; // 0,1
            public byte unknown_05_byte3; // 0
            public byte unknown_05_byte4; // 0
            
            public byte unknown_06_byte1; // 0
            public byte unknown_06_byte2; // 0,1
            public byte unknown_06_byte3; // 0
            public byte unknown_06_byte4; // 0

            public int PassIndex; // Pass Index ?
            public ShaderPass Pass
            {
                get
                {
                    if (_cache.Passes != null && _cache.Passes.Length > PassIndex) return _cache.Passes[PassIndex];
                    return null;
                }
            }

            public Technique(BinaryReader r, SBShaderCache cache, int index)
            {
                _cache = cache;
                Index = index;
                Read(r);
            }

            public void Read(BinaryReader r)
            {
                byte[] tmp = r.ReadBytes(8);
                unknown_00_byte1 = tmp[0];
                unknown_00_byte2 = tmp[1];
                unknown_00_byte3 = tmp[2];
                unknown_00_byte4 = tmp[3];

                unknown_01_byte1 = tmp[4];
                unknown_01_byte2 = tmp[5];
                unknown_01_byte3 = tmp[6];
                unknown_01_byte4 = tmp[7];

                // union
                HashPart1 = r.ReadUInt32();
                HashPart2 = r.ReadUInt32();
                HashComplete = ((ulong)HashPart2 << 32) | HashPart1;

                tmp = r.ReadBytes(12);
                unknown_04_byte1 = tmp[0];
                unknown_04_byte2 = tmp[1];
                unknown_04_byte3 = tmp[2];
                unknown_04_byte4 = tmp[3];

                unknown_05_byte1 = tmp[4];
                unknown_05_byte2 = tmp[5];
                unknown_05_byte3 = tmp[6];
                unknown_05_byte4 = tmp[7];

                unknown_06_byte1 = tmp[8];
                unknown_06_byte2 = tmp[9];
                unknown_06_byte3 = tmp[10];
                unknown_06_byte4 = tmp[11];
                
                PassIndex = r.ReadInt32();
            }

            public override string ToString()
            {
                return string.Format("{0} {1} {2} {3} {4} {5} {6} {7} [0x{8:x16}] {9} {10} {11} {12} {13} {14} {15} {16} {17} {18} {19} {20} [{21,5}]",
                    unknown_00_byte1, unknown_00_byte2, unknown_00_byte3, unknown_00_byte4,
                    unknown_01_byte1, unknown_01_byte2, unknown_01_byte3, unknown_01_byte4,
                    HashComplete,
                    unknown_04_byte1, unknown_04_byte2, unknown_04_byte3, unknown_04_byte4,
                    unknown_05_byte1, unknown_05_byte2, unknown_05_byte3, unknown_05_byte4,
                    unknown_06_byte1, unknown_06_byte2, unknown_06_byte3, unknown_06_byte4,
                    PassIndex);
            }
        }

        public class ShaderPass
        {
            private SBShaderCache _cache;

            public int Index;

            public short VertexShaderIndex1;
            public ShaderCode2 VertexShaderCode1
            {
                get 
                {
                    if (_cache.codes != null && 
                        VertexShaderIndex1 >= 0 && 
                        VertexShaderIndex1 < _cache.codes.Length) return _cache.codes[VertexShaderIndex1];
                    return null; 
                }
            }
            
            public short VertexShaderIndex2;
            public ShaderCode2 VertexShaderCode2
            {
                get
                {
                    if (_cache.codes != null &&
                        VertexShaderIndex2 >= 0 &&
                        VertexShaderIndex2 < _cache.codes.Length) return _cache.codes[VertexShaderIndex2];
                    return null;
                }
            }            
            
            public short VertexShaderIndex3;
            public ShaderCode2 VertexShaderCode3
            {
                get
                {
                    if (_cache.codes != null &&
                        VertexShaderIndex3 >= 0 &&
                        VertexShaderIndex3 < _cache.codes.Length) return _cache.codes[VertexShaderIndex3];
                    return null;
                }
            }            
            
            public short PixelShaderIndex1;
            public ShaderCode2 PixelShaderCode1
            {
                get
                {
                    if (_cache.codes != null && 
                        PixelShaderIndex1 >= 0 && 
                        PixelShaderIndex1 < _cache.codes.Length) return _cache.codes[PixelShaderIndex1];
                    return null;
                }
            }
            
            public short PixelShaderIndex2;
            public ShaderCode2 PixelShaderCode2
            {
                get
                {
                    if (_cache.codes != null &&
                        PixelShaderIndex2 >= 0 &&
                        PixelShaderIndex2 < _cache.codes.Length) return _cache.codes[PixelShaderIndex2];
                    return null;
                }
            }            

            public short PixelShaderIndex3;
            public ShaderCode2 PixelShaderCode3
            {
                get
                {
                    if (_cache.codes != null &&
                        PixelShaderIndex3 >= 0 &&
                        PixelShaderIndex3 < _cache.codes.Length) return _cache.codes[PixelShaderIndex3];
                    return null;
                }
            }

            public byte unknown_00_byte;
            public byte unknown_01_byte;
            public byte unknown_02_byte;
            public byte unknown_03_byte;

            public ShaderPass(BinaryReader r, SBShaderCache cache, int index)
            {
                _cache = cache;
                Index = index;
                Read(r);
            }

            public void Read(BinaryReader r)
            {
                VertexShaderIndex1 = r.ReadInt16();
                VertexShaderIndex2 = r.ReadInt16();
                VertexShaderIndex3 = r.ReadInt16();
                PixelShaderIndex1 = r.ReadInt16();
                PixelShaderIndex2 = r.ReadInt16();
                PixelShaderIndex3 = r.ReadInt16();
                byte[] tmp = r.ReadBytes(4);
                unknown_00_byte = tmp[0];
                unknown_01_byte = tmp[1];
                unknown_02_byte = tmp[2];
                unknown_03_byte = tmp[3];
            }

            public override string ToString()
            {
                return string.Format("{0} {1} {2} {3} {4} {5} {6} {7} {8} {9}",
                    VertexShaderIndex1,
                    VertexShaderIndex2,
                    VertexShaderIndex3,
                    PixelShaderIndex1,
                    PixelShaderIndex2,
                    PixelShaderIndex3,
                    unknown_00_byte, unknown_01_byte, unknown_02_byte, unknown_03_byte);
            }
        }

        public class ShaderCode
        {
            public class Parameter
            {
                // d007 = PosDecompCoeffs
                public string Name;
                public short StartRegister;
                public short DataSize;
                public byte NumSrcElements;
                
                public byte Mask;               // x, xy, xyz, xyzw, etc...
                public byte SrcElementSize;     //

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

                public void Read(BinaryReader r)
                {
                    Name = Encoding.ASCII.GetString(r.ReadBytes(4));
                    StartRegister = r.ReadInt16();
                    DataSize = r.ReadInt16();
                    byte[] tmp = r.ReadBytes(4);
                    NumSrcElements = tmp[0];
                    Mask = tmp[1];
                    SrcElementSize = tmp[2];
                    //unknown_05_byte = tmp[3]; // padding
                    
                    System.Diagnostics.Debug.Assert(NumSrcElements * SrcElementSize == DataSize);
                }

                public override string ToString()
                {
                    return string.Format("{0} {1} {2} {3} {4} {5}", Name, StartRegister, DataSize, NumSrcElements, Mask, SrcElementSize);
                }
            }
            
            public byte[] compiledCode;
            public Parameter[] parameters;
            public int parameterHash;

            public ShaderCache.ShaderType type;
            public bool IsNull = true;

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

            public void Read(BinaryReader r)
            {
                int paramOffset = r.ReadInt32();
                int codeSize = r.ReadInt32();

                if (paramOffset != 0)
                {
                    System.Diagnostics.Debug.Assert(paramOffset == codeSize + 4);
                }

                if(codeSize != 0)
                {
                    IsNull = false;
                    
                    compiledCode = r.ReadBytes(codeSize);

                    uint test = BitConverter.ToUInt32(compiledCode, 0);
                    if (test == 0xfffe0300)
                    {
                        type = ShaderCache.ShaderType.VertexShader;
                    }
                    else if (test == 0xffff0300)
                    {
                        type = ShaderCache.ShaderType.PixelShader;
                    }
                    else
                    {
                        System.Diagnostics.Debugger.Break();
                    }

                    int paramCount = r.ReadInt32();
                    if (paramCount != 0)
                    {
                        parameters = new Parameter[paramCount];
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            parameters[i] = new Parameter(r);
                        }
                        parameterHash = r.ReadInt32();
                    }
                }
            }

            public override string ToString()
            {
                if (compiledCode == null) return "NULL";
                return string.Format("{0} [{1}]", type, compiledCode.Length);
            }
        }

        public class ShaderCode2
        {
            public int Index;
            
            public ShaderCache.ShaderType type;
            public short NumInstructionSlots;

            public ShaderCache.TargetGPU target;

            // NV__
            // ATF4
            // ATI_
            // UNKN
            public ShaderCode code;

            public ShaderCode2(BinaryReader r, int index)
            {
                Index = index;
                Read(r);
            }

            public void Read(BinaryReader r)
            {
                short test = r.ReadInt16();

                if (test == 0x0700)
                {
                    type = ShaderCache.ShaderType.VertexShader;
                }
                else if (test == 0x0800)
                {
                    type = ShaderCache.ShaderType.PixelShader;
                }
                else
                {
                    System.Diagnostics.Debugger.Break();
                }

                NumInstructionSlots = r.ReadInt16();

                ShaderCode f1 = new ShaderCode(r);
                ShaderCode f2 = new ShaderCode(r);
                ShaderCode f3 = new ShaderCode(r);
                ShaderCode f4 = new ShaderCode(r);
                if (!f1.IsNull)
                {
                    target = ShaderCache.TargetGPU.NVIDIA;
                    code = f1;
                    return;
                }
                
                if (!f2.IsNull)
                {
                    target = ShaderCache.TargetGPU.ATF4;
                    code = f2;
                    return;
                }
                
                if (!f3.IsNull)
                {
                    target = ShaderCache.TargetGPU.ATI;
                    code = f3;
                    return;
                }

                if (!f4.IsNull)
                {
                    target = ShaderCache.TargetGPU.UNKNOWN;
                    code = f4;
                    return;
                }
            }

            public override string ToString()
            {
                return string.Format("{0} ({1}) [Inst:{2}]", type, target, NumInstructionSlots);
            }
        }

        public class MaterialEffect : IList<Technique>
        {
            public ulong Hash;

            public MaterialEffect(ulong hash)
            {
                Hash = hash;
            }

            public void AddTechnique(Technique tech)
            {
                _innerList.Add(tech);
            }

            #region IList
            private List<Technique> _innerList = new List<Technique>();

            public int IndexOf(Technique item)
            {
                return _innerList.IndexOf(item);
            }

            public void Insert(int index, Technique item)
            {
                throw new NotImplementedException();
            }

            public void RemoveAt(int index)
            {
                throw new NotImplementedException();
            }

            public Technique this[int index]
            {
                get
                {
                    return _innerList[index];
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            public void Add(Technique item)
            {
                throw new NotImplementedException();
            }

            public void Clear()
            {
                throw new NotImplementedException();
            }

            public bool Contains(Technique item)
            {
                return _innerList.Contains(item);
            }

            public void CopyTo(Technique[] array, int arrayIndex)
            {
                _innerList.CopyTo(array, arrayIndex);
            }

            public int Count
            {
                get { return _innerList.Count; }
            }

            public bool IsReadOnly
            {
                get { return true; }
            }

            public bool Remove(Technique item)
            {
                throw new NotImplementedException();
            }

            public IEnumerator<Technique> GetEnumerator()
            {
                return _innerList.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return _innerList.GetEnumerator();
            }
            #endregion

            public override string ToString()
            {
                return string.Format("Effect (0x{0:x16}) {1}", Hash, _innerList.Count);
            }
        }

        const int MAGIC = 0x43534253; // SBSC

        public int unknown_00_int;
        public int unknown_01_int;
        public int unknown_02_int;
        public int unknown_03_int;
        public int unknown_04_int;

        public Technique[] Techniques;

        public Dictionary<ulong, MaterialEffect> MaterialTechniquesSorted;

        public ShaderPass[] Passes;

        public int unknown_05_int;
        public int unknown_06_int;

        public ShaderCode2[] codes;

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

        public void Read(BinaryReader r)
        {
            int magic = r.ReadInt32();
            if (magic != MAGIC)
            {
                System.Diagnostics.Debugger.Break();
            }
            unknown_00_int = r.ReadInt32();
            unknown_01_int = r.ReadInt32();
            unknown_02_int = r.ReadInt32();
            unknown_03_int = r.ReadInt32();
            unknown_04_int = r.ReadInt32();
            
            int count = r.ReadInt32();
            MaterialTechniquesSorted = new Dictionary<ulong, MaterialEffect>();
            Techniques = new Technique[count];
            for (int i = 0; i < count; i++)
            {
                Technique t = new Technique(r, this, i);
                Techniques[i] = t;

                if (t.IsMaterialEffect)
                {
                    if (!MaterialTechniquesSorted.ContainsKey(t.HashComplete))
                    {
                        MaterialTechniquesSorted.Add(t.HashComplete, new MaterialEffect(t.HashComplete));
                    }
                    MaterialTechniquesSorted[t.HashComplete].AddTechnique(t);
                }
            }

            count = r.ReadInt32();
            Passes = new ShaderPass[count];
            for (int i = 0; i < count; i++)
            {
                Passes[i] = new ShaderPass(r, this, i);
            }
            
            unknown_05_int = r.ReadInt32();
            unknown_06_int = r.ReadInt32();
            count = r.ReadInt32();

            codes = new ShaderCode2[count];
            for (int i = 0; i < count; i++)
            {
                codes[i] = new ShaderCode2(r, i);
            }
        }
    }
}
