using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using static System.Buffers.Binary.BinaryPrimitives;

namespace PKHeX.Core;

/// <summary> Generation 7 <see cref="PKM"/> format used for <see cref="GameVersion.GG"/>. </summary>
public sealed class PB7 : G6PKM, IHyperTrain, IAwakened, IScaledSizeValue, ICombatPower, IFavorite,
    IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetMemory6, IRibbonSetCommon7, IRibbonSetRibbons,
    IFormArgument, IAppliedMarkings7, IFullnessEnjoyment
{
    public override ReadOnlySpan<ushort> ExtraBytes =>
    [
        0x2A, // Old Marking Value (ResortEventStatus)
        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, // Unused Ribbons
        0x58, 0x59, // Nickname Terminator
        0x73,
        0x90, 0x91, // HT Terminator
        0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, // Old Geolocation/memories
        0xA7, 0xAA, 0xAB,
        0xC8, 0xC9, // OT Terminator
    ];

    public override int SIZE_PARTY => SIZE;
    public override int SIZE_STORED => SIZE;
    private const int SIZE = 260;
    public override EntityContext Context => EntityContext.Gen7b;
    public override PersonalInfo7GG PersonalInfo => PersonalTable.GG.GetFormEntry(Species, Form);

    public PB7() : base(SIZE) { }
    public PB7(Memory<byte> data) : base(DecryptParty(data)) { }

    private static Memory<byte> DecryptParty(Memory<byte> data)
    {
        PokeCrypto.DecryptIfEncrypted67(ref data);
        if (data.Length >= SIZE)
            return data;

        var result = new byte[SIZE];
        data.Span.CopyTo(result);
        return result;
    }

    public override PB7 Clone() => new(Data.ToArray());

    // Structure
    #region Block A
    public override uint EncryptionConstant
    {
        get => ReadUInt32LittleEndian(Data);
        set => WriteUInt32LittleEndian(Data, value);
    }

    public override ushort Sanity
    {
        get => ReadUInt16LittleEndian(Data[0x04..]);
        set => WriteUInt16LittleEndian(Data[0x04..], value);
    }

    public override ushort Checksum
    {
        get => ReadUInt16LittleEndian(Data[0x06..]);
        set => WriteUInt16LittleEndian(Data[0x06..], value);
    }

    public override ushort Species
    {
        get => ReadUInt16LittleEndian(Data[0x08..]);
        set => WriteUInt16LittleEndian(Data[0x08..], value);
    }

    public override int HeldItem
    {
        get => ReadUInt16LittleEndian(Data[0x0A..]);
        set => WriteUInt16LittleEndian(Data[0x0A..], (ushort)value);
    }

    public override uint ID32
    {
        get => ReadUInt32LittleEndian(Data[0x0C..]);
        set => WriteUInt32LittleEndian(Data[0x0C..], value);
    }

    public override ushort TID16
    {
        get => ReadUInt16LittleEndian(Data[0x0C..]);
        set => WriteUInt16LittleEndian(Data[0x0C..], value);
    }

    public override ushort SID16
    {
        get => ReadUInt16LittleEndian(Data[0x0E..]);
        set => WriteUInt16LittleEndian(Data[0x0E..], value);
    }

    public override uint EXP
    {
        get => ReadUInt32LittleEndian(Data[0x10..]);
        set => WriteUInt32LittleEndian(Data[0x10..], value);
    }

    public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; }
    public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); }
    public bool IsFavorite { get => (Data[0x15] & 8) != 0; set => Data[0x15] = (byte)((Data[0x15] & ~8) | ((value ? 1 : 0) << 3)); }
    public ushort MarkingValue { get => ReadUInt16LittleEndian(Data[0x16..]); set => WriteUInt16LittleEndian(Data[0x16..], value); }

    public override uint PID
    {
        get => ReadUInt32LittleEndian(Data[0x18..]);
        set => WriteUInt32LittleEndian(Data[0x18..], value);
    }

    public override Nature Nature { get => (Nature)Data[0x1C]; set => Data[0x1C] = (byte)value; }
    public override bool FatefulEncounter { get => (Data[0x1D] & 1) == 1; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x01) | (value ? 1 : 0)); }
    public override byte Gender { get => (byte)((Data[0x1D] >> 1) & 0x3); set => Data[0x1D] = (byte)((Data[0x1D] & ~0x06) | (value << 1)); }
    public override byte Form { get => (byte)(Data[0x1D] >> 3); set => Data[0x1D] = (byte)((Data[0x1D] & 0x07) | (value << 3)); }
    public override int EV_HP { get => Data[0x1E]; set => Data[0x1E] = (byte)value; }
    public override int EV_ATK { get => Data[0x1F]; set => Data[0x1F] = (byte)value; }
    public override int EV_DEF { get => Data[0x20]; set => Data[0x20] = (byte)value; }
    public override int EV_SPE { get => Data[0x21]; set => Data[0x21] = (byte)value; }
    public override int EV_SPA { get => Data[0x22]; set => Data[0x22] = (byte)value; }
    public override int EV_SPD { get => Data[0x23]; set => Data[0x23] = (byte)value; }
    public byte AV_HP  { get => Data[0x24]; set => Data[0x24] = value; }
    public byte AV_ATK { get => Data[0x25]; set => Data[0x25] = value; }
    public byte AV_DEF { get => Data[0x26]; set => Data[0x26] = value; }
    public byte AV_SPE { get => Data[0x27]; set => Data[0x27] = value; }
    public byte AV_SPA { get => Data[0x28]; set => Data[0x28] = value; }
    public byte AV_SPD { get => Data[0x29]; set => Data[0x29] = value; }
    public ResortEventState ResortEventStatus { get => (ResortEventState)Data[0x2A]; set => Data[0x2A] = (byte)value; }
    public byte PokerusState { get => Data[0x2B]; set => Data[0x2B] = value; }
    public override int PokerusDays { get => PokerusState & 0xF; set => PokerusState = (byte)((PokerusState & ~0xF) | value); }
    public override int PokerusStrain { get => PokerusState >> 4; set => PokerusState = (byte)((PokerusState & 0xF) | (value << 4)); }
    public float HeightAbsolute { get => ReadSingleLittleEndian(Data[0x2C..]); set => WriteSingleLittleEndian(Data[0x2C..], value); }
    private byte RIB0 { get => Data[0x30]; set => Data[0x30] = value; } // Ribbons are read as uints, but let's keep them per byte.
    private byte RIB1 { get => Data[0x31]; set => Data[0x31] = value; }
    private byte RIB2 { get => Data[0x32]; set => Data[0x32] = value; }
    private byte RIB3 { get => Data[0x33]; set => Data[0x33] = value; }
    private byte RIB4 { get => Data[0x34]; set => Data[0x34] = value; }
    private byte RIB5 { get => Data[0x35]; set => Data[0x35] = value; }
    private byte RIB6 { get => Data[0x36]; set => Data[0x36] = value; }
    //private byte RIB7 { get => Data[0x37]; set => Data[0x37] = value; } // Unused
    public bool RibbonChampionKalos         { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); }
    public bool RibbonChampionG3            { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); }
    public bool RibbonChampionSinnoh        { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); }
    public bool RibbonBestFriends           { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); }
    public bool RibbonTraining              { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); }
    public bool RibbonBattlerSkillful       { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); }
    public bool RibbonBattlerExpert         { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); }
    public bool RibbonEffort                { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); }
    public bool RibbonAlert                 { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); }
    public bool RibbonShock                 { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); }
    public bool RibbonDowncast              { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); }
    public bool RibbonCareless              { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); }
    public bool RibbonRelax                 { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); }
    public bool RibbonSnooze                { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); }
    public bool RibbonSmile                 { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); }
    public bool RibbonGorgeous              { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); }
    public bool RibbonRoyal                 { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); }
    public bool RibbonGorgeousRoyal         { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); }
    public bool RibbonArtist                { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); }
    public bool RibbonFootprint             { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); }
    public bool RibbonRecord                { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); }
    public bool RibbonLegend                { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); }
    public bool RibbonCountry               { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); }
    public bool RibbonNational              { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); }
    public bool RibbonEarth                 { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); }
    public bool RibbonWorld                 { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); }
    public bool RibbonClassic               { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); }
    public bool RibbonPremier               { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); }
    public bool RibbonEvent                 { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); }
    public bool RibbonBirthday              { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); }
    public bool RibbonSpecial               { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); }
    public bool RibbonSouvenir              { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); }
    public bool RibbonWishing               { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); }
    public bool RibbonChampionBattle        { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); }
    public bool RibbonChampionRegional      { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); }
    public bool RibbonChampionNational      { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); }
    public bool RibbonChampionWorld         { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); }
    public bool HasContestMemoryRibbon      { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); }
    public bool HasBattleMemoryRibbon       { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); }
    public bool RibbonChampionG6Hoenn       { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); }
    public bool RibbonContestStar           { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); }
    public bool RibbonMasterCoolness        { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); }
    public bool RibbonMasterBeauty          { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); }
    public bool RibbonMasterCuteness        { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); }
    public bool RibbonMasterCleverness      { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); }
    public bool RibbonMasterToughness       { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); }
    public bool RibbonChampionAlola         { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); }
    public bool RibbonBattleRoyale          { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); }
    public bool RibbonBattleTreeGreat       { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); }
    public bool RibbonBattleTreeMaster      { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); }
    public bool RIB6_2                      { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused
    public bool RIB6_3                      { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused
    public bool RIB6_4                      { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused
    public bool RIB6_5                      { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused
    public bool RIB6_6                      { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused
    public bool RIB6_7                      { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused
    public byte RibbonCountMemoryContest { get => Data[0x38]; set => Data[0x38] = value; }
    public byte RibbonCountMemoryBattle  { get => Data[0x39]; set => Data[0x39] = value; }

    // 0x38 Unused
    // 0x39 Unused
    public byte HeightScalar { get => Data[0x3A]; set => Data[0x3A] = value; }
    public byte WeightScalar { get => Data[0x3B]; set => Data[0x3B] = value; }
    public uint FormArgument { get => ReadUInt32LittleEndian(Data[0x3C..]); set => WriteUInt32LittleEndian(Data[0x3C..], value); }
    public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; }
    public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); }
    public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); }

    public int RibbonCount => BitOperations.PopCount(ReadUInt64LittleEndian(Data[0x30..]) & 0b00000000_00000011__11111111_11111111__11111111_11111111__11111111_11111111);

    #endregion
    #region Block B
    public override string Nickname
    {
        get => StringConverter8.GetString(NicknameTrash);
        set => StringConverter8.SetString(NicknameTrash, value, 12, StringConverterOption.None);
    }

    public override ushort Move1
    {
        get => ReadUInt16LittleEndian(Data[0x5A..]);
        set => WriteUInt16LittleEndian(Data[0x5A..], value);
    }

    public override ushort Move2
    {
        get => ReadUInt16LittleEndian(Data[0x5C..]);
        set => WriteUInt16LittleEndian(Data[0x5C..], value);
    }

    public override ushort Move3
    {
        get => ReadUInt16LittleEndian(Data[0x5E..]);
        set => WriteUInt16LittleEndian(Data[0x5E..], value);
    }

    public override ushort Move4
    {
        get => ReadUInt16LittleEndian(Data[0x60..]);
        set => WriteUInt16LittleEndian(Data[0x60..], value);
    }

    public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; }
    public override int Move2_PP { get => Data[0x63]; set => Data[0x63] = (byte)value; }
    public override int Move3_PP { get => Data[0x64]; set => Data[0x64] = (byte)value; }
    public override int Move4_PP { get => Data[0x65]; set => Data[0x65] = (byte)value; }
    public override int Move1_PPUps { get => Data[0x66]; set => Data[0x66] = (byte)value; }
    public override int Move2_PPUps { get => Data[0x67]; set => Data[0x67] = (byte)value; }
    public override int Move3_PPUps { get => Data[0x68]; set => Data[0x68] = (byte)value; }
    public override int Move4_PPUps { get => Data[0x69]; set => Data[0x69] = (byte)value; }

    public override ushort RelearnMove1
    {
        get => ReadUInt16LittleEndian(Data[0x6A..]);
        set => WriteUInt16LittleEndian(Data[0x6A..], value);
    }

    public override ushort RelearnMove2
    {
        get => ReadUInt16LittleEndian(Data[0x6C..]);
        set => WriteUInt16LittleEndian(Data[0x6C..], value);
    }

    public override ushort RelearnMove3
    {
        get => ReadUInt16LittleEndian(Data[0x6E..]);
        set => WriteUInt16LittleEndian(Data[0x6E..], value);
    }

    public override ushort RelearnMove4
    {
        get => ReadUInt16LittleEndian(Data[0x70..]);
        set => WriteUInt16LittleEndian(Data[0x70..], value);
    }

    // 0x72 Unused
    // 0x73 Unused
    public override uint IV32 { get => ReadUInt32LittleEndian(Data[0x74..]); set => WriteUInt32LittleEndian(Data[0x74..], value); }
    public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); }
    public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); }
    public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); }
    public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); }
    public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); }
    public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); }
    public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); }
    public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); }
    #endregion
    #region Block C
    public override string HandlingTrainerName
    {
        get => StringConverter8.GetString(HandlingTrainerTrash);
        set => StringConverter8.SetString(HandlingTrainerTrash, value, 12, StringConverterOption.None);
    }

    public override byte HandlingTrainerGender { get => Data[0x92]; set => Data[0x92] = value; }
    public override byte CurrentHandler { get => Data[0x93]; set => Data[0x93] = value; }
    // 0x94 Unused
    // 0x95 Unused
    // 0x96 Unused
    // 0x97 Unused
    // 0x98 Unused
    // 0x99 Unused
    // 0x9A Unused
    // 0x9B Unused
    // 0x9C Unused
    // 0x9D Unused
    // 0x9E Unused
    // 0x9F Unused
    // 0xA0 Unused
    // 0xA1 Unused
    public override byte HandlingTrainerFriendship { get => Data[0xA2]; set => Data[0xA2] = value; }
    // 0xA1 HandlingTrainerAffection Unused
    public byte HT_Intensity { get => Data[0xA4]; set => Data[0xA4] = value; }
    public byte HT_Memory { get => Data[0xA5]; set => Data[0xA5] = value; }
    public byte HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = value; }
    // 0xA7 Unused
    public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data[0xA8..]); set => WriteUInt16LittleEndian(Data[0xA8..], value); }
    // 0xAA Unused
    // 0xAB Unused
    public byte Spirit { get => Data[0xAC]; set => Data[0xAC] = value; }
    public byte Mood { get => Data[0xAD]; set => Data[0xAD] = value; }
    public byte Fullness { get => Data[0xAE]; set => Data[0xAE] = value; }
    public byte Enjoyment { get => Data[0xAF]; set => Data[0xAF] = value; }
    #endregion
    #region Block D
    public override string OriginalTrainerName
    {
        get => StringConverter8.GetString(OriginalTrainerTrash);
        set => StringConverter8.SetString(OriginalTrainerTrash, value, 12, StringConverterOption.None);
    }

    public override byte OriginalTrainerFriendship { get => Data[0xCA]; set => Data[0xCA] = value; }

    // Local time of the console when the Pokémon was received
    public byte ReceivedYear { get => Data[0xCB]; set => Data[0xCB] = value; }
    public byte ReceivedMonth { get => Data[0xCC]; set => Data[0xCC] = value; }
    public byte ReceivedDay { get => Data[0xCD]; set => Data[0xCD] = value; }
    public byte ReceivedHour { get => Data[0xCE]; set => Data[0xCE] = value; }
    public byte ReceivedMinute { get => Data[0xCF]; set => Data[0xCF] = value; }
    public byte ReceivedSecond { get => Data[0xD0]; set => Data[0xD0] = value; }
    public DateOnly? ReceivedDate
    {
        get
        {
            if (!DateUtil.IsValidDate(2000 + ReceivedYear, ReceivedMonth, ReceivedDay))
                return null;
            return new DateOnly(ReceivedYear + 2000, ReceivedMonth, ReceivedDay);
        }
        set
        {
            if (value is { } d)
            {
                // Only update the properties if a value is provided.
                ReceivedYear = (byte)(d.Year - 2000);
                ReceivedMonth = (byte)d.Month;
                ReceivedDay = (byte)d.Day;
            }
            else
            {
                // Clear the Date.
                // If code tries to access Date again, null will be returned.
                ReceivedYear = 0;
                ReceivedMonth = 0;
                ReceivedDay = 0;
            }
        }
    }

    public TimeOnly? ReceivedTime
    {
        get
        {
            if (!DateUtil.IsValidTime(ReceivedHour, ReceivedMinute, ReceivedSecond))
                return null;
            return new TimeOnly(ReceivedHour, ReceivedMinute, ReceivedSecond);
        }
        set
        {
            if (value is { } t)
            {
                // Only update the properties if a value is provided.
                ReceivedHour = (byte)t.Hour;
                ReceivedMinute = (byte)t.Minute;
                ReceivedSecond = (byte)t.Second;
            }
            else
            {
                // Clear the Time.
                // If code tries to access Time again, null will be returned.
                ReceivedHour = 0;
                ReceivedMinute = 0;
                ReceivedSecond = 0;
            }
        }
    }

    public override byte EggYear { get => Data[0xD1]; set => Data[0xD1] = value; }
    public override byte EggMonth { get => Data[0xD2]; set => Data[0xD2] = value; }
    public override byte EggDay { get => Data[0xD3]; set => Data[0xD3] = value; }
    public override byte MetYear { get => Data[0xD4]; set => Data[0xD4] = value; }
    public override byte MetMonth { get => Data[0xD5]; set => Data[0xD5] = value; }
    public override byte MetDay { get => Data[0xD6]; set => Data[0xD6] = value; }
    public int Rank { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // unused but fetched for stat calcs, and set for trpoke data?
    public override ushort EggLocation { get => ReadUInt16LittleEndian(Data[0xD8..]); set => WriteUInt16LittleEndian(Data[0xD8..], value); }
    public override ushort MetLocation { get => ReadUInt16LittleEndian(Data[0xDA..]); set => WriteUInt16LittleEndian(Data[0xDA..], value); }
    public override byte Ball { get => Data[0xDC]; set => Data[0xDC] = value; }
    public override byte MetLevel { get => (byte)(Data[0xDD] & ~0x80); set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); }
    public override byte OriginalTrainerGender { get => (byte)(Data[0xDD] >> 7); set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); }
    public byte HyperTrainFlags { get => Data[0xDE]; set => Data[0xDE] = value; }
    public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1;  set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0)); }
    public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1)); }
    public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2)); }
    public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3)); }
    public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4)); }
    public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5)); }
    public override GameVersion Version { get => (GameVersion)Data[0xDF]; set => Data[0xDF] = (byte)value; }
    // 0xE0 Unused
    // 0xE1 Unused
    // 0xE2 Unused
    public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; }
    public float WeightAbsolute { get => ReadSingleLittleEndian(Data[0xE4..]); set => WriteSingleLittleEndian(Data[0xE4..], value); }
    #endregion
    #region Battle Stats
    public override int Status_Condition { get => ReadInt32LittleEndian(Data[0xE8..]); set => WriteInt32LittleEndian(Data[0xE8..], value); }
    public override byte Stat_Level { get => Data[0xEC]; set => Data[0xEC] = value; }
    public byte DirtType { get => Data[0xED]; set => Data[0xED] = value; }
    public byte DirtLocation { get => Data[0xEE]; set => Data[0xEE] = value; }
    // 0xEF unused
    public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data[0xF0..]); set => WriteUInt16LittleEndian(Data[0xF0..], (ushort)value); }
    public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data[0xF2..]); set => WriteUInt16LittleEndian(Data[0xF2..], (ushort)value); }
    public override int Stat_ATK { get => ReadUInt16LittleEndian(Data[0xF4..]); set => WriteUInt16LittleEndian(Data[0xF4..], (ushort)value); }
    public override int Stat_DEF { get => ReadUInt16LittleEndian(Data[0xF6..]); set => WriteUInt16LittleEndian(Data[0xF6..], (ushort)value); }
    public override int Stat_SPE { get => ReadUInt16LittleEndian(Data[0xF8..]); set => WriteUInt16LittleEndian(Data[0xF8..], (ushort)value); }
    public override int Stat_SPA { get => ReadUInt16LittleEndian(Data[0xFA..]); set => WriteUInt16LittleEndian(Data[0xFA..], (ushort)value); }
    public override int Stat_SPD { get => ReadUInt16LittleEndian(Data[0xFC..]); set => WriteUInt16LittleEndian(Data[0xFC..], (ushort)value); }
    public int Stat_CP { get => ReadUInt16LittleEndian(Data[0xFE..]); set => WriteUInt16LittleEndian(Data[0xFE..], (ushort)value); }
    public bool Stat_Mega { get => Data[0x100] != 0; set => Data[0x100] = value ? (byte)1 : (byte)0; }
    public int Stat_MegaForm { get => Data[0x101]; set => Data[0x101] = (byte)value; }
    // 102/103 unused
    #endregion

    public int MarkingCount => 6;

    public MarkingColor GetMarking(int index)
    {
        ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MarkingCount);
        return (MarkingColor)((MarkingValue >> (index * 2)) & 3);
    }

    public void SetMarking(int index, MarkingColor value)
    {
        ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MarkingCount);
        var shift = index * 2;
        MarkingValue = (ushort)((MarkingValue & ~(0b11 << shift)) | (((byte)value & 3) << shift));
    }

    public MarkingColor MarkingCircle   { get => GetMarking(0); set => SetMarking(0, value); }
    public MarkingColor MarkingTriangle { get => GetMarking(1); set => SetMarking(1, value); }
    public MarkingColor MarkingSquare   { get => GetMarking(2); set => SetMarking(2, value); }
    public MarkingColor MarkingHeart    { get => GetMarking(3); set => SetMarking(3, value); }
    public MarkingColor MarkingStar     { get => GetMarking(4); set => SetMarking(4, value); }
    public MarkingColor MarkingDiamond  { get => GetMarking(5); set => SetMarking(5, value); }

    protected override bool TradeOT(ITrainerInfo tr)
    {
        // Check to see if the OT matches the SAV's OT info.
        if (!BelongsTo(tr))
            return false;

        if (CurrentHandler != 0)
        {
            CurrentHandler = 0;
            ReceivedDate = IsUntraded ? MetDate : EncounterDate.GetDateSwitch();
            ReceivedTime = EncounterDate.GetTime();
        }
        return true;
    }

    private new bool BelongsTo(ITrainerInfo tr)
    {
        if (tr.Version != Version)
        {
            // GO Park captures are the save file but with GO as the version. Allow it to remain with OT rather than forcing HT.
            if (Version != GameVersion.GO || !IsUntraded)
                return false;
        }

        if (tr.ID32 != ID32)
            return false;
        if (tr.Gender != OriginalTrainerGender)
            return false;

        Span<char> ot = stackalloc char[MaxStringLengthTrainer];
        int len = LoadString(OriginalTrainerTrash, ot);
        return ot[..len].SequenceEqual(tr.OT);
    }

    protected override void TradeHT(ITrainerInfo tr)
    {
        Span<char> ht = stackalloc char[TrashCharCountTrainer];
        var len = LoadString(HandlingTrainerTrash, ht);
        ht = ht[..len];

        var other = tr.OT;
        if (!ht.SequenceEqual(other))
        {
            HandlingTrainerName = other;
            HandlingTrainerFriendship = CurrentFriendship; // copy friendship instead of resetting (don't alter CP)

            ReceivedDate = EncounterDate.GetDateSwitch();
            ReceivedTime = EncounterDate.GetTime();
        }
        CurrentHandler = 1;
        HandlingTrainerGender = tr.Gender;
    }

    public void FixMemories()
    {
        if (IsUntraded)
        {
            HandlingTrainerTrash.Clear();
            HandlingTrainerGender = HandlingTrainerFriendship = 0;
        }
    }

    // Maximums
    public override ushort MaxMoveID => Legal.MaxMoveID_7b;
    public override ushort MaxSpeciesID => Legal.MaxSpeciesID_7b;
    public override int MaxAbilityID => Legal.MaxAbilityID_7_USUM;
    public override int MaxItemID => Legal.MaxItemID_7_USUM;
    public override int MaxBallID => Legal.MaxBallID_7;
    public override GameVersion MaxGameID => Legal.MaxGameID_7b;

    public override void LoadStats(IBaseStat p, Span<ushort> stats)
    {
        var level = CurrentLevel;
        var nature = Nature;
        int friend = CurrentFriendship; // stats +10% depending on friendship!
        int scalar = (int)(((friend / 255.0f / 10.0f) + 1.0f) * 100.0f);
        stats[0] = (ushort)(AV_HP  + GetStat(p.HP,  HT_HP  ? 31 : IV_HP,  level) + 10 + level);
        stats[1] = (ushort)(AV_ATK + (scalar * GetStat(p.ATK, HT_ATK ? 31 : IV_ATK, level, nature, 0) / 100));
        stats[2] = (ushort)(AV_DEF + (scalar * GetStat(p.DEF, HT_DEF ? 31 : IV_DEF, level, nature, 1) / 100));
        stats[3] = (ushort)(AV_SPE + (scalar * GetStat(p.SPE, HT_SPE ? 31 : IV_SPE, level, nature, 4) / 100));
        stats[4] = (ushort)(AV_SPA + (scalar * GetStat(p.SPA, HT_SPA ? 31 : IV_SPA, level, nature, 2) / 100));
        stats[5] = (ushort)(AV_SPD + (scalar * GetStat(p.SPD, HT_SPD ? 31 : IV_SPD, level, nature, 3) / 100));
    }

    /// <summary>
    /// Gets the initial stat value based on the base stat value, IV, and current level.
    /// </summary>
    /// <param name="baseStat"><see cref="PersonalInfo"/> stat.</param>
    /// <param name="iv">Current IV, already accounted for Hyper Training</param>
    /// <param name="level">Current Level</param>
    /// <returns>Initial Stat</returns>
    private static int GetStat(int baseStat, int iv, byte level) => (iv + (2 * baseStat)) * level / 100;

    /// <summary>
    /// Gets the initial stat value with nature amplification applied. Used for all stats except HP.
    /// </summary>
    /// <param name="baseStat"><see cref="PersonalInfo"/> stat.</param>
    /// <param name="iv">Current IV, already accounted for Hyper Training</param>
    /// <param name="level">Current Level</param>
    /// <param name="nature"><see cref="PKM.Nature"/></param>
    /// <param name="statIndex">Stat amp index in the nature amp table</param>
    /// <returns>Initial Stat with nature amplification applied.</returns>
    private static int GetStat(int baseStat, int iv, byte level, Nature nature, int statIndex)
    {
        int initial = GetStat(baseStat, iv, level) + 5;
        return NatureAmp.AmplifyStat(nature, statIndex, initial);
    }

    public int CalcCP => Math.Min(10000, AwakeCP + BaseCP);

    public int BaseCP
    {
        get
        {
            var p = PersonalInfo;
            var level = CurrentLevel;
            var nature = Nature;
            int scalar = CPScalar;

            // Calculate stats for all, then sum together.
            // HP is not overriden to 1 like a regular stat calc for Shedinja.
            var statSum =
                (ushort)GetStat(p.HP, HT_HP ? 31 : IV_HP, level) + 10 + level
                + (ushort)(GetStat(p.ATK, HT_ATK ? 31 : IV_ATK, level, nature, 0) * scalar / 100)
                + (ushort)(GetStat(p.DEF, HT_DEF ? 31 : IV_DEF, level, nature, 1) * scalar / 100)
                + (ushort)(GetStat(p.SPE, HT_SPE ? 31 : IV_SPE, level, nature, 4) * scalar / 100)
                + (ushort)(GetStat(p.SPA, HT_SPA ? 31 : IV_SPA, level, nature, 2) * scalar / 100)
                + (ushort)(GetStat(p.SPD, HT_SPD ? 31 : IV_SPD, level, nature, 3) * scalar / 100);

            float result = statSum * 6f;
            result *= level;
            result /= 100f;
            return (int)result;
        }
    }

    public int CPScalar
    {
        get
        {
            int friend = CurrentFriendship; // stats +10% depending on friendship!
            float scalar = friend / 255f;
            scalar /= 10f;
            scalar++;
            scalar *= 100f;
            return (int)scalar;
        }
    }

    public int AwakeCP
    {
        get
        {
            var sum = this.AwakeningSum();
            if (sum == 0)
                return 0;
            var lvl = CurrentLevel;
            float scalar = lvl * 4f;
            scalar /= 100f;
            scalar += 2f;
            float result = sum * scalar;
            return (int)result;
        }
    }

    public void ResetCP() => Stat_CP = CalcCP;

    public void ResetCalculatedValues()
    {
        ResetCP();
        ResetHeight();
        ResetWeight();
    }

    public float HeightRatio => GetHeightRatio(HeightScalar);
    public float WeightRatio => GetWeightRatio(WeightScalar);

    public float CalcHeightAbsolute => GetHeightAbsolute(PersonalInfo, HeightScalar);
    public float CalcWeightAbsolute => GetWeightAbsolute(PersonalInfo, HeightScalar, WeightScalar);

    public void ResetHeight() => HeightAbsolute = CalcHeightAbsolute;
    public void ResetWeight() => WeightAbsolute = CalcWeightAbsolute;

    [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
    private static float GetHeightRatio(byte heightScalar)
    {
        // + 40%, -20
        float result = heightScalar / 255f; // 0x437F0000
        result *= 0.79999995f; // 0x3F4CCCCC
        result += 0.6f; // 0x3F19999A
        return result;
    }

    [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
    private static float GetWeightRatio(byte weightScalar)
    {
        // +/- 20%
        float result = weightScalar / 255f; // 0x437F0000
        result *= 0.40000004f; // 0x3ECCCCCE
        result += 0.8f; // 0x3F4CCCCD
        return result;
    }

    [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
    public static float GetHeightAbsolute(IPersonalMisc p, byte heightScalar)
    {
        float HeightRatio = GetHeightRatio(heightScalar);
        return HeightRatio * p.Height;
    }

    [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
    public static float GetWeightAbsolute(IPersonalMisc p, byte heightScalar, byte weightScalar)
    {
        float HeightRatio = GetHeightRatio(heightScalar);
        float WeightRatio = GetWeightRatio(weightScalar);

        float weight = WeightRatio * p.Weight;
        return HeightRatio * weight;
    }

    [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
    public static byte GetHeightScalar(float height, int avgHeight)
    {
        // height is already *100
        float biasH = avgHeight * -0.6f;
        float biasL = avgHeight * 0.79999995f;
        float numerator = biasH + height;
        float result = numerator / biasL;
        result *= 255f;
        int value = (int)result;
        int unsigned = value & ~(value >> 31);
        if (unsigned > 255)
            unsigned = 255;
        return (byte)unsigned;
    }

    [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
    public static byte GetWeightScalar(float height, float weight, int avgHeight, int avgWeight)
    {
        // height is already *100
        // weight is already *10
        float heightRatio = height / avgHeight;
        float weightComponent = heightRatio * weight;
        float top = avgWeight * -0.8f;
        top += weightComponent;
        float bot = avgWeight * 0.40000004f;
        float result = top / bot;
        result *= 255f;
        int value = (int)result;
        int unsigned = value & ~(value >> 31);
        if (unsigned > 255)
            unsigned = 255;
        return (byte)unsigned;
    }

    public static int GetRandomIndex(int bits, int characterIndex, Nature nature)
    {
        if (bits is 6 or 7)
            return GetRandomIndex(characterIndex);
        if (bits is 0)
            return 0;
        var amps = NatureAmp.GetAmps(nature);
        if (amps[bits - 1] != -1) // not a negative stat
            return bits;

        // remap a negative stat to positive
        return 1 + amps.IndexOf((sbyte)1);
    }

    private static int GetRandomIndex(int characterIndex) => (characterIndex / 5) switch
    {
        0 => 0,
        1 => 1,
        2 => 2,
        3 => 5,
        4 => 3,
        5 => 4,
        _ => throw new ArgumentOutOfRangeException(nameof(characterIndex)), // never happens, characteristic is always 0-29
    };

    public const byte InitialSpiritMood = 100;
    public void ResetSpiritMood() => Spirit = Mood = InitialSpiritMood;

    public bool IsStarter => this switch
    {
        { Species: (int)Core.Species.Pikachu, Form: 8 } => true,
        { Species: (int)Core.Species.Eevee, Form: 1 } => true,
        _ => false,
    };

    public override string GetString(ReadOnlySpan<byte> data)
        => StringConverter8.GetString(data);
    public override int LoadString(ReadOnlySpan<byte> data, Span<char> destBuffer)
        => StringConverter8.LoadString(data, destBuffer);
    public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
        => StringConverter8.SetString(destBuffer, value, maxLength, option);
    public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
        => TrashBytesUTF16.GetTerminatorIndex(data);
    public override int GetStringLength(ReadOnlySpan<byte> data)
        => TrashBytesUTF16.GetStringLength(data);
    public override int GetBytesPerChar() => 2;
}
