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

namespace HaloObjects
{
    public class Meta : MetaNode
    {
        public MetaNode[] Fields = new MetaNode[0];
        public string CompiledClass = "", DefaultExtension = "";
        public int HeaderSize = 0;

        public List<string> TagReferences = new List<string>();
        public List<string> TagStringIds = new List<string>();

        public Meta(MetaNode[] Fields, string CompiledClass, string DefaultExtension, int HeaderSize)
        {
            this.Fields = Fields;
            this.CompiledClass = CompiledClass;
            this.DefaultExtension = DefaultExtension;
            this.HeaderSize = HeaderSize;
        }

        #region MetaNode Members

        public void Read(System.IO.BinaryReader br)
        {
            // Header Values
            int TagCount = br.ReadInt32();
            int StringCount = br.ReadInt32();
            int DataSize = br.ReadInt32();

            // Read Strings
            br.BaseStream.Position = DataSize;
            for (int i = 0; i < TagCount; i++)
            {
                string temp = "";
                char c;
                while ((c = Convert.ToChar(br.ReadByte())) != '\0')
                    temp += c;

                TagReferences.Add(temp);
            }

            // StringIds
            for (int i = 0; i < StringCount; i++)
            {
                string temp = "";
                char c;
                while ((c = Convert.ToChar(br.ReadByte())) != '\0')
                    temp += c;

                TagStringIds.Add(temp);
            }

            // Set Globals
            Global.Globals.Plugin = this;

            // Fields
            br.BaseStream.Position = 512;
            for (int i = 0; i < Fields.Length; i++)
                Fields[i].Read(br);
        }

        public void Read(System.IO.BinaryReader br, int Magic)
        {
            // Fields
            for (int i = 0; i < Fields.Length; i++)
            {
                Fields[i].Read(br, Magic);
            }
        }

        public void Write(System.IO.BinaryWriter bw)
        {
            // Set Globals
            Global.Globals.Plugin = this;

            // Header
            bw.Write(new byte[512]);
            bw.Write(new byte[HeaderSize]);
            bw.BaseStream.Position = 512;

            // Fields
            for (int i = 0; i < Fields.Length; i++)
                Fields[i].Write(bw);

            // Finish Header
            bw.BaseStream.Position = 0;
            bw.Write(TagReferences.Count);
            bw.Write(TagStringIds.Count);
            bw.Write((int)bw.BaseStream.Length);
            bw.Write(((FileStream)bw.BaseStream).Name.ToCharArray());
            bw.BaseStream.Position = bw.BaseStream.Length;

            // Write Tag Reference Names
            for (int i = 0; i < TagReferences.Count; i++)
            {
                bw.Write(TagReferences[i].ToCharArray());
                bw.Write((byte)0);
            }

            // Write String Reference Names
            for (int i = 0; i < TagStringIds.Count; i++)
            {
                bw.Write(TagStringIds[i].ToCharArray());
                bw.Write((byte)0);
            }
        }

        public object GetValue()
        {
            return Fields;
        }

        public void SetValue(object Value)
        {
            Fields = (MetaNode[])Value;
        }

        public int GetSize()
        {
            return 0;
        }

        public void Compile(System.IO.BinaryWriter bw, int Magic)
        {
            // Write Header
            int Offset = (int)bw.BaseStream.Position;
            bw.Write(new byte[HeaderSize]);
            bw.BaseStream.Position = Offset;

            // Compile Fields
            for (int i = 0; i < Fields.Length; i++)
                Fields[i].Compile(bw, Magic);
        }

        public void Decompile(System.IO.BinaryWriter bw, int Magic)
        {
            // Header
            bw.Write(new byte[512]);
            bw.Write(new byte[HeaderSize]);
            bw.BaseStream.Position = 512;

            // Fields
            for (int i = 0; i < Fields.Length; i++)
                Fields[i].Decompile(bw, Magic);

            // Finish Header
            bw.BaseStream.Position = 0;
            bw.Write(Globals.TagReferences.Count);
            bw.Write(Globals.TagStringIds.Count);
            bw.Write((int)bw.BaseStream.Length);
            bw.Write(((FileStream)bw.BaseStream).Name.ToCharArray());
            bw.BaseStream.Position = bw.BaseStream.Length;

            // Write Tag Reference Names
            for (int i = 0; i < Globals.TagReferences.Count; i++)
            {
                bw.Write(Globals.TagReferences[i].ToCharArray());
                bw.Write((byte)0);
            }

            // Write String Reference Names
            for (int i = 0; i < Globals.TagStringIds.Count; i++)
            {
                bw.Write(Globals.TagStringIds[i].ToCharArray());
                bw.Write((byte)0);
            }
        }

        public string GetName()
        {
            return "";
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            throw new NotImplementedException();
        }

        #endregion

        public void Decompile(System.IO.BinaryReader br, System.IO.BinaryWriter bw, System.IO.BinaryWriter Mbw, int Magic, string Class, int TagOffset)
        {
            // Get the Name
            string Location = ((System.IO.FileStream)Mbw.BaseStream).Name;
            string Extension = Location.Substring(Location.IndexOf(".") + 1);
            Location = Location.Remove(Location.IndexOf('.'));

            // Read Values
            br.BaseStream.Position = TagOffset;
            Read(br, Magic);

            // Write Raw
            #region Bitmap

            if (Extension == "bitmap")
            {
                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".bitmapraw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive bitm = (Reflexive)Fields[20];
                for (int i = 0; i < bitm.ChunkCount; i++)
                {
                    // Get Fields
                    bitm.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])bitm.GetValue();

                    // Decompile all 3 LODs
                    for (int x = 0; x < 3; x++)
                    {
                        // Get Values
                        int Offset = (int)fields[12 + x].GetValue();
                        int Size = (int)fields[18 + x].GetValue();

                        // Double Check
                        if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                        {
                            // Write new Offset
                            Offset &= 0x3FFFFFFF;
                            fields[12 + x].SetValue((int)writer.BaseStream.Length);

                            // Write Raw
                            br.BaseStream.Position = Offset;
                            byte[] Buffer = br.ReadBytes(Size);
                            writer.Write(Buffer);
                        }
                    }

                    // Save Changes
                    bitm.SetValue(fields);
                }
                writer.Close();
            }

            #endregion

            #region Decorator

            else if (Extension == "decorator")
            {
                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".decoratorraw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Get Fields
                int Offset = (int)Fields[8].GetValue();
                int Size = (int)Fields[9].GetValue();

                // Decompile
                if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                {
                    // Write new Offset
                    Offset &= 0x3FFFFFFF;
                    Fields[8].SetValue((int)writer.BaseStream.Length);

                    // Write Raw
                    br.BaseStream.Position = Offset;
                    byte[] Buffer = br.ReadBytes(Size);
                    writer.Write(Buffer);
                }
                writer.Close();
            }

            #endregion

            #region Animation

            else if (Extension == "model_animation_graph")
            {
                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".animationraw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive Anim = (Reflexive)Fields[21];
                for (int i = 0; i < Anim.ChunkCount; i++)
                {
                    // Get Fields
                    Anim.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])Anim.GetValue();

                    // Get Offset and Size
                    int Size = (int)fields[1].GetValue();
                    int Offset = (int)fields[2].GetValue();

                    // Write Raw
                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[2].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    Anim.SetValue(fields);
                }
                writer.Close();
            }

            #endregion

            #region Particles

            else if (Extension == "particle_model")
            {
                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".particleraw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Get Offset and Size
                int Offset = (int)Fields[24].GetValue();
                int Size = (int)Fields[25].GetValue();

                // Decompile
                if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                {
                    // Write new Offset
                    Offset &= 0x3FFFFFFF;
                    Fields[24].SetValue((int)writer.BaseStream.Length);

                    // Write Raw
                    br.BaseStream.Position = Offset;
                    byte[] Buffer = br.ReadBytes(Size);
                    writer.Write(Buffer);
                }
                writer.Close();
            }

            #endregion

            #region BSP

            else if (Extension == "scenario_structure_bsp")
            {
                #region Detail Objects

                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".bsp1raw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive Bsp1 = (Reflexive)Fields[26];
                for (int i = 0; i < Bsp1.ChunkCount; i++)
                {
                    // Get Fields
                    Bsp1.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])Bsp1.GetValue();

                    // Get Offset and Size
                    int Offset = (int)fields[19].GetValue();
                    int Size = (int)fields[20].GetValue();

                    // Write Raw
                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[19].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    // Save Changes
                    Bsp1.SetValue(fields);
                }
                writer.Close();

                #endregion

                #region Permutations

                // Create Raw File
                writer = new BinaryWriter(new FileStream(Location + ".bsp2raw", FileMode.Create, FileAccess.Write, FileShare.Write));

                // Decompile
                Reflexive Bsp2 = (Reflexive)Fields[41];
                for (int i = 0; i < Bsp2.ChunkCount; i++)
                {
                    // Get Fields
                    Bsp2.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])Bsp2.GetValue();

                    // Get Offset and Size
                    int Offset = (int)fields[19].GetValue();
                    int Size = (int)fields[20].GetValue();

                    // Write Raw
                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[19].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    // Save Changes
                    Bsp2.SetValue(fields);
                }
                writer.Close();

                #endregion

                #region Water Definitions

                // Create Raw File
                writer = new BinaryWriter(new FileStream(Location + ".bsp3raw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive Bsp3 = (Reflexive)Fields[63];
                for (int i = 0; i < Bsp3.ChunkCount; i++)
                {
                    // Get Fields
                    Bsp3.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])Bsp3.GetValue();

                    // Get Offset and Size
                    int Offset = (int)fields[2].GetValue();
                    int Size = (int)fields[3].GetValue();

                    // Write Raw
                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[2].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    // Save Changes
                    Bsp3.SetValue(fields);
                }
                writer.Close();

                #endregion

                #region Decorators

                // Create Raw File
                writer = new BinaryWriter(new FileStream(Location + ".bsp4raw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive Bsp4 = (Reflexive)Fields[67];
                for (int i = 0; i < Bsp4.ChunkCount; i++)
                {
                    // Get Fields
                    Bsp4.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])Bsp4.GetValue();

                    // Decompile
                    Reflexive Blocks = (Reflexive)fields[4];
                    for (int x = 0; x < Blocks.ChunkCount; x++)
                    {
                        // Get Fields
                        Blocks.ChangeIndex(x);
                        MetaNode[] fields1 = (MetaNode[])Blocks.GetValue();

                        // Get Offset and Size
                        int Offset = (int)fields1[0].GetValue();
                        int Size = (int)fields1[1].GetValue();

                        // Write Raw
                        if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                        {
                            // Write new Offset
                            Offset &= 0x3FFFFFFF;
                            fields1[0].SetValue((int)writer.BaseStream.Length);

                            // Write Data
                            br.BaseStream.Position = Offset;
                            byte[] Buffer = br.ReadBytes(Size);
                            writer.Write(Buffer);
                        }

                        // Save Changes
                        Blocks.SetValue(fields1);
                    }
                    
                    // Save Changes
                    Bsp4.SetValue(fields);
                }
                writer.Close();

                #endregion
            }

            #endregion

            #region Lightmap

            else if (Extension == "lightmap")
            {
                #region Lightmap Groups

                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".lightmapraw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive ltmp = (Reflexive)Fields[15];
                for (int i = 0; i < ltmp.ChunkCount; i++)
                {
                    // Get Fields
                    ltmp.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])ltmp.GetValue();

                    #region Clustors

                    // Decompile
                    Reflexive Raw1 = (Reflexive)fields[6];
                    for (int x = 0; x < Raw1.ChunkCount; x++)
                    {
                        // Get Fields
                        Raw1.ChangeIndex(i);
                        MetaNode[] fields1 = (MetaNode[])Raw1.GetValue();

                        // Get Offset and Size
                        int Offset = (int)fields1[18].GetValue();
                        int Size = (int)fields1[19].GetValue();

                        // Write Raw
                        if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                        {
                            // Write new Offset
                            Offset &= 0x3FFFFFFF;
                            fields1[18].SetValue((int)writer.BaseStream.Length);

                            // Write Data
                            br.BaseStream.Position = Offset;
                            byte[] Buffer = br.ReadBytes(Size);
                            writer.Write(Buffer);
                        }

                        // Save Changes
                        Raw1.SetValue(fields1);
                    }

                    #endregion

                    #region Poop Definitions

                    // Decompile
                    Reflexive Raw2 = (Reflexive)fields[8];
                    for (int x = 0; x < Raw2.ChunkCount; x++)
                    {
                        // Get Fields
                        Raw2.ChangeIndex(i);
                        MetaNode[] fields1 = (MetaNode[])Raw2.GetValue();

                        // Get Offset and Size
                        int Offset = (int)fields1[18].GetValue();
                        int Size = (int)fields1[19].GetValue();

                        // Write Raw
                        if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                        {
                            // Write new Offset
                            Offset &= 0x3FFFFFFF;
                            fields1[18].SetValue((int)writer.BaseStream.Length);

                            // Write Data
                            br.BaseStream.Position = Offset;
                            byte[] Buffer = br.ReadBytes(Size);
                            writer.Write(Buffer);
                        }

                        // Save Changes
                        Raw2.SetValue(fields1);
                    }

                    #endregion

                    #region Geometry Buckets

                    // Decompile
                    Reflexive Raw3 = (Reflexive)fields[10];
                    for (int x = 0; x < Raw3.ChunkCount; x++)
                    {
                        // Get Fields
                        Raw3.ChangeIndex(i);
                        MetaNode[] fields1 = (MetaNode[])Raw3.GetValue();

                        // Get Offset and Size
                        int Offset = (int)fields1[2].GetValue();
                        int Size = (int)fields1[3].GetValue();

                        // Write Raw
                        if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                        {
                            // Write new Offset
                            Offset &= 0x3FFFFFFF;
                            fields1[2].SetValue((int)writer.BaseStream.Length);

                            // Write Data
                            br.BaseStream.Position = Offset;
                            byte[] Buffer = br.ReadBytes(Size);
                            writer.Write(Buffer);
                        }

                        // Save Changes
                        Raw3.SetValue(fields1);
                    }

                    #endregion

                    // Save Changes
                    ltmp.SetValue(fields);
                }
                writer.Close();

                #endregion
            }

            #endregion

            #region Model

            else if (Extension == "render_model")
            {
                #region Mesh Sections

                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".model1raw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive mode1 = (Reflexive)Fields[6];
                for (int i = 0; i < mode1.ChunkCount; i++)
                {
                    // Get Fields
                    mode1.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])mode1.GetValue();

                    // Get Offset and Size
                    int Offset = (int)fields[26].GetValue();
                    int Size = (int)fields[27].GetValue();

                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[26].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    // Save Changes
                    mode1.SetValue(fields);
                }
                writer.Close();

                #endregion

                #region Depreciated Lightmap Raw

                // Create Raw File
                writer = new BinaryWriter(new FileStream(Location + ".model2raw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive mode2 = (Reflexive)Fields[21];
                for (int i = 0; i < mode2.ChunkCount; i++)
                {
                    // Get Fields
                    mode2.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])mode2.GetValue();

                    // Get Offset and Size
                    int Offset = (int)fields[11].GetValue();
                    int Size = (int)fields[12].GetValue();

                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[11].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    // Save Changes
                    mode2.SetValue(fields);
                }
                writer.Close();

                #endregion
            }

            #endregion

            #region Coconut Raw

            else if (Extension == "sound_diagnostics")
            {
                #region Sound Raw

                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".soundraw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive snd = (Reflexive)Fields[8];
                for (int i = 0; i < snd.ChunkCount; i++)
                {
                    // Get Fields
                    snd.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])snd.GetValue();

                    // Get Offset and Size
                    int Offset = (int)fields[0].GetValue();
                    int Size = (int)fields[1].GetValue() & 0x7FFFFFFF;

                    // Write Data
                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[0].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    // Save Changes
                    snd.SetValue(fields);
                }
                writer.Close();

                #endregion

                #region Model Raw

                // Create Raw File
                writer = new BinaryWriter(new FileStream(Location + ".modelraw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive model = (Reflexive)Fields[10];
                for (int i = 0; i < model.ChunkCount; i++)
                {
                    // Get Fields
                    model.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])model.GetValue();

                    // Get Offset and Size
                    int Offset = (int)fields[1].GetValue();
                    int Size = (int)fields[2].GetValue();

                    // Write Data
                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[1].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    // Save Changes
                    model.SetValue(fields);
                }
                writer.Close();

                #endregion
            }

            #endregion

            #region Weather

            else if (Extension == "weather")
            {
                // Create Raw File
                BinaryWriter writer = new BinaryWriter(new FileStream(Location + ".weatherraw", FileMode.Create, FileAccess.Write, FileShare.Write));
                
                // Decompile
                Reflexive weat = (Reflexive)Fields[0];
                for (int i = 0; i < weat.ChunkCount; i++)
                {
                    // Get Fields
                    weat.ChangeIndex(i);
                    MetaNode[] fields = (MetaNode[])weat.GetValue();

                    // Get Offset and Size
                    int Offset = (int)fields[0].GetValue();
                    int Size = (int)fields[0].GetValue();

                    // Write Raw
                    if ((Offset & 0xC0000000) == 0 && (Offset & 0x3FFFFFFF) > 0 && Size > 0)
                    {
                        // Write new Offset
                        Offset &= 0x3FFFFFFF;
                        fields[0].SetValue((int)writer.BaseStream.Length);

                        // Write Data
                        br.BaseStream.Position = Offset;
                        byte[] Buffer = br.ReadBytes(Size);
                        writer.Write(Buffer);
                    }

                    // Save Changes
                    weat.SetValue(fields);
                }
                writer.Close();
            }

            #endregion

            // Decompile the Tag
            Decompile(Mbw, Magic);
        }
    }
}
