using System;
using WingsEmu.Game._enum;
using WingsEmu.Game.Algorithm;
using WingsEmu.Packets.Enums;
using WingsEmu.Packets.Enums.Battle;
using WingsEmu.Packets.Enums.Character;

namespace Plugin.ResourceLoader
{
    /*
     * Coefficient of statistics for the class:
     * Adventurer: 0, 0, 0
     * Swordsman: 8, 2, 0
     * Archer: 3, 6, 1
     * Mage: 0, 2, 8
     * Martial Artist: 5, 3, 2
     *
     * Additional statistics:
     * Adventurer: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
     * Swordsman: 0, 15, 0, 0, 0, 0, 0, 0, 0, 0
     * Archer: 0, 15, 50, 15, 0, 0, 10, 0, 10, 0
     * Magician: 0, 0, 0, 0, 0, 0, 10, 20, 0, 0
     * Martial Artist: 0, 0, 20, 0, 0, 0, 20, 10, 10, 0
     */
    public class BattleEntityAlgorithmService : IBattleEntityAlgorithmService
    {
        private static readonly double[] DefenseRace0 = { 16, 13.5, 11, 50, 50, 50 };
        private static readonly double[] DefenseRace1 = { 20, 17, 19, 100, 100, 100 };
        private static readonly double[] DefenseRace2 = { 15, 15, 15, 75, 50, 40 };
        private static readonly double[] DefenseRace3 = { 15, 15, 15, 50, 50, 50 };
        private static readonly double[] DefenseRace4 = { 17.4, 17.4, 17.4, 60, 60, 100 };
        private static readonly double[] DefenseRace5 = { 13.4, 13.4, 13.4, 40, 40, 40 };
        private static readonly double[] DefenseRace6 = { 11.5, 15, 25, 50, 50, 75 };
        private static readonly double[] DefenseRace8 = { 10, 10, 10, 100, 100, 100 };
        private static readonly double[] DefaultDefense = { 0, 0, 0, 0, 0, 0 };

        private static readonly int[,] Statistics =
        {
            { 0, 0, 0 },
            { 8, 2, 0 },
            { 3, 6, 1 },
            { 0, 2, 8 },
            { 5, 3, 2 }
        };

        private static readonly int[,] AdditionalStatistics =
        {
            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
            { 0, 15, 0, 0, 0, 0, 0, 0, 0, 0 },
            { 0, 15, 50, 15, 0, 0, 10, 0, 10, 0 },
            { 0, 0, 0, 0, 0, 0, 10, 20, 0, 0 },
            { 0, 0, 20, 0, 0, 0, 20, 10, 10, 0 }
        };

        public int GetBasicHp(int race, int level, int modifier, int additionalHp = 0, bool isMonster = true)
        {
            double hp = 0;
            int a = 0;
            int b = 0;
            int c = 0;

            if (isMonster)
            {
                modifier = 0;
            }

            switch (race)
            {
                case 0:
                    a = 0;
                    b = 2;
                    c = 138;
                    break;
                case 1:
                    a = 10;
                    b = 10;
                    c = 610;
                    break;
                case 2:
                    a = 5;
                    b = 0;
                    c = 105;
                    break;
                case 3:
                    a = 0;
                    b = 0;
                    c = 205;
                    break;
                case 4:
                    a = 2;
                    b = 5;
                    c = 695;
                    break;
                case 5:
                    a = -2;
                    b = -3;
                    c = 263;
                    break;
                case 6:
                    a = 0;
                    b = -7;
                    c = 21;
                    break;
                default:
                    a = 0;
                    b = 0;
                    c = 0;
                    break;
            }

            if (race == 8)
            {
                hp = 7;
            }
            else
            {
                int x = level;
                if ((modifier + a) != 0)
                {
                    x += (int)Math.Floor((level - 1) / (decimal)(10.0 / (modifier + a)));
                }

                hp = 0.5 * (x * x) + (15.5 + b) * x + c;
            }

            if (!isMonster)
            {
                return (int)Math.Floor(hp + additionalHp);
            }

            switch (level)
            {
                case >= 37 and <= 51:
                    hp *= 1.2;
                    break;
                case >= 52 and <= 61:
                    hp *= 1.5;
                    break;
                case >= 62 and <= 71:
                    hp *= 1.8;
                    break;
                case >= 72 and <= 81:
                    hp *= 2.5;
                    break;
                case >= 81:
                    hp *= 3.5;
                    break;
            }

            return (int)Math.Floor(hp + additionalHp);
        }

        public int GetBaseStatistic(int level, ClassType classType, StatisticType statisticType)
        {
            switch (statisticType)
            {
                case StatisticType.ATTACK_MELEE:
                    return (int)(level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 0] / 10.0))) + AdditionalStatistics[(int)classType, 0];
                case StatisticType.ATTACK_RANGED:
                    return (int)(level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 1] / 10.0))) + AdditionalStatistics[(int)classType, 2];
                case StatisticType.ATTACK_MAGIC:
                    return (int)(level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 2] / 10.0))) + AdditionalStatistics[(int)classType, 4];
                case StatisticType.HITRATE_MELEE:
                    return (int)(level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 1] / 10.0))) + AdditionalStatistics[(int)classType, 1];
                case StatisticType.HITRATE_RANGED:
                    return (int)((level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 1] / 10.0))) * 2) + AdditionalStatistics[(int)classType, 3];
                case StatisticType.DEFENSE_MELEE:
                    return (int)((level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 0] / 10.0))) * (decimal)0.5) + AdditionalStatistics[(int)classType, 5];
                case StatisticType.DEFENSE_RANGED:
                    return (int)((level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 1] / 10.0))) * (decimal)0.5) + AdditionalStatistics[(int)classType, 7];
                case StatisticType.DEFENSE_MAGIC:
                    return (int)((level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 2] / 10.0))) * (decimal)0.5) + AdditionalStatistics[(int)classType, 9];
                case StatisticType.DODGE_MELEE:
                    return (int)(level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 1] / 10.0))) + AdditionalStatistics[(int)classType, 6];
                case StatisticType.DODGE_RANGED:
                    return (int)(level + 9 + Math.Floor((decimal)((level - 1) * Statistics[(int)classType, 1] / 10.0))) + AdditionalStatistics[(int)classType, 8];
            }

            return 0;
        }

        public int GetBasicMp(int race, int level, int modifier, int additionalMp = 0, bool isMonster = true)
        {
            double mp = 0;
            int z = 0;
            double d = 0;
            int a = 0;
            int g = 0;
            int x = 0;

            if (isMonster)
            {
                modifier = 0;
            }

            switch (race)
            {
                case 0:
                    d = 4.75;
                    a = 0;
                    g = 0;
                    break;
                case 1:
                    d = 178.75;
                    a = 10;
                    g = 8;
                    break;
                case 2:
                    d = 50.75;
                    a = -2;
                    g = 4;
                    break;
                case 3:
                    d = 50.75;
                    a = 0;
                    g = 4;
                    break;
                case 4:
                    d = 385.75;
                    a = 10;
                    g = 6;
                    z = 1;
                    break;
                case 5:
                    d = 23.75;
                    a = -2;
                    g = 2;
                    z = 1;
                    break;
                case 6:
                    d = 705.75;
                    a = 5;
                    g = 14;
                    break;
                case 8:
                    return (int)Math.Floor(4 + (double)additionalMp);
                default:
                    d = 0;
                    a = 0;
                    g = 0;
                    break;
            }

            x = level;
            if ((modifier + a) != 0)
            {
                x += (int)Math.Floor((level - 1) / (decimal)(10.0 / (modifier + a)) + z);
            }

            mp = Math.Floor((5.25 + g) * x + d) + (Math.Floor((double)(x - 6) / 4) + 1) * 2 * (Mod(x - 2, 4) + 1 + Math.Floor(((double)x - 6) / 4) * 2);

            return (int)Math.Floor(mp + additionalMp);
        }

        public int GetBasicHpByClass(ClassType classType, int level)
        {
            int hp = classType switch
            {
                ClassType.Adventurer => GetBasicHp(3, level, 0, 0, false),
                ClassType.Swordman => GetBasicHp(3, level, 8, 0, false),
                ClassType.Archer => GetBasicHp(3, level, 3, 0, false),
                ClassType.Magician => GetBasicHp(3, level, 0, 0, false),
                ClassType.Wrestler => GetBasicHp(3, level, 5, 0, false)
            };

            return hp;
        }

        public int GetBasicMpByClass(ClassType classType, int level)
        {
            int mp = classType switch
            {
                ClassType.Adventurer => GetBasicMp(3, level, 0, 0, false),
                ClassType.Swordman => GetBasicMp(3, level, 0, 0, false),
                ClassType.Archer => GetBasicMp(3, level, 1, 0, false),
                ClassType.Magician => GetBasicMp(3, level, 8, 0, false),
                ClassType.Wrestler => GetBasicMp(3, level, 2, 0, false)
            };

            return mp;
        }

        public int GetAttack(bool isMin, int race, AttackType attackType, short weaponLevel, byte wInfo, short level, int modifier, int additional, bool isWild = true, short petLevel = 0,
            MateType mateType = MateType.Pet)
        {
            int calcLevel;
            int weaponMod;
            int a;
            int b;
            int c;

            if (isWild)
            {
                modifier = 0;
                calcLevel = level;
                weaponMod = weaponLevel;
            }
            else
            {
                calcLevel = petLevel;
                weaponMod = petLevel + level - weaponLevel;
            }

            switch (attackType)
            {
                case AttackType.Melee:
                    switch (race)
                    {
                        case 0:
                            a = 35;
                            b = 0;
                            break;
                        case 1:
                            a = 43;
                            b = 10;
                            break;
                        case 2:
                            a = 33;
                            b = 5;
                            break;
                        case 3:
                            a = 33;
                            b = 0;
                            break;
                        case 4:
                            a = 38;
                            b = 2;
                            break;
                        case 5:
                            a = 30;
                            b = -2;
                            break;
                        case 6:
                            a = 26;
                            b = 0;
                            break;
                        case 8:
                            a = 23;
                            b = 0;
                            break;
                        default:
                            a = 0;
                            b = 0;
                            break;
                    }

                    break;
                case AttackType.Ranged:
                    switch (race)
                    {
                        case 0:
                            a = 30;
                            b = 0;
                            break;
                        case 1:
                            a = 38;
                            b = 10;
                            break;
                        case 2:
                            a = 33;
                            b = 0;
                            break;
                        case 3:
                            a = 33;
                            b = 0;
                            break;
                        case 4:
                            a = 38;
                            b = 2;
                            break;
                        case 5:
                            a = 30;
                            b = -2;
                            break;
                        case 6:
                            a = 33;
                            b = 0;
                            break;
                        case 8:
                            a = 23;
                            b = 0;
                            break;
                        default:
                            a = 0;
                            b = 0;
                            break;
                    }

                    break;
                case AttackType.Magical:
                    switch (race)
                    {
                        case 0:
                            a = 25;
                            b = 0;
                            break;
                        case 1:
                            a = 41;
                            b = 10;
                            break;
                        case 2:
                            a = 33;
                            b = -2;
                            break;
                        case 3:
                            a = 33;
                            b = 0;
                            break;
                        case 4:
                            a = 38;
                            b = 10;
                            break;
                        case 5:
                            a = 30;
                            b = -2;
                            break;
                        case 6:
                            a = 53;
                            b = 5;
                            break;
                        case 8:
                            a = 23;
                            b = 0;
                            break;
                        default:
                            a = 0;
                            b = 0;
                            break;
                    }

                    break;
                default:
                    a = 0;
                    b = 0;
                    break;
            }

            if (wInfo > 1)
            {
                c = (int)Math.Floor((decimal)((calcLevel + 2.0) / (wInfo - 1))) + 1;
            }
            else
            {
                c = 0;
            }

            if (!isWild && mateType == MateType.Partner)
            {
                return calcLevel + 9 + (int)Math.Floor((calcLevel - 1) * (modifier / 10.0));
            }

            if (isMin)
            {
                return (int)Math.Floor(calcLevel + (a - 7.2) + 3.2 * weaponMod + Math.Floor((calcLevel - 1) * ((modifier + b) / 10.0)) + additional + c);
            }

            return (int)Math.Floor(calcLevel + a + 4.8 * weaponMod + Math.Floor((calcLevel - 1) * ((modifier + b) / 10.0)) + additional - c);
        }

        public int GetHitrate(int race, AttackType attackType, short weaponLevel, short level, int modifier, int additional, bool isWild = true, short petLevel = 0, MateType mateType = MateType.Pet)
        {
            int calcLevel;
            int weaponMod;
            int a;
            int b;

            if (isWild)
            {
                modifier = 0;
                calcLevel = level;
                weaponMod = weaponLevel;
            }
            else
            {
                calcLevel = petLevel;
                weaponMod = petLevel + level - weaponLevel;
            }

            if (!isWild && mateType == MateType.Partner)
            {
                switch (attackType)
                {
                    case AttackType.Melee:
                        return calcLevel + 9 + (int)Math.Floor((calcLevel - 1) * (modifier / 10.0));
                    case AttackType.Ranged:
                        return 2 * (calcLevel + 9 + (int)Math.Floor((calcLevel - 1) * (modifier / 10.0)));
                    default:
                        return 0;
                }
            }

            switch (attackType)
            {
                case AttackType.Melee:
                    switch (race)
                    {
                        case 0:
                            a = 22;
                            b = 0;
                            break;
                        case 1:
                            a = 30;
                            b = 10;
                            break;
                        case 2:
                            a = 25;
                            b = 0;
                            break;
                        case 3:
                            a = 25;
                            b = 0;
                            break;
                        case 4:
                            a = 30;
                            b = 2;
                            break;
                        case 5:
                            a = 22;
                            b = -2;
                            break;
                        case 6:
                            a = 25;
                            b = 0;
                            break;
                        case 8:
                            a = 15;
                            b = 0;
                            break;
                        default:
                            a = 0;
                            b = 0;
                            break;
                    }

                    return (int)Math.Floor(calcLevel + 4 * weaponMod + a + Math.Floor((calcLevel - 1) * ((modifier + b) / 10.0)) + additional);

                case AttackType.Ranged:
                    switch (race)
                    {
                        case 0:
                            a = 28;
                            b = 0;
                            break;
                        case 1:
                            a = 44;
                            b = 10;
                            break;
                        case 2:
                            a = 34;
                            b = 0;
                            break;
                        case 3:
                            a = 34;
                            b = 0;
                            break;
                        case 4:
                            a = 44;
                            b = 2;
                            break;
                        case 5:
                            a = 28;
                            b = -2;
                            break;
                        case 6:
                            a = 34;
                            b = 0;
                            break;
                        case 8:
                            a = 15;
                            b = 0;
                            break;
                        default:
                            a = 0;
                            b = 0;
                            break;
                    }

                    return (int)Math.Floor(2 * calcLevel + 4 * weaponMod + a + Math.Floor((calcLevel - 1) * ((modifier + b) / 10.0)) * 2 + additional);
                case AttackType.Magical:
                    return 70 + additional;
                default:
                    return 0;
            }
        }

        public int GetDodge(int race, short armorLevel, short level, int modifier, int additional, bool isWild = true, short petLevel = 0, MateType mateType = MateType.Partner)
        {
            int calcLevel;
            int armorMod;
            int a;
            int b;

            if (isWild)
            {
                modifier = 0;
                calcLevel = level;
                armorMod = armorLevel;
            }
            else
            {
                calcLevel = petLevel;
                armorMod = petLevel + level - armorLevel;
            }

            if (!isWild && mateType == MateType.Partner)
            {
                return calcLevel + 9 + (int)Math.Floor((calcLevel - 1) * (modifier / 10.0));
            }

            switch (race)
            {
                case 0:
                    a = 26;
                    b = 0;
                    break;
                case 1:
                    a = 34;
                    b = 10;
                    break;
                case 2:
                    a = 29;
                    b = 0;
                    break;
                case 3:
                    a = 29;
                    b = 0;
                    break;
                case 4:
                    a = 34;
                    b = 2;
                    break;
                case 5:
                    a = 26;
                    b = -2;
                    break;
                case 6:
                    a = 29;
                    b = 0;
                    break;
                case 8:
                    a = 19;
                    b = 0;
                    break;
                default:
                    a = 0;
                    b = 0;
                    break;
            }

            return (int)Math.Floor(calcLevel + 4 * armorMod + a + Math.Floor((calcLevel - 1) * ((modifier + b) / 10.0)) + additional);
        }

        public int GetDefense(int race, AttackType attackType, short armorLevel, short level, int modifier, int additional, bool isWild = true, short petLevel = 0, MateType mateType = MateType.Pet)
        {
            int calcLevel;
            int armorMod;

            if (isWild)
            {
                modifier = 0;
                calcLevel = level;
                armorMod = armorLevel;
            }
            else
            {
                calcLevel = petLevel;
                armorMod = petLevel + level - armorLevel;
            }

            if (!isWild && mateType == MateType.Partner)
            {
                return (int)(0.5 * (calcLevel + 9 + (int)Math.Floor((calcLevel - 1) * (modifier / 10.0))));
            }

            double[] raceInfo = race switch
            {
                0 => DefenseRace0,
                1 => DefenseRace1,
                2 => DefenseRace2,
                3 => DefenseRace3,
                4 => DefenseRace4,
                5 => DefenseRace5,
                6 => DefenseRace6,
                8 => DefenseRace8,
                _ => DefaultDefense
            };

            return attackType switch
            {
                AttackType.Melee => (int)Math.Floor(2 * armorMod + raceInfo[0] + Math.Floor((armorMod + 5) * 0.08) + (calcLevel - 1) * ((modifier * 10 + (raceInfo[3] - 5 * modifier)) / 100.0) +
                    additional),
                AttackType.Ranged => (int)Math.Floor(2 * armorMod + raceInfo[1] + Math.Floor((armorMod + 5) * 0.36) + (calcLevel - 1) * ((modifier * 10 + (raceInfo[4] - 5 * modifier)) / 100.0) +
                    additional),
                AttackType.Magical => (int)Math.Floor(2 * armorMod + raceInfo[2] + Math.Floor((armorMod + 5) * 0.04) + (calcLevel - 1) * ((modifier * 10 + (raceInfo[5] - 5 * modifier)) / 100.0) +
                    additional)
            };
        }

        public byte GetSpeed(ClassType classType)
        {
            byte speed = classType switch
            {
                ClassType.Adventurer => 11,
                ClassType.Swordman => 11,
                ClassType.Archer => 12,
                ClassType.Magician => 10,
                ClassType.Wrestler => 11,
                _ => 0
            };

            return speed;
        }

        private static double Mod(int a, int modulo)
        {
            if (a >= 0)
            {
                return a % modulo;
            }

            return Math.Abs((a - 2) % modulo);
        }
    }
}