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

namespace Halo3Map.Map
{
    /// <summary>
    /// Holds information about A Halo 3 Map file
    /// </summary>
    public class Map
    {
        /// <summary>
        /// Lets us perfom IO functions on the currently loaded map
        /// </summary>
        private EndianIO io;
        public EndianIO IO { get { return io; } }
        /// <summary>
        /// Holds information on how to find various pieces of the map file
        /// </summary>
        private Header header;
        public Header Header { get { return header; } }
        /// <summary>
        /// Holds information about the tags contained within the map file
        /// </summary>
        private IndexHeader index;
        public IndexHeader Index { get { return index; } }
        /// <summary>
        /// Lets us access the strings within the map
        /// </summary>
        private StringsTable stringsTable;
        public StringsTable StringsTable { get { return stringsTable; } }
        /// <summary>
        /// Magic number used to translate pointers and reflexives
        /// </summary>
        public int Magic { get { return header.base_address - (header.raw_data_offset + header.raw_data_size); } }

        public Map(string FilePath)
        {
            //Setup the IO
            io = new EndianIO(FilePath, EndianType.BigEndian);
        }
        /// <summary>
        /// Opens the IO stream for Reading and Writing
        /// </summary>
        public void Open()
        {
            io.Open();
        }
        /// <summary>
        /// Closes the IO stream
        /// </summary>
        public void Close()
        {
            io.Close();
        }

        public void Read()
        {
            //Read the header
            io.In.SeekTo(0);
            header = new Header();
            header.Map = this;
            header.Read(io.In);

            //Now lets make sure they arent trying to read too far into a shared
            //Make sure this isnt a shared map before we read the tags
            if (header.map_type == Map_Type.shared ||
                header.map_type == Map_Type.single_player_shared)
                return;

            //Read the index
            io.In.SeekTo(header.memory_modifier - Magic);
            index = new IndexHeader();
            index.Map = this;
            index.Read(io.In);

            //Read Strings Table
            io.In.SeekTo(header.string_table_offset - header.HeaderMagic);
            stringsTable = new StringsTable();
            stringsTable.Read(header.string_count, io.In);
        }
        /// <summary>
        /// Finds the Index number of a tag using the tags Ident
        /// </summary>
        /// <param name="Idt">Ident of tag</param>
        /// <returns>Index Number</returns>
        public int FindIndexNumberByID(int Idt)
        {
            for (int i = 0; i < index.meta_count; i++)
            {
                if (Idt == index.Metas[i].ident)
                {
                    return i;
                }
            }
            return -1;
        }
        public bool TagClassExist(string tag_class)
        {
            for (int i = 0; i < index.MetaClasses.Count; i++)
            {                
                if (tag_class == index.MetaClasses[i].type)
                {
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Finds the Index number of a tag using the tags filename
        /// </summary>
        /// <param name="Filename">Filename of tag</param>
        /// <returns>Index Number</returns>        
        public int FindIndexNumberByFilename(string Filename)
        {
            for (int i = 0; i < index.meta_count; i++)
            {
                if (Filename == index.Metas[i].FilePath)
                {
                    return i;
                }
            }
            return -1;
        }
        /// <summary>
        /// Recalculates a Halo 3 Map files Checksum
        /// </summary>
        public void Resign()
        {
            int result = 0;
            int x;
            io.In.BaseStream.Position = 12288;
            int temp = ((int)io.In.BaseStream.Length - 12288) / 4;
            for (x = 0; x < temp; x++)
            {
                result = result ^ io.In.ReadInt32();
            }
            io.Out.BaseStream.Seek(708, SeekOrigin.Begin);
            io.Out.Write(result);
            header.checksum = result;
        }
        /// <summary>
        /// Returns the 'Magic' of the currently selcected map.
        /// Not really usefull anymore now that the proper way of
        /// calculating 'Magic' values is now known.
        /// </summary>
        /// <param name="Name">Name of maps to get magic for</param>
        private uint GetMagicFromInternalName(string Name)
        {
            switch (Name)
            {
                case "005_intro":
                    {
                        return 0xB724D000;
                    }
                case "010_jungle":
                    {
                        return 0xACEBC000;
                    }
                case "020_base":
                    {
                        return 0xA6D47000;
                    }
                case "030_outskirts":
                    {
                        return 0xAA39A000;
                    }
                case "040_voi":
                    {
                        return 0xA7BAE000;
                    }
                case "050_floodvoi":
                    {
                        return 0xA7E9B000;
                    }
                case "070_waste":
                    {
                        return 0xA6191000;
                    }
                case "100_citadel":
                    {
                        return 0xA19C9000;
                    }
                case "110_hc":
                    {
                        return 0xAE931000;
                    }
                case "120_halo":
                    {
                        return 0xA80D9000;
                    }
                case "130_epilogue":
                    {
                        return 0xB74A3000;
                    }
                case "chill":
                    {
                        return 0xBA060000;
                    }
                case "construct":
                    {
                        return 0xB9B9A000;
                    }
                case "salvation":
                    {
                        return 0xBADEE000;
                    }
                case "cyberdyne":
                    {
                        return 0xB9D20000;
                    }
                case "deadlock":
                    {
                        return 0xB94D3000;
                    }
                case "guardian":
                    {
                        return 0xB9BF8000;
                    }
                case "isolation":
                    {
                        return 0xB8D02000;
                    }
                case "mainmenu":
                    {
                        return 0xBC931000;
                    }
                case "riverworld":
                    {
                        return 0xB950F000;
                    }
                case "shrine":
                    {
                        return 0xB97CF000;
                    }
                case "snowbound":
                    {
                        return 0xBA84D000;
                    }
                case "zanzibar":
                    {
                        return 0xB81A0000;
                    }
            }
            return 0x0;
        }
    }
}