using System;

namespace PKHeX.Core
{
    public abstract class G4PKM : PKM,
        IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetUnique3, IRibbonSetUnique4, IRibbonSetCommon3, IRibbonSetCommon4,
        IContestStats, IContestStatsMutable, IGroundTile
    {
        protected G4PKM(byte[] data) : base(data) { }
        protected G4PKM(int size) : base(size) { }

        // Maximums
        public sealed override int MaxMoveID => Legal.MaxMoveID_4;
        public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_4;
        public sealed override int MaxAbilityID => Legal.MaxAbilityID_4;
        public sealed override int MaxItemID => Legal.MaxItemID_4_HGSS;
        public sealed override int MaxBallID => Legal.MaxBallID_4;
        public sealed override int MaxGameID => Legal.MaxGameID_4;
        public sealed override int MaxIV => 31;
        public sealed override int MaxEV => 255;
        public sealed override int OTLength => 7;
        public sealed override int NickLength => 10;

        public sealed override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 3);
        public sealed override int TSV => (TID ^ SID) >> 3;

        protected bool PtHGSS => Pt || HGSS;

        public sealed override int Characteristic
        {
            get
            {
                int pm6 = (int)(EncryptionConstant % 6); // PID
                int maxIV = MaximumIV;
                int pm6stat = 0;
                for (int i = 0; i < 6; i++)
                {
                    pm6stat = (pm6 + i) % 6;
                    if (GetIV(pm6stat) == maxIV)
                        break;
                }
                return (pm6stat * 5) + (maxIV % 5);
            }
        }

        public abstract ushort Sanity { get; set; }
        public abstract ushort Checksum { get; set; }
        public sealed override void RefreshChecksum() => Checksum = CalculateChecksum();
        public sealed override bool ChecksumValid => CalculateChecksum() == Checksum;
        public override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } }
        protected virtual ushort CalculateChecksum() => PokeCrypto.GetCHK(Data, PokeCrypto.SIZE_4STORED);

        // Trash Bytes
        public sealed override Span<byte> Nickname_Trash => Data.AsSpan(0x48, 22);
        public sealed override Span<byte> OT_Trash => Data.AsSpan(0x68, 16);

        // Future Attributes
        public sealed override uint EncryptionConstant { get => PID; set { } }
        public sealed override int Nature { get => (int)(PID % 25); set { } }
        public sealed override int CurrentFriendship { get => OT_Friendship; set => OT_Friendship = value; }
        public sealed override int CurrentHandler { get => 0; set { } }
        public sealed override int AbilityNumber { get => 1 << PIDAbility; set { } }

        public abstract int ShinyLeaf { get; set; }

        #region Ribbons
        public abstract bool RibbonEarth { get; set; }
        public abstract bool RibbonNational { get; set; }
        public abstract bool RibbonCountry { get; set; }
        public abstract bool RibbonChampionBattle { get; set; }
        public abstract bool RibbonChampionRegional { get; set; }
        public abstract bool RibbonChampionNational { get; set; }
        public abstract bool RibbonClassic { get; set; }
        public abstract bool RibbonWishing { get; set; }
        public abstract bool RibbonPremier { get; set; }
        public abstract bool RibbonEvent { get; set; }
        public abstract bool RibbonBirthday { get; set; }
        public abstract bool RibbonSpecial { get; set; }
        public abstract bool RibbonWorld { get; set; }
        public abstract bool RibbonChampionWorld { get; set; }
        public abstract bool RibbonSouvenir { get; set; }
        public abstract bool RibbonWinning { get; set; }
        public abstract bool RibbonVictory { get; set; }
        public abstract bool RibbonAbility { get; set; }
        public abstract bool RibbonAbilityGreat { get; set; }
        public abstract bool RibbonAbilityDouble { get; set; }
        public abstract bool RibbonAbilityMulti { get; set; }
        public abstract bool RibbonAbilityPair { get; set; }
        public abstract bool RibbonAbilityWorld { get; set; }
        public abstract bool RibbonG3Cool { get; set; }
        public abstract bool RibbonG3CoolSuper { get; set; }
        public abstract bool RibbonG3CoolHyper { get; set; }
        public abstract bool RibbonG3CoolMaster { get; set; }
        public abstract bool RibbonG3Beauty { get; set; }
        public abstract bool RibbonG3BeautySuper { get; set; }
        public abstract bool RibbonG3BeautyHyper { get; set; }
        public abstract bool RibbonG3BeautyMaster { get; set; }
        public abstract bool RibbonG3Cute { get; set; }
        public abstract bool RibbonG3CuteSuper { get; set; }
        public abstract bool RibbonG3CuteHyper { get; set; }
        public abstract bool RibbonG3CuteMaster { get; set; }
        public abstract bool RibbonG3Smart { get; set; }
        public abstract bool RibbonG3SmartSuper { get; set; }
        public abstract bool RibbonG3SmartHyper { get; set; }
        public abstract bool RibbonG3SmartMaster { get; set; }
        public abstract bool RibbonG3Tough { get; set; }
        public abstract bool RibbonG3ToughSuper { get; set; }
        public abstract bool RibbonG3ToughHyper { get; set; }
        public abstract bool RibbonG3ToughMaster { get; set; }
        public abstract bool RibbonG4Cool { get; set; }
        public abstract bool RibbonG4CoolGreat { get; set; }
        public abstract bool RibbonG4CoolUltra { get; set; }
        public abstract bool RibbonG4CoolMaster { get; set; }
        public abstract bool RibbonG4Beauty { get; set; }
        public abstract bool RibbonG4BeautyGreat { get; set; }
        public abstract bool RibbonG4BeautyUltra { get; set; }
        public abstract bool RibbonG4BeautyMaster { get; set; }
        public abstract bool RibbonG4Cute { get; set; }
        public abstract bool RibbonG4CuteGreat { get; set; }
        public abstract bool RibbonG4CuteUltra { get; set; }
        public abstract bool RibbonG4CuteMaster { get; set; }
        public abstract bool RibbonG4Smart { get; set; }
        public abstract bool RibbonG4SmartGreat { get; set; }
        public abstract bool RibbonG4SmartUltra { get; set; }
        public abstract bool RibbonG4SmartMaster { get; set; }
        public abstract bool RibbonG4Tough { get; set; }
        public abstract bool RibbonG4ToughGreat { get; set; }
        public abstract bool RibbonG4ToughUltra { get; set; }
        public abstract bool RibbonG4ToughMaster { get; set; }
        public abstract bool RibbonChampionG3 { get; set; }
        public abstract bool RibbonArtist { get; set; }
        public abstract bool RibbonEffort { get; set; }
        public abstract bool RibbonChampionSinnoh { get; set; }
        public abstract bool RibbonAlert { get; set; }
        public abstract bool RibbonShock { get; set; }
        public abstract bool RibbonDowncast { get; set; }
        public abstract bool RibbonCareless { get; set; }
        public abstract bool RibbonRelax { get; set; }
        public abstract bool RibbonSnooze { get; set; }
        public abstract bool RibbonSmile { get; set; }
        public abstract bool RibbonGorgeous { get; set; }
        public abstract bool RibbonRoyal { get; set; }
        public abstract bool RibbonGorgeousRoyal { get; set; }
        public abstract bool RibbonFootprint { get; set; }
        public abstract bool RibbonRecord { get; set; }
        public abstract bool RibbonLegend { get; set; }

        // Unused
        public abstract bool RIB3_4 { get; set; }
        public abstract bool RIB3_5 { get; set; }
        public abstract bool RIB3_6 { get; set; }
        public abstract bool RIB3_7 { get; set; }
        public abstract bool RIBA_4 { get; set; }
        public abstract bool RIBA_5 { get; set; }
        public abstract bool RIBA_6 { get; set; }
        public abstract bool RIBA_7 { get; set; }
        public abstract bool RIBB_0 { get; set; }
        public abstract bool RIBB_1 { get; set; }
        public abstract bool RIBB_2 { get; set; }
        public abstract bool RIBB_3 { get; set; }
        public abstract bool RIBB_4 { get; set; }
        public abstract bool RIBB_5 { get; set; }
        public abstract bool RIBB_6 { get; set; }
        public abstract bool RIBB_7 { get; set; }
        #endregion

        public abstract byte CNT_Cool { get; set; }
        public abstract byte CNT_Beauty { get; set; }
        public abstract byte CNT_Cute { get; set; }
        public abstract byte CNT_Smart { get; set; }
        public abstract byte CNT_Tough { get; set; }
        public abstract byte CNT_Sheen { get; set; }

        public abstract GroundTileType GroundTile { get; set; }
        public abstract byte BallDPPt { get; set; }
        public abstract byte BallHGSS { get; set; }
        public abstract byte PokéathlonStat { get; set; }

        public abstract ushort Egg_LocationDP { get; set; }
        public abstract ushort Egg_LocationExtended { get; set; }
        public abstract ushort Met_LocationDP { get; set; }
        public abstract ushort Met_LocationExtended { get; set; }

        public sealed override int Egg_Location
        {
            get
            {
                ushort hgssloc = Egg_LocationExtended;
                if (hgssloc != 0)
                    return hgssloc;
                return Egg_LocationDP;
            }
            set
            {
                if (value == 0)
                {
                    Egg_LocationDP = Egg_LocationExtended = 0;
                }
                else if (Locations.IsPtHGSSLocation(value) || Locations.IsPtHGSSLocationEgg(value))
                {
                    // Met location not in DP, set to Faraway Place
                    Egg_LocationDP = Locations.Faraway4;
                    Egg_LocationExtended = (ushort)value;
                }
                else
                {
                    int pthgss = PtHGSS ? value : 0; // only set to PtHGSS loc if encountered in game
                    Egg_LocationDP = (ushort)value;
                    Egg_LocationExtended = (ushort)pthgss;
                }
            }
        }

        public sealed override int Met_Location
        {
            get
            {
                ushort hgssloc = Met_LocationExtended;
                if (hgssloc != 0)
                    return hgssloc;
                return Met_LocationDP;
            }
            set
            {
                if (value == 0)
                {
                    Met_LocationDP = Met_LocationExtended = 0;
                }
                else if (Locations.IsPtHGSSLocation(value) || Locations.IsPtHGSSLocationEgg(value))
                {
                    // Met location not in DP, set to Faraway Place
                    Met_LocationDP = Locations.Faraway4;
                    Met_LocationExtended = (ushort)value;
                }
                else
                {
                    int pthgss = PtHGSS ? value : 0; // only set to PtHGSS loc if encountered in game
                    Met_LocationDP = (ushort)value;
                    Met_LocationExtended = (ushort)pthgss;
                }
            }
        }

        public sealed override int Ball
        {
            // HG/SS added new ball IDs mid-generation, and the previous Gen4 games couldn't handle invalid ball values.
            // Pokémon obtained in HG/SS have the HG/SS ball value set (@0x86) to the capture ball.
            // However, this info is not set in event gift data!
            // Event gift data contains a pre-formatted PK4 template, which is slightly mutated.
            // No HG/SS ball values were used in these event gifts, and no HG/SS ball values are set (0).

            // To get the display ball (assume HG/SS +), return the higher of the two values.
            get => Math.Max(BallHGSS, BallDPPt);
            set
            {
                static byte Clamp(int value, Ball max) => (uint)value <= (uint)max ? (byte)value : (byte)Core.Ball.Poke;

                // Ball to display in DPPt
                BallDPPt = Clamp(value, Core.Ball.Cherish);

                // Only set the HG/SS value if it originated in HG/SS and was not an event.
                if (!HGSS || FatefulEncounter)
                    BallHGSS = 0;
                else
                    BallHGSS = Clamp(value, Core.Ball.Sport);
            }
        }

        // Synthetic Trading Logic
        public bool Trade(string SAV_Trainer, int SAV_TID, int SAV_SID, int SAV_GENDER, int Day = 1, int Month = 1, int Year = 2009)
        {
            // Eggs do not have any modifications done if they are traded
            if (IsEgg && !(SAV_Trainer == OT_Name && SAV_TID == TID && SAV_SID == SID && SAV_GENDER == OT_Gender))
            {
                SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade4);
                return true;
            }
            return false;
        }

        protected T ConvertTo<T>() where T : G4PKM, new()
        {
            var pk = new T
            {
                PID = PID,
                Species = Species,
                HeldItem = HeldItem,
                TID = TID,
                SID = SID,
                EXP = EXP,
                OT_Friendship = OT_Friendship,
                Ability = Ability,
                Language = Language,

                IsEgg = IsEgg,
                IsNicknamed = IsNicknamed,
                OT_Gender = OT_Gender,

                IV_HP = IV_HP,
                IV_ATK = IV_ATK,
                IV_DEF = IV_DEF,
                IV_SPE = IV_SPE,
                IV_SPA = IV_SPA,
                IV_SPD = IV_SPD,
                EV_HP = EV_HP,
                EV_ATK = EV_ATK,
                EV_DEF = EV_DEF,
                EV_SPE = EV_SPE,
                EV_SPA = EV_SPA,
                EV_SPD = EV_SPD,
                CNT_Cool = CNT_Cool,
                CNT_Beauty = CNT_Beauty,
                CNT_Cute = CNT_Cute,
                CNT_Smart = CNT_Smart,
                CNT_Tough = CNT_Tough,
                CNT_Sheen = CNT_Sheen,

                Move1 = Move1,
                Move2 = Move2,
                Move3 = Move3,
                Move4 = Move4,
                Move1_PP = Move1_PP,
                Move2_PP = Move2_PP,
                Move3_PP = Move3_PP,
                Move4_PP = Move4_PP,
                Move1_PPUps = Move1_PPUps,
                Move2_PPUps = Move2_PPUps,
                Move3_PPUps = Move3_PPUps,
                Move4_PPUps = Move4_PPUps,

                Gender = Gender,
                Form = Form,
                ShinyLeaf = ShinyLeaf,
                Version = Version,
                PKRS_Days = PKRS_Days,
                PKRS_Strain = PKRS_Strain,
                BallDPPt = BallDPPt,
                BallHGSS = BallHGSS,
                GroundTile = GroundTile,
                PokéathlonStat = PokéathlonStat,
                FatefulEncounter = FatefulEncounter,

                Met_Level = Met_Level,
                Met_Location = Met_Location,
                Met_Year = Met_Year,
                Met_Month = Met_Month,
                Met_Day = Met_Day,

                Egg_Location = Egg_Location,
                Egg_Year = Egg_Year,
                Egg_Month = Egg_Month,
                Egg_Day = Egg_Day,

                #region Ribbons
                RibbonChampionSinnoh = RibbonChampionSinnoh,
                RibbonAbility = RibbonAbility,
                RibbonAbilityGreat = RibbonAbilityGreat,
                RibbonAbilityDouble = RibbonAbilityDouble,
                RibbonAbilityMulti = RibbonAbilityMulti,
                RibbonAbilityPair = RibbonAbilityPair,
                RibbonAbilityWorld = RibbonAbilityWorld,
                RibbonAlert = RibbonAlert,
                RibbonShock = RibbonShock,
                RibbonDowncast = RibbonDowncast,
                RibbonCareless = RibbonCareless,
                RibbonRelax = RibbonRelax,
                RibbonSnooze = RibbonSnooze,
                RibbonSmile = RibbonSmile,
                RibbonGorgeous = RibbonGorgeous,
                RibbonRoyal = RibbonRoyal,
                RibbonGorgeousRoyal = RibbonGorgeousRoyal,
                RibbonFootprint = RibbonFootprint,
                RibbonRecord = RibbonRecord,
                RibbonEvent = RibbonEvent,
                RibbonLegend = RibbonLegend,
                RibbonChampionWorld = RibbonChampionWorld,
                RibbonBirthday = RibbonBirthday,
                RibbonSpecial = RibbonSpecial,
                RibbonSouvenir = RibbonSouvenir,
                RibbonWishing = RibbonWishing,
                RibbonClassic = RibbonClassic,
                RibbonPremier = RibbonPremier,
                RibbonG3Cool = RibbonG3Cool,
                RibbonG3CoolSuper = RibbonG3CoolSuper,
                RibbonG3CoolHyper = RibbonG3CoolHyper,
                RibbonG3CoolMaster = RibbonG3CoolMaster,
                RibbonG3Beauty = RibbonG3Beauty,
                RibbonG3BeautySuper = RibbonG3BeautySuper,
                RibbonG3BeautyHyper = RibbonG3BeautyHyper,
                RibbonG3BeautyMaster = RibbonG3BeautyMaster,
                RibbonG3Cute = RibbonG3Cute,
                RibbonG3CuteSuper = RibbonG3CuteSuper,
                RibbonG3CuteHyper = RibbonG3CuteHyper,
                RibbonG3CuteMaster = RibbonG3CuteMaster,
                RibbonG3Smart = RibbonG3Smart,
                RibbonG3SmartSuper = RibbonG3SmartSuper,
                RibbonG3SmartHyper = RibbonG3SmartHyper,
                RibbonG3SmartMaster = RibbonG3SmartMaster,
                RibbonG3Tough = RibbonG3Tough,
                RibbonG3ToughSuper = RibbonG3ToughSuper,
                RibbonG3ToughHyper = RibbonG3ToughHyper,
                RibbonG3ToughMaster = RibbonG3ToughMaster,
                RibbonChampionG3 = RibbonChampionG3,
                RibbonWinning = RibbonWinning,
                RibbonVictory = RibbonVictory,
                RibbonArtist = RibbonArtist,
                RibbonEffort = RibbonEffort,
                RibbonChampionBattle = RibbonChampionBattle,
                RibbonChampionRegional = RibbonChampionRegional,
                RibbonChampionNational = RibbonChampionNational,
                RibbonCountry = RibbonCountry,
                RibbonNational = RibbonNational,
                RibbonEarth = RibbonEarth,
                RibbonWorld = RibbonWorld,
                RibbonG4Cool = RibbonG4Cool,
                RibbonG4CoolGreat = RibbonG4CoolGreat,
                RibbonG4CoolUltra = RibbonG4CoolUltra,
                RibbonG4CoolMaster = RibbonG4CoolMaster,
                RibbonG4Beauty = RibbonG4Beauty,
                RibbonG4BeautyGreat = RibbonG4BeautyGreat,
                RibbonG4BeautyUltra = RibbonG4BeautyUltra,
                RibbonG4BeautyMaster = RibbonG4BeautyMaster,
                RibbonG4Cute = RibbonG4Cute,
                RibbonG4CuteGreat = RibbonG4CuteGreat,
                RibbonG4CuteUltra = RibbonG4CuteUltra,
                RibbonG4CuteMaster = RibbonG4CuteMaster,
                RibbonG4Smart = RibbonG4Smart,
                RibbonG4SmartGreat = RibbonG4SmartGreat,
                RibbonG4SmartUltra = RibbonG4SmartUltra,
                RibbonG4SmartMaster = RibbonG4SmartMaster,
                RibbonG4Tough = RibbonG4Tough,
                RibbonG4ToughGreat = RibbonG4ToughGreat,
                RibbonG4ToughUltra = RibbonG4ToughUltra,
                RibbonG4ToughMaster = RibbonG4ToughMaster,
                RIB3_4 = RIB3_4,
                RIB3_5 = RIB3_5,
                RIB3_6 = RIB3_6,
                RIB3_7 = RIB3_7,
                RIBA_4 = RIBA_4,
                RIBA_5 = RIBA_5,
                RIBA_6 = RIBA_6,
                RIBA_7 = RIBA_7,
                RIBB_0 = RIBB_0,
                RIBB_1 = RIBB_1,
                RIBB_2 = RIBB_2,
                RIBB_3 = RIBB_3,
                RIBB_4 = RIBB_4,
                RIBB_5 = RIBB_5,
                RIBB_6 = RIBB_6,
                RIBB_7 = RIBB_7,
                #endregion
            };

            // Transfer Trash Bytes
            for (int i = 0; i < 11; i++) // Nickname
            {
                pk.Data[0x48 + (2 * i)] = Data[0x48 + (2 * i) + 1];
                pk.Data[0x48 + (2 * i) + 1] = Data[0x48 + (2 * i)];
            }
            for (int i = 0; i < 8; i++) // OT_Name
            {
                pk.Data[0x68 + (2 * i)] = Data[0x68 + (2 * i) + 1];
                pk.Data[0x68 + (2 * i) + 1] = Data[0x68 + (2 * i)];
            }
            return pk;
        }
    }
}
