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

namespace HaloPlugins
{
    public class TagBlock : IMetaNode
    {
        int BlockSize = 0, Ptr = 0, Padding = 4, MaxCount = 0;
        string Name = "TagBlock";
        IMetaNode[] Fields = new IMetaNode[0];
        List<IMetaNode[]> Blocks = new List<IMetaNode[]>();
        HaloEngine Engine = HaloEngine.Halo2;

        #region Constructor

        public TagBlock(string Name, int BlockSize, int MaxCount, IMetaNode[] Fields)
        {
            this.Name = Name;
            this.BlockSize = BlockSize;
            this.MaxCount = MaxCount;
            this.Fields = Fields;
        }

        public TagBlock(string Name, int BlockSize, int MaxCount, HaloEngine Engine, IMetaNode[] Fields)
        {
            this.Name = Name;
            this.BlockSize = BlockSize;
            this.MaxCount = MaxCount;
            this.Engine = Engine;
            this.Fields = Fields;
        }

        public TagBlock(string Name, int BlockSize, int MaxCount, int Padding, IMetaNode[] Fields)
        {
            this.Name = Name;
            this.BlockSize = BlockSize;
            this.MaxCount = MaxCount;
            this.Padding = Padding;
            this.Fields = Fields;
        }

        public TagBlock(string Name, int BlockSize, int MaxCount, int Padding, HaloEngine Engine, IMetaNode[] Fields)
        {
            this.Name = Name;
            this.BlockSize = BlockSize;
            this.MaxCount = MaxCount;
            this.Padding = Padding;
            this.Engine = Engine;
            this.Fields = Fields;
        }

        #endregion

        #region TagBlock Members

        public void AddBlock()
        {
            // Create New Block
            IMetaNode[] New = new IMetaNode[Fields.Length];
            for (int i = 0; i < Fields.Length; i++)
            {
                New[i] = (IMetaNode)Fields[i].Clone();
            }

            // Add
            Blocks.Add(New);
        }

        public void AddBlock(IMetaNode[] Fields)
        {
            // Create New Block
            IMetaNode[] New = new IMetaNode[Fields.Length];
            for (int i = 0; i < Fields.Length; i++)
            {
                New[i] = (IMetaNode)Fields[i].Clone();
            }

            // Add
            Blocks.Add(New);
        }

        public void InsertBlock(int BlockIndex)
        {
            // Create New Block
            IMetaNode[] New = new IMetaNode[Fields.Length];
            for (int i = 0; i < Fields.Length; i++)
            {
                New[i] = (IMetaNode)Fields[i].Clone();
            }

            // Insert
            Blocks.Insert(BlockIndex, New);
        }

        public void RemoveBlock(int BlockIndex)
        {
            Blocks.RemoveAt(BlockIndex);
        }

        public void RemoveAll()
        {
            Blocks.Clear();
        }

        public int BlockCount
        {
            get { return Blocks.Count; }
        }

        #endregion

        #region MetaNode Members

        public void Read(EndianReader br)
        {
            // Read Header
            int BlockCount = br.ReadInt32();
            Ptr = br.ReadInt32();
            if (Engine != HaloEngine.Halo2)
                br.BaseStream.Position += 4;

            // Save Position
            int OldPos = (int)br.BaseStream.Position;

            // Read Blocks
            for (int i = 0; i < BlockCount; i++)
            {
                // Goto Block Start
                br.BaseStream.Position = Ptr + (i * BlockSize);

                // Add New Chunks
                AddBlock();

                // Read Fields
                for (int x = 0; x < Fields.Length; x++)
                    Blocks[i][x].Read(br);
            }

            // Restore Position
            br.BaseStream.Position = OldPos;
        }

        public void Read(EndianReader br, int Magic)
        {
            // Read Header
            int BlockCount = br.ReadInt32();
            Ptr = br.ReadInt32() - Magic;
            if (Engine != HaloEngine.Halo2)
                br.BaseStream.Position += 4;

            // Save Position
            int OldPos = (int)br.BaseStream.Position;

            // Read Blocks
            for (int i = 0; i < BlockCount; i++)
            {
                // Goto Block Start
                br.BaseStream.Position = Ptr + (i * BlockSize);

                // Add New Chunks
                AddBlock();

                // Read Fields
                for (int x = 0; x < Fields.Length; x++)
                    Blocks[i][x].Read(br, Magic);
            }

            // Restore Position
            br.BaseStream.Position = OldPos;
        }

        public void Write(EndianWriter bw)
        {
            // Write Header
            bw.Write(Blocks.Count);
            bw.Write((int)bw.BaseStream.Length);
            if (Engine != HaloEngine.Halo2)
                bw.Write(0);

            // Save Position
            int OldPos = (int)bw.BaseStream.Position;

            // Goto the new Trans
            bw.BaseStream.Position = bw.BaseStream.Length;
            Ptr = (int)bw.BaseStream.Position;

            // Write Padding to correct any nested reflexives
            bw.Write(new byte[Blocks.Count * BlockSize]);
            bw.BaseStream.Position = Ptr;

            // Write Blocks
            for (int x = 0; x < Blocks.Count; x++)
            {
                // Goto Block Start
                bw.BaseStream.Position = Ptr + (x * BlockSize);

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

            // Restore Position
            bw.BaseStream.Position = OldPos;
        }

        public object GetValue()
        {
            return BlockCount == 0 ? Fields : Blocks[0];
        }

        public void SetValue(object Value)
        {
            throw new NotImplementedException();
        }

        public int GetSize()
        {
            return Engine == HaloEngine.Halo2 ? 8 : 12;
        }

        public void Compile(EndianWriter bw, int Magic)
        {
            // Write Header
            bw.Write(Blocks.Count);
            bw.Write(0);
            if (Engine != HaloEngine.Halo2)
                bw.Write(0);

            // Save Old Position
            int OldPos = (int)bw.BaseStream.Position;

            // Goto the new Trans
            bw.BaseStream.Position = (int)bw.BaseStream.Length;

            // Write Alignment Padding
            if (Blocks.Count > 0)
            {
                int Pad = Padding - ((int)bw.BaseStream.Position % Padding);
                if (Pad != Padding)
                    bw.Write(new byte[Pad]);
            }

            // Write Padding to correct any nested reflexives
            Ptr = (int)bw.BaseStream.Position;
            bw.Write(new byte[Blocks.Count * BlockSize]);
            bw.BaseStream.Position = Ptr;

            // Write Blocks
            for (int x = 0; x < Blocks.Count; x++)
            {
                // Goto Block Start
                bw.BaseStream.Position = Ptr + (x * BlockSize);

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

            // Restore Old Position
            bw.BaseStream.Position = OldPos - (Engine == HaloEngine.Halo2 ? 4 : 8);

            // Write Translation with Padding
            if (Blocks.Count > 0)
                bw.Write(Ptr + Magic);
            else
                bw.Write(new byte[4]);

            if (Engine != HaloEngine.Halo2)
                bw.BaseStream.Position += 4;
        }

        public void Decompile(EndianWriter bw, int Magic)
        {
            // Write Header
            bw.Write(Blocks.Count);
            bw.Write((int)bw.BaseStream.Length);
            if (Engine != HaloEngine.Halo2)
                bw.Write(0);

            // Save Old Position
            int OldPos = (int)bw.BaseStream.Position;

            // Goto the new Trans
            bw.BaseStream.Position = (int)bw.BaseStream.Length;
            Ptr = (int)bw.BaseStream.Position;

            // Write Padding to correct any nested reflexives
            bw.Write(new byte[Blocks.Count * BlockSize]);
            bw.BaseStream.Position = Ptr;

            // Write Blocks
            for (int x = 0; x < Blocks.Count; x++)
            {
                // Goto Block Start
                bw.BaseStream.Position = Ptr + (x * BlockSize);

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

            // Restore Old Position
            bw.BaseStream.Position = OldPos;
        }

        public string GetName()
        {
            return Name;
        }

        #endregion

        #region Indexers

        public IMetaNode[] this[int BlockIndex]
        {
            // Return Fields
            get { return Blocks[BlockIndex]; }
            set
            {
                // Copy Fields
                for (int i = 0; i < Fields.Length; i++)
                    Blocks[BlockIndex][i] = (IMetaNode)value[i].Clone();
            }
        }

        public IMetaNode this[int BlockIndex, int NodeIndex]
        {
            // Return Field
            get { return Blocks[BlockIndex][NodeIndex]; }

            // Set Field
            set { Blocks[BlockIndex][NodeIndex] = (IMetaNode)value.Clone(); }
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            // Initialize New Tag Block
            TagBlock NewBlock = new TagBlock(Name, BlockSize, MaxCount, Padding, Engine, Fields);

            // Copy Blocks
            for (int i = 0; i < Blocks.Count; i++)
            {
                // Add New Block
                NewBlock.AddBlock();

                // Clone Fields
                for (int x = 0; x < Fields.Length; x++)
                    NewBlock[i][x] = (IMetaNode)Blocks[i][x].Clone();
            }

            return NewBlock;
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            return "TagBlock: " + Name + ", BlockCount: " + Blocks.Count.ToString() + ", BlockSize: " + BlockSize.ToString() + ", Padding: " + Padding.ToString() + ", MaxCount: " + MaxCount.ToString();
        }

        #endregion
    }
}
