// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2022
//                www.magicbane.com


package engine.objects;

import engine.Enum;
import engine.Enum.TargetColor;
import engine.gameManager.ZoneManager;
import engine.math.Vector3fImmutable;
import engine.server.MBServerStatics;

import java.util.ArrayList;
import java.util.TreeMap;

import static engine.gameManager.LootManager.LOOTMANAGER;

public class Experience {

    private static final TreeMap<Integer, Integer> ExpToLevel;
    private static final int[] LevelToExp = {Integer.MIN_VALUE, // Pad
            // everything
            // over 1

            // R0
            0, // Level 1
            150, // Level 2
            1200, // Level 3
            4050, // Level 4
            9600, // Level 5
            18750, // Level 6
            32400, // Level 7
            51450, // Level 8
            76800, // Level 9

            // R1
            109350, // Level 10
            150000, // Level 11
            199650, // Level 12
            259200, // Level 13
            329550, // Level 14
            411600, // Level 15
            506250, // Level 16
            614400, // Level 17
            736950, // Level 18
            874800, // Level 19

            // R2
            1028850, // Level 20
            1200000, // Level 21
            1389150, // Level 22
            1597200, // Level 23
            1825050, // Level 24
            2073600, // Level 25
            2343750, // Level 26
            2636400, // Level 27
            2952450, // Level 28
            3292800, // Level 29

            // R3
            3658350, // Level 30
            4050000, // Level 31
            4468650, // Level 32
            4915200, // Level 33
            5390550, // Level 34
            5895600, // Level 35
            6431250, // Level 36
            6998400, // Level 37
            7597950, // Level 38
            8230800, // Level 39

            // R4
            8897850, // Level 40
            10091520, // Level 41
            11396777, // Level 42
            12820187, // Level 43
            14368505, // Level 44
            16048666, // Level 45
            17867790, // Level 46
            19833183, // Level 47
            21952335, // Level 48
            24232919, // Level 49

            // R5
            26682793, // Level 50
            29310000, // Level 51
            32122766, // Level 52
            35129502, // Level 53
            38338805, // Level 54
            41759452, // Level 55
            45400409, // Level 56
            49270824, // Level 57
            53380030, // Level 58
            57737542, // Level 59

            // R6
            62353064, // Level 60
            67236479, // Level 61
            72397859, // Level 62
            77847457, // Level 63
            83595712, // Level 64
            89653247, // Level 65
            96030869, // Level 66
            102739569, // Level 67
            109790524, // Level 68
            117195093, // Level 69

            // R7
            124964822, // Level 70
            133111438, // Level 71
            141646855, // Level 72
            150583171, // Level 73
            159932666, // Level 74
            169707808, // Level 75
            179921247, // Level 76
            190585732,  // Level 77
            201714185,  // Level 78
            213319687,  // Level 79
            225415457,   // Level 80
            238014819   // Level 81

    };

    private static final float[] MaxExpPerLevel = {Float.MIN_VALUE, // Pad
            // everything
            // over
            // 1

            // R0
            15, // Level 1
            105, // Level 2
            285, // Level 3
            555, // Level 4
            610, // Level 5
            682.5f, // Level 6
            730, // Level 7
            975, // Level 8
            1251.92f, // Level 9

            // R1
            1563.46f, // Level 10
            1909.62f, // Level 11
            2290.38f, // Level 12
            2705.77f, // Level 13
            3155.77f, // Level 14
            3640.38f, // Level 15
            4159.62f, // Level 16
            4713.46f, // Level 17
            5301.92f, // Level 18
            5925, // Level 19

            // R2
            6582.69f, // Level 20
            7275, // Level 21
            8001.92f, // Level 22
            8763.46f, // Level 23
            9559.62f, // Level 24
            10390.38f, // Level 25
            11255.77f, // Level 26
            12155.77f, // Level 27
            13090.38f, // Level 28
            14059.62f, // Level 29

            // R3
            15063.46f, // Level 30
            16101.92f, // Level 31
            17175, // Level 32
            18282.69f, // Level 33
            19425, // Level 34
            20601.92f, // Level 35
            21813.46f, // Level 36
            23059.62f, // Level 37
            24340.38f, // Level 38
            25655.77f, // Level 39

            // R4
            45910.38f, // Level 40
            34348.87f, // Level 41
            37458.16f, // Level 42
            40745.21f, // Level 43
            44214.76f, // Level 44
            47871.68f, // Level 45
            51720.87f, // Level 46
            55767.16f, // Level 47
            60015.37f, // Level 48
            64470.37f, // Level 49

            // R5
            69137.03f, // Level 50
            74020.16f, // Level 51
            79124.63f, // Level 52
            84455.34f, // Level 53
            90017.03f, // Level 54
            95814.66f, // Level 55
            101853.03f, // Level 56
            108137, // Level 57
            114671.37f, // Level 58
            121461.11f, // Level 59

            // R6
            128510.92f, // Level 60
            135825.79f, // Level 61
            143410.47f, // Level 62
            151269.87f, // Level 63
            159408.82f, // Level 64
            167832.16f, // Level 65
            176544.74f, // Level 66
            185551.45f, // Level 67
            194857.08f, // Level 68
            204466.55f, // Level 69

            // R7
            214384.63f, // Level 70
            224616.24f, // Level 71
            235166.21f, // Level 72
            246039.34f, // Level 73
            257240.58f, // Level 74
            268774.71f, //Level 75
            280647.69f, // Level 76
            292865.22f, // Level 77
            305433.33f, // Level 78
            318358.08f, // Level 79

            // R8
            1 //331645.74f // Level 80

    };
    // Used to calcuate the amount of experience a monster grants in the
    // following formula
    // expGranted = a(moblevel)^2 + b(moblevel) + c
    private static final float EXPQUADA = 10.0f;
    private static final float EXPQUADB = 6.0f;
    private static final float EXPQUADC = -10.0f;
    // Adds addtional exp per addtional member of a group using the following
    // (expGranted / group.size()) * (groupBonus * (group.size()-1) +1)
    private static final float GROUP_BONUS = 0.5f; // 0.2 grants (20%) addtional

    static {
        ExpToLevel = new TreeMap<>();

        // flip keys and values for other Map
        for (int i = 1; i < LevelToExp.length; i++) {
            ExpToLevel.put(LevelToExp[i], i);
        }
    } // end Static block
    // exp per group member

    // called to determine current level based on xp
    public static int getLevel(int experience) {

        int expKey = ExpToLevel.floorKey(experience);
        int level = ExpToLevel.get(expKey);

        if (level > MBServerStatics.LEVELCAP)
            level = MBServerStatics.LEVELCAP;

        return level;
    }

    // Get the base xp for a level
    public static int getBaseExperience(int level) {

        if (level < LevelToExp.length)
            return LevelToExp[level];

        int fLevel = level - 1;
        int baseXP = fLevel * fLevel * fLevel;

        return (int) ((fLevel < 40) ? (baseXP * 150)
                : (baseXP * (150 + (7.6799998 * (level - 40)))));
    }

    // Max XP granted for killing a blue, yellow or orange mob
    public static float maxXPPerKill(int level) {

        if (level < 1)
            level = 1;

        if (level > MBServerStatics.LEVELCAP)
            level = MBServerStatics.LEVELCAP;

        return MaxExpPerLevel[level];
    }

    // Returns a penalty modifier depending on mob color
    public static double getConMod(AbstractCharacter pc, AbstractCharacter mob) {

        switch (TargetColor.getCon(pc, mob)) {
            case Red:
                return 1.1;
            case Orange:
                return 1.15;
            case Yellow:
                return 1.2;
            case Blue:
                return 1.25;
            case Cyan:
                return 0.9;
            case Green:
                return 0.7;
            default:
                return 0;
        }
    }

    public static double getGroupMemberPenalty(double leadership,
                                               PlayerCharacter currPlayer, ArrayList<PlayerCharacter> players,
                                               int highestLevel) {

        double penalty = 0.0;
        int level = currPlayer.getLevel();

        // Group Size Penalty

        if (players.size() > 2)
            penalty = (players.size() - 2) * 1.5;

        // Calculate Penalty For Highest level -> Current Player difference, !=
        // check to prevent divide by zero error

        if (highestLevel != level)
            penalty += ((highestLevel - level) * .5);

        // double avgLevels = totalLevels / adjustedGroupSize;
        // if (adjustedGroupSize >= 1)
        // if (level < avgLevels)
        // penalty += ((avgLevels - level) * .5);

        // Extra noob penalty

        if ((highestLevel - level) > 25)
            penalty += (highestLevel - level - 25);

        return penalty;
    }

    public static void doExperience(PlayerCharacter killer, AbstractCharacter mob, Group g) {

        // Check for some failure conditions

        if (killer == null || mob == null)
            return;

        if(killer.equals(mob))
            return;

        if(killer.pvpKills.contains(mob.getObjectUUID()))
            return;

        double grantedExperience = 0.0;

        if (g != null) { // Do group EXP stuff

            int leadership = 0;
            int highestLevel = 0;
            double penalty = 0.0;

            ArrayList<PlayerCharacter> giveEXPTo = new ArrayList<>();

            // Check if leader is within range of kill and then get leadership
            // skill

            Vector3fImmutable killLoc = mob.getLoc();

            if (killLoc.distanceSquared2D(g.getGroupLead().getLoc()) < (MBServerStatics.EXP_RANGE * MBServerStatics.EXP_RANGE)) {
                CharacterSkill leaderskill = g.getGroupLead().skills
                        .get("Leadership");

                if (leaderskill != null)
                    leadership = leaderskill.getNumTrains();
                if (leadership > 90)
                    leadership = 90; // leadership caps at 90%
            }

            // Check every group member for distance to see if they get xp

            for (PlayerCharacter pc : g.getMembers()) {
                if (pc.isAlive()) { // Skip if the player is dead.

                    // Check within range

                    if (killLoc.distanceSquared2D(pc.getLoc()) < (MBServerStatics.EXP_RANGE * MBServerStatics.EXP_RANGE)) {

                        giveEXPTo.add(pc);

                        // Track highest level character

                        if (pc.getLevel() > highestLevel)
                            highestLevel = pc.getLevel();
                    }
                }
            }

            // Process every player in the group getting XP

            for (PlayerCharacter playerCharacter : giveEXPTo) {
                if (playerCharacter.getLevel() >= MBServerStatics.LEVELCAP)
                    continue;

                if(playerCharacter.level >= 75 && !mob.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))
                    continue; // cannot PVE higher than level 75

                // Sets Max XP with server exp mod taken into account.

                grantedExperience = (double) LOOTMANAGER.NORMAL_EXP_RATE * maxXPPerKill(playerCharacter.getLevel());

                grantedExperience *= (1/ giveEXPTo.size()+0.9);
                // Adjust XP for Mob Level

                grantedExperience *= getConMod(playerCharacter, mob);

                // Process XP for this member

                penalty = getGroupMemberPenalty(leadership, playerCharacter, giveEXPTo,
                        highestLevel);

                // Leadership Penalty Reduction

                if (leadership > 0)
                    penalty -= ((leadership) * 0.01) * penalty;

                // Modify for hotzone

                if (grantedExperience != 0)
                    if (ZoneManager.inHotZone(mob.getLoc()))
                        grantedExperience *= LOOTMANAGER.HOTZONE_EXP_RATE;

                // Check for 0 XP due to white mob, otherwise subtract penalty
                // xp

                if (grantedExperience == 0)
                    grantedExperience = 1;
                else {
                    grantedExperience -= (penalty * 0.01) * grantedExperience;

                    // Errant Penalty Calculation
                    if (playerCharacter.getGuild().isEmptyGuild())
                        grantedExperience *= 0.6;
                }

                if (grantedExperience == 0)
                    grantedExperience = 1;

                // Grant the player the EXP
                playerCharacter.grantXP((int) Math.floor(grantedExperience));
            }

        } else { // Give EXP to a single character
            //if (!killer.isAlive()) // Skip if the player is dead.
             //   return;

            if (killer.getLevel() >= MBServerStatics.LEVELCAP)
                return;

            if(killer.level >= 75 && !mob.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))
                return;

            // Get XP and adjust for Mob Level with world xp modifier taken into account
            grantedExperience = (double) LOOTMANAGER.NORMAL_EXP_RATE * maxXPPerKill(killer.getLevel());
            grantedExperience *= getConMod(killer, mob);

            // Modify for hotzone
            if (ZoneManager.inHotZone(mob.getLoc()))
                grantedExperience *= LOOTMANAGER.HOTZONE_EXP_RATE;

            // Errant penalty
            if (grantedExperience != 1)
                if (killer.getGuild().isEmptyGuild())
                    grantedExperience *= .6;

            // Grant XP
            killer.grantXP((int) Math.floor(grantedExperience));
        }
    }
}
