using System;
using System.Collections;
using System.IO;
using System.Windows.Forms;

namespace DSPRE.ROMFiles {
    public class PartyPokemon : RomFile {
        #region Fields
        public ushort? pokeID = null;
        public ushort level = 0;
        public ushort unknown1_DATASTART = 0;
        public ushort unknown2_DATAEND = 0;

        public ushort? heldItem = null;
        public ushort[] moves = null;
        #endregion

        #region Constructor
        public PartyPokemon(bool hasItems = false, bool hasMoves = false) {
            UpdateItemsAndMoves(hasItems, hasMoves);
        }

        public PartyPokemon(ushort Unk1, ushort Level, ushort Pokemon, ushort Unk2, ushort? heldItem = null, ushort[] moves = null) {
            pokeID = Pokemon;
            level = Level;
            unknown1_DATASTART = Unk1;
            unknown2_DATAEND = Unk2;
            this.heldItem = heldItem;
            this.moves = moves;
        }
        public override byte[] ToByteArray() {
            MemoryStream newData = new MemoryStream();
            using (BinaryWriter writer = new BinaryWriter(newData)) {
                writer.Write(unknown1_DATASTART);
                writer.Write(level);
                writer.Write(pokeID ?? 0);

                if (heldItem != null) {
                    writer.Write((ushort)heldItem);
                }

                if (moves != null) {
                    foreach (ushort move in moves) {
                        writer.Write(move);
                    }
                }
                writer.Write(unknown2_DATAEND);
            }
            return newData.ToArray();
        }
        public void UpdateItemsAndMoves(bool hasItems = false, bool hasMoves = false) {
            if (hasItems) {
                this.heldItem = 0;
            }
            if (hasMoves) {
                this.moves = new ushort[4];
            }
        }

        public override string ToString() {
            return CheckEmpty() ? "Empty" : this.pokeID + " Lv. " + this.level;
        }
        public bool CheckEmpty () {
            return this is null || pokeID is null || level <= 0;
        }
        #endregion
    }

    public class TrainerProperties : RomFile {
        public const int AI_COUNT = 11;
        public const int TRAINER_ITEMS = 4;

        #region Fields
        public ushort trainerID;
        public byte trDataUnknown;

        public byte trainerClass = 0;
        public byte partyCount = 0;

        public bool doubleBattle = false;
        public bool hasMoves = false;
        public bool hasItems = false;

        public ushort[] trainerItems = new ushort[TRAINER_ITEMS];
        public BitArray AI;
        #endregion

        #region Constructor
        public TrainerProperties(ushort ID, byte partyCount = 0) {
            trainerID = ID;
            trainerItems = new ushort[TRAINER_ITEMS];
            AI = new BitArray( new bool[AI_COUNT] { true, false, false, false, false, false, false, false, false, false, false } );
            trDataUnknown = 0;
        }
        public TrainerProperties(ushort ID, Stream trainerPropertiesStream) {
            trainerID = ID;
            using (BinaryReader reader = new BinaryReader(trainerPropertiesStream)) {
                byte flags = reader.ReadByte();
                hasMoves = (flags & 1) != 0;
                hasItems = (flags & 2) != 0;

                trainerClass = reader.ReadByte();
                trDataUnknown = reader.ReadByte();
                partyCount = reader.ReadByte();

                for (int i = 0; i < trainerItems.Length; i++) {
                    trainerItems[i] = reader.ReadUInt16();
                }

                AI = new BitArray( BitConverter.GetBytes(reader.ReadUInt32()) );
                doubleBattle = reader.ReadUInt32() == 2;
            }
        }
        #endregion

        #region Methods
        public override byte[] ToByteArray() {
            MemoryStream newData = new MemoryStream();
            using (BinaryWriter writer = new BinaryWriter(newData)) {
                byte flags = 0;
                flags |= (byte)(hasMoves ? 1 : 0);
                flags |= (byte)(hasItems ? 2 : 0);

                writer.Write(flags);
                writer.Write(trainerClass);
                writer.Write(trDataUnknown);
                writer.Write(partyCount);

                foreach (ushort trItem in trainerItems) {
                    writer.Write(trItem);
                }

                uint AIflags = 0;
                for (int i = 0; i < AI.Length; i++) {
                    if (AI[i]) {
                        AIflags |= (uint)1 << i;
                    }
                }

                writer.Write(AIflags);
                writer.Write((uint)(doubleBattle ? 2 : 0));
            }
            return newData.ToArray();
        }

        public void SaveToFileExplorePath(string suggestedFileName, bool showSuccessMessage = true) {
            SaveToFileExplorePath("Gen IV Trainer Properties", "trp", suggestedFileName, showSuccessMessage);
        }
        #endregion

    }

    public class Party : RomFile {
        private PartyPokemon[] content;
        private TrainerProperties trp;
        public bool exportCondensedData;

        public const int MOVES_PER_POKE = 4;
        public Party(int POKE_IN_PARTY, bool init, TrainerProperties trp) {
            this.trp = trp;
            this.content = new PartyPokemon[POKE_IN_PARTY];

            if (init) {
                for (int i = 0; i < content.Length; i++) {
                    this.content[i] = new PartyPokemon();
                }
            }
        }

        public Party(bool readFirstByte, int maxPoke, Stream partyData, TrainerProperties traipr) {
            using (BinaryReader reader = new BinaryReader(partyData)) {
                try {
                    this.trp = traipr;
                    if (readFirstByte) {
                        byte flags = reader.ReadByte();

                        trp.hasMoves = (flags & 1) != 0;
                        trp.hasItems = (flags & 2) != 0;
                        trp.partyCount = (byte)((flags & 28) >> 2);
                    }

                    int dividend = 8;

                    if (trp.hasMoves) {
                        dividend += Party.MOVES_PER_POKE * sizeof(ushort);
                    }
                    if (trp.hasItems) {
                        dividend += sizeof(ushort);
                    }

                    int endval = Math.Min((int)(partyData.Length - 1 / dividend), trp.partyCount);
                    this.content = new PartyPokemon[maxPoke];
                    for (int i = 0; i < endval; i++) {
                        ushort unknown1 = reader.ReadUInt16();
                        ushort level = reader.ReadUInt16();

                        //NOTE: The following bitwise 'AND' operation fixes TUBER JARED [PLAT] and all trainers who
                        //use pokemon with a special form --> ALL PARTY POKEMON WITH A SPECIAL FORM WILL LOSE IT
                        
                        //the way special forms are stored is

                        //U16 POKEMON
                        // 0 1 2 3   4 5 6 7    8 9 A B   C D E F
                        //BITS 6 - F --> pokemon ID
                        //BITS 0 - 5 --> form ID 
                        ushort pokemon = (ushort)(reader.ReadUInt16() & (ushort.MaxValue>>6)); 
                        
                        ushort? heldItem = null;
                        ushort[] moves = null;

                        if (trp.hasItems) {
                            heldItem = reader.ReadUInt16();
                        }
                        if (trp.hasMoves) {
                            moves = new ushort[MOVES_PER_POKE];
                            for (int m = 0; m < moves.Length; m++) {
                                ushort val = reader.ReadUInt16();
                                moves[m] = (ushort)(val == ushort.MaxValue ? 0 : val);
                            }
                        }

                        content[i] = new PartyPokemon(unknown1, level, pokemon, reader.ReadUInt16(), heldItem, moves);
                    }
                    for (int i = endval; i < maxPoke; i++) {
                        content[i] = new PartyPokemon();
                    };
                } catch (EndOfStreamException) {
                    MessageBox.Show("There was a problem reading the party data of this " + this.GetType().Name + ".", "Read Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        public PartyPokemon this[int index] {
            get {
                return content[index];
            }
            set {
                content[index] = value;
            }
        }
        public override string ToString() {
            if (this.content == null) {
                return "Empty";
            } else {
                string buffer = "";
                byte nonEmptyCtr = 0;
                foreach(PartyPokemon p in this.content) {
                    if (!p.CheckEmpty()) {
                        nonEmptyCtr++;
                    }
                }
                buffer += nonEmptyCtr + " Poke ";
                if (this.trp.hasMoves) {
                    buffer += ", moves ";
                }
                if (this.trp.hasItems) {
                    buffer += ", items ";
                }
                return buffer;
            }
        }
        public override byte[] ToByteArray() {
            MemoryStream newData = new MemoryStream();
            using (BinaryWriter writer = new BinaryWriter(newData)) {
                if (this.exportCondensedData && trp != null) {
                    byte condensedTrData = (byte)(((trp.hasMoves ? 1 : 0) & 0b_1) + (((trp.hasItems ? 1 : 0) & 0b_1) << 1) + ((trp.partyCount & 0b_1111_11) << 2));
                    writer.Write(condensedTrData);
                }

                foreach (PartyPokemon poke in this.content) {
                    if (!poke.CheckEmpty()) {
                        writer.Write(poke.ToByteArray());
                    }
                }
            }
            return newData.ToArray();
        }
        public void SaveToFileExplorePath(string suggestedFileName, bool showSuccessMessage = true) {
            SaveToFileExplorePath("Gen IV Party Data", "pdat", suggestedFileName, showSuccessMessage);
        }
    }
    public class TrainerFile : RomFile {
        public const int POKE_IN_PARTY = 6;
        public static readonly string NAME_NOT_FOUND = "NAME READ ERROR";

        #region Fields
        public string name;
        public TrainerProperties trp;
        public Party party;
        #endregion

        #region Constructor
        public TrainerFile(TrainerProperties trp, string name = ""){
            this.name = name;
            this.trp = trp;
            trp.partyCount = 1;
            this.party = new Party(1, init: true, trp);
        }
        public TrainerFile(TrainerProperties trp, Stream partyData, string name = "") {
            this.name = name;
            this.trp = trp;
            party = new Party(readFirstByte: false, POKE_IN_PARTY, partyData, this.trp);
        }
        #endregion

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

                byte[] trDat = trp.ToByteArray();
                writer.Write((byte)trDat.Length);
                writer.Write(trDat);

                byte[] pDat = party.ToByteArray();
                writer.Write((byte)pDat.Length);
                writer.Write(pDat);
            }
            return newData.ToArray();
        }

        public void SaveToFileExplorePath(string suggestedFileName, bool showSuccessMessage = true) {
            SaveToFileExplorePath("Gen IV Trainer File", "trf", suggestedFileName, showSuccessMessage);
        }
        #endregion

    }

}