using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using static DSPRE.RomInfo;

namespace DSPRE.ROMFiles {
    /* ---------------------- HEADER DATA STRUCTURE (DPPt):----------------------------
        
       0x0  //  byte:       Area data value
       0x1  //  byte:       Unknown value
       0x2  //  ushort:     Matrix number
       0x4  //  ushort:     Script file number
       0x6  //  ushort:     Level script file number
       0x8  //  ushort:     Text Archive number
       0xA  //  ushort:     Day music track number
       0xC  //  ushort:     Night music track number
       0xE  //  ushort:     Wild Pokémon file number
       0x10 //  ushort:     Event file number

       * D/P:
       0x12 //  ushort:     Index of map name in Text Archive #382 (US version)   
       
       * Plat:
       0x12 //  byte:       Index of map name in Text Archive #382 (US version)
       0x13 //  byte:       Map name textbox type value

       0x14 //  byte:       Weather value
       0x15 //  byte:       Camera value
       0x16 //  byte:       Boolean flag: show name when entering map
       0x17 //  byte:       Bitwise permission flags:

       -----------------    1: Allow Fly
       -----------------    2: ?
       -----------------    3: ?
       -----------------    4: Allow Bike usage
       -----------------    5: ?
       -----------------    6: ?
       -----------------    7: Esc. Rope
       -----------------    8: ?

    /* ---------------------- HEADER DATA STRUCTURE (HGSS):----------------------------
        
       0x0  //  byte:       Wild Pokémon file number
       0x1  //  byte:       Area data value
       0x2  //  byte:       ?
       0x3  //  byte:       ?
       0x4  //  ushort:     Matrix number
       0x6  //  ushort:     Script file number
       0x8  //  ushort:     Level script file
       0xA  //  ushort:     Text Archive number
       0xC  //  ushort:     Day music track number
       0xE  //  ushort:     Night music track number
       0x10 //  ushort:     Event file number
       0x12 //  byte:       Index of map name in Text Archive #382 (US version)
       0x13 //  byte:       Map name textbox type value
       0x14 //  byte:       Weather value
       0x15 //  byte:       Camera value
       0x16 //  byte:       Follow mode (for the Pokémon following hero)
       0x17 //  byte:       Bitwise permission flags:

        DPPT
       -----------------    1: Allow Fly
       -----------------    2: Allow Esc. Rope
       -----------------    3: Allow Running
       -----------------    4: Allow Bike 
       -----------------    5: Battle BG b4
       -----------------    6: Battle BG b3
       -----------------    7: Battle BG b2
       -----------------    8: Battle BG b1

        HGSS
       -----------------    1: ?
       -----------------    2: ?
       -----------------    3: ?
       -----------------    4: Allow Fly 
       -----------------    5: Allow Esc. Rope
       -----------------    6: ?
       -----------------    7: Allow Bicycle
       -----------------    8: ?

    ----------------------------------------------------------------------------------*/

    /// <summary>
    /// General class to store common map header data across all Gen IV Pokémon NDS games
    /// </summary>
    public abstract class MapHeader : RomFile {
        /*System*/
        public static readonly string DefaultFilter = "DSPRE Header File (*.dsh; *.bin)|*.dsh;*.bin";
        public ushort ID { get; set; }
        public static readonly byte length = 24;
        public static readonly string nameSeparator = " -   ";
        public const int HGSS_NULL_ENCOUNTER_FILE_ID = 0xff;
        public const int DPPT_NULL_ENCOUNTER_FILE_ID = 0xffff;

        public enum SearchableFields: byte {
            AreaDataID,
            CameraAngleID,
            EventFileID,
            InternalName,
            LevelScriptID,
            MatrixID,
            MusicDayID,
            //MusicDayName,
            MusicNightID,
            //MusicNightName,
            ScriptFileID,
            TextArchiveID,
            WeatherID,
        };



        #region Fields (10)
        public byte areaDataID { get; set; }
        public byte cameraAngleID { get; set; }
        public ushort eventFileID { get; set; }
        public ushort levelScriptID { get; set; }
        public ushort matrixID { get; set; }
        public ushort scriptFileID { get; set; }
        public ushort musicDayID { get; set; }
        public ushort musicNightID { get; set; }
        public byte locationSpecifier { get; set; }
        public byte battleBackground { get; set; }
        public ushort textArchiveID { get; set; }
        public byte weatherID { get; set; }
        public byte flags { get; set; }
        public ushort wildPokemon { get; set; }
        #endregion Fields

        #region Methods (1)
        public static string BuildName(int headerID, string name) {
            return headerID.ToString("D3") + MapHeader.nameSeparator + name;
        }

        public static MapHeader LoadFromByteArray(byte[] headerData, ushort headerNumber, RomInfo.GameFamilies gameFamily = RomInfo.GameFamilies.NULL) {
            /* Encapsulate header data into the class appropriate for the gameVersion */
            if (headerData.Length < MapHeader.length) {
                MessageBox.Show("File of header " + headerNumber + " is too small and can't store header data.", "Header file too small", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }

            if (gameFamily == RomInfo.GameFamilies.NULL) {
                gameFamily = RomInfo.gameFamily;
            }

            switch (gameFamily) {
                case RomInfo.GameFamilies.DP:
                    return new HeaderDP(headerNumber, new MemoryStream(headerData));
                case RomInfo.GameFamilies.Plat:
                    return new HeaderPt(headerNumber, new MemoryStream(headerData));
                default:
                    return new HeaderHGSS(headerNumber, new MemoryStream(headerData));
            }
        }

        public static MapHeader LoadFromFile(string filename, ushort headerNumber, long offsetInFile, RomInfo.GameFamilies gameFamily = RomInfo.GameFamilies.NULL) {
            /* Calculate header offset and load data */
            byte[] headerData = DSUtils.ReadFromFile(filename, offsetInFile, MapHeader.length);
            return LoadFromByteArray(headerData, headerNumber, gameFamily);
        }

        public static MapHeader LoadFromARM9(ushort headerNumber, RomInfo.GameFamilies gameFamily = RomInfo.GameFamilies.NULL) {
            long headerOffset = RomInfo.headerTableOffset + MapHeader.length * headerNumber;
            return LoadFromFile(RomInfo.arm9Path, headerNumber, headerOffset, gameFamily);
        }

        public static MapHeader GetMapHeader(ushort headerNumber) {
            MapHeader mapHeader;

            //Dynamic headers patch unsupported in DP
            if (RomInfo.gameFamily.Equals(RomInfo.GameFamilies.DP)) {
                return MapHeader.LoadFromARM9(headerNumber);
            }

            /* Check if dynamic headers patch has been applied, and load header from arm9 or a/0/5/0 accordingly */
            if (PatchToolboxDialog.flag_DynamicHeadersPatchApplied) {
                string path = Filesystem.GetDynamicHeaderPath(headerNumber);
                mapHeader = MapHeader.LoadFromFile(path, headerNumber, 0);
            } else {
                mapHeader = MapHeader.LoadFromARM9(headerNumber);
            }

            return mapHeader;
        }

        public static int GetHeaderCount() {
            int headerCount;
            if (PatchToolboxDialog.flag_DynamicHeadersPatchApplied) {
                headerCount = Filesystem.GetDynamicHeadersCount();
            } else {
                headerCount = RomInfo.GetHeaderCount();
            }

            return headerCount;
        }

        public void SaveFile() {
            /* Check if dynamic headers patch has been applied, and save header to arm9 or a/0/5/0 accordingly */
            if (PatchToolboxDialog.flag_DynamicHeadersPatchApplied) {
                string path = Filesystem.GetDynamicHeaderPath(ID);
                DSUtils.WriteToFile(path, this.ToByteArray(), 0, 0, fmode: FileMode.Create);
            } else {
                uint headerOffset = (uint)(RomInfo.headerTableOffset + MapHeader.length * this.ID);
                ARM9.WriteBytes(this.ToByteArray(), headerOffset);
            }
        }


        #endregion
    }

    /// <summary>
    /// Class to store map header data from Pokémon D and P
    /// </summary>
    public class HeaderDP : MapHeader {
        #region Fields (5)
        public byte unknown1 { get; set; }
        public ushort locationName { get; set; }
        #endregion Fields

        #region Constructors (1)
        public HeaderDP(ushort headerNumber, Stream data) {
            this.ID = headerNumber;
            using (BinaryReader reader = new BinaryReader(data)) {
                areaDataID = reader.ReadByte();
                unknown1 = reader.ReadByte();
                matrixID = reader.ReadUInt16();
                scriptFileID = reader.ReadUInt16();
                levelScriptID = reader.ReadUInt16();
                textArchiveID = reader.ReadUInt16();
                musicDayID = reader.ReadUInt16();
                musicNightID = reader.ReadUInt16();
                wildPokemon = reader.ReadUInt16();
                eventFileID = reader.ReadUInt16();
                locationName = reader.ReadUInt16();
                weatherID = reader.ReadByte();
                cameraAngleID = reader.ReadByte();
                locationSpecifier = reader.ReadByte();

                byte mapSettings = reader.ReadByte();
                battleBackground = (byte)(mapSettings & 0b_1111);
                flags = (byte)(mapSettings >> 4 & 0b_1111);
            }
        }
        #endregion Constructors

        #region Methods (1)
        public override byte[] ToByteArray() {
            MemoryStream newData = new MemoryStream();
            using (BinaryWriter writer = new BinaryWriter(newData)) {
                writer.Write(areaDataID);
                writer.Write(unknown1);
                writer.Write(matrixID);
                writer.Write(scriptFileID);
                writer.Write(levelScriptID);
                writer.Write(textArchiveID);
                writer.Write(musicDayID);
                writer.Write(musicNightID);
                writer.Write(wildPokemon);
                writer.Write(eventFileID);
                writer.Write(locationName);
                writer.Write(weatherID);
                writer.Write(cameraAngleID);
                writer.Write(locationSpecifier);

                byte mapSettings = (byte)((battleBackground & 0b_1111) + ((flags & 0b_1111) << 4));
                writer.Write(mapSettings);
            }
            return newData.ToArray();
        }
        #endregion
    }

    /// <summary>
    /// Class to store map header data from Pokémon Plat
    /// </summary>
    public class HeaderPt : MapHeader {
        #region Fields (5)
        public byte areaIcon { get; set; }
        public byte locationName { get; set; }
        public byte unknown1 { get; set; }
        #endregion Fields

        #region Constructors (1)
        public HeaderPt(ushort headerNumber, Stream data) {
            this.ID = headerNumber;
            using (BinaryReader reader = new BinaryReader(data)) {
                try {
                    areaDataID = reader.ReadByte();
                    unknown1 = reader.ReadByte();
                    matrixID = reader.ReadUInt16();
                    scriptFileID = reader.ReadUInt16();
                    levelScriptID = reader.ReadUInt16();
                    textArchiveID = reader.ReadUInt16();
                    musicDayID = reader.ReadUInt16();
                    musicNightID = reader.ReadUInt16();
                    wildPokemon = reader.ReadUInt16();
                    eventFileID = reader.ReadUInt16();
                    locationName = reader.ReadByte();
                    areaIcon = reader.ReadByte();
                    weatherID = reader.ReadByte();
                    cameraAngleID = reader.ReadByte();

                    ushort mapSettings = reader.ReadUInt16();
                    locationSpecifier = (byte)(mapSettings & 0b_1111_111);
                    battleBackground = (byte)(mapSettings >> 7 & 0b_1111_1);
                    flags = (byte)(mapSettings >> 12 & 0b_1111);

                } catch (EndOfStreamException) {
                    MessageBox.Show("Error loading header " + ID + '.', "Unexpected EOF", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
        #endregion Constructors

        #region Methods(1)
        public override byte[] ToByteArray() {
            MemoryStream newData = new MemoryStream();
            using (BinaryWriter writer = new BinaryWriter(newData)) {
                writer.Write(areaDataID);
                writer.Write(unknown1);
                writer.Write(matrixID);
                writer.Write(scriptFileID);
                writer.Write(levelScriptID);
                writer.Write(textArchiveID);
                writer.Write(musicDayID);
                writer.Write(musicNightID);
                writer.Write(wildPokemon);
                writer.Write(eventFileID);
                writer.Write(locationName);
                writer.Write(areaIcon);
                writer.Write(weatherID);
                writer.Write(cameraAngleID);

                ushort mapSettings = (ushort)((locationSpecifier & 0b_1111_111) + ((battleBackground & 0b_1111_1) << 7) + ((flags & 0b_1111) << 12));
                writer.Write(mapSettings);
            }
            return newData.ToArray();
        }
        #endregion
    }

    /// <summary>
    /// Class to store map header data from Pokémon HG and SS
    /// </summary>
    public class HeaderHGSS : MapHeader {
        #region Fields (7)
        public byte areaIcon { get; set; }
        public byte followMode { get; set; }
        public byte locationName { get; set; }
        public byte locationType { get; set; }  //4 bits only
        public byte unknown0 { get; set; } //4 bits only
        public byte unknown1 { get; set; } //4 bits only
        public byte worldmapX { get; set; } //6 bits only
        public byte worldmapY { get; set; } //6 bits only
        public bool kantoFlag { get; set; }
        #endregion

        #region Constructors (1)
        public HeaderHGSS(ushort headerNumber, Stream data) {
            this.ID = headerNumber;
            using (BinaryReader reader = new BinaryReader(data)) {
                try {
                    wildPokemon = reader.ReadByte();
                    areaDataID = reader.ReadByte();

                    ushort coords = reader.ReadUInt16();
                    unknown0 = (byte)(coords & 0b_1111); //get 4 bits
                    worldmapX = (byte)((coords >> 4) & 0b_1111_11); //get 6 bits after the first 4
                    worldmapY = (byte)((coords >> 10) & 0b_1111_11); //get 6 bits after the first 10

                    matrixID = reader.ReadUInt16();
                    scriptFileID = reader.ReadUInt16();
                    levelScriptID = reader.ReadUInt16();
                    textArchiveID = reader.ReadUInt16();
                    musicDayID = reader.ReadUInt16();
                    musicNightID = reader.ReadUInt16();
                    eventFileID = reader.ReadUInt16();
                    locationName = reader.ReadByte();
                    
                    byte areaProperties = reader.ReadByte();
                    areaIcon = (byte)(areaProperties & 0b_1111); //get 4 bits
                    unknown1 = (byte)((areaProperties >> 4) & 0b_1111); //get 4 bits after the first 4

                    uint last32 = reader.ReadUInt32();
                    kantoFlag = (last32 & 0b_1) == 1; //get 1 bit
                    weatherID = (byte)((last32 >> 1) & 0b_1111_111); //get 7 bits after the first one

                    locationType = (byte)((last32 >> 8) & 0b_1111); //get 4 bits after the first 8
                    cameraAngleID = (byte)((last32 >> 12) & 0b_1111_11); //get 6 bits after the first 12
                    followMode = (byte)((last32 >> 18) & 0b_11); //get 2 bits after the first 17
                    battleBackground = (byte)((last32 >> 20) & 0b_1111_1); //get 5 bits after the first 19
                    flags = (byte)(last32 >> 25 & 0b_1111_111); //get 7 bits after the first 24

                } catch (EndOfStreamException) {
                    MessageBox.Show("Error loading header " + ID + '.', "Unexpected EOF", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    ID = ushort.MaxValue;
                }
            }
        }
        #endregion Constructors

        #region Methods(1)
        public override byte[] ToByteArray() {
            MemoryStream newData = new MemoryStream();
            using (BinaryWriter writer = new BinaryWriter(newData)) {
                writer.Write((byte)wildPokemon);
                writer.Write(areaDataID);

                ushort worldMapCoordinates = (ushort)((unknown0 & 0b_1111) + ((worldmapX & 0b_1111_11) << 4) + ((worldmapY & 0b_1111_11) << 10));
                writer.Write(worldMapCoordinates);

                writer.Write(matrixID);
                writer.Write(scriptFileID);
                writer.Write(levelScriptID);
                writer.Write(textArchiveID);
                writer.Write(musicDayID);
                writer.Write(musicNightID);
                writer.Write(eventFileID);
                writer.Write(locationName);

                byte areaProperties = (byte)((areaIcon & 0b_1111) + ((unknown1 & 0b_1111) << 4));
                writer.Write(areaProperties);

                uint last32 = (uint)(((weatherID & 0b_1111_111) << 1) +
                    ((locationType & 0b_1111) << 8) +
                    ((cameraAngleID & 0b_1111_1) << 12) +
                    ((followMode & 0b_11) << 18) +
                    ((battleBackground & 0b_1111_1) << 20) +
                    ((flags & 0b_1111_111) << 25));

                if (kantoFlag) {
                    last32++;
                }

                writer.Write(last32);
            }
            return newData.ToArray();
        }
        #endregion
    }
}