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


package engine.objects;

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

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

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

	};

	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
			1 // 268774.71 //Level 75

	};

	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

	// 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
	// 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)))));
	}

	// Get XP needed for the next level
	public static int getExpForNextLevel(int experience, int currentLevel) {
		return (getBaseExperience(currentLevel + 1) - experience);
	}

	// Max XP granted for killing a blue, yellow or orange mob
	public static float maxXPPerKill(int level) {
		if (level < 1)
			level = 1;
		if (level > 75)
			level = 75;
		return MaxExpPerLevel[level];
		// return (LevelToExp[level + 1] - LevelToExp[level])/(11 + level/2);
		// return ((((level * level)-level)*50)+16);
	}

	// 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.25;
		case Orange:
			return 1.15;
		case Yellow:
			return 1.05;
		case Blue:
			return 1;
		case Cyan:
			return 0.8;
		case Green:
			return 0.5;
		default:
			return 0;
		}
	}

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

		double penalty = 0.0;
		int adjustedGroupSize = 0;
		int totalLevels = 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;

		double xp = 0.0;

		//Get the xp modifier for the world
		float xpMod = MBServerStatics.EXP_RATE_MOD;



		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 pc : giveEXPTo) {
				if (pc.getLevel() >= MBServerStatics.LEVELCAP)
					continue;

				// Sets Max XP with server exp mod taken into account.
				xp = (double) xpMod * maxXPPerKill(pc.getLevel());

				// Adjust XP for Mob Level
				xp *= getConMod(pc, mob);

				// Process XP for this member
				penalty = getGroupMemberPenalty(leadership, pc, giveEXPTo,
						highestLevel);

				// Leadership Penalty Reduction
				if (leadership > 0)
					penalty -= ((leadership) * 0.01) * penalty;

				// Modify for hotzone
				if (xp != 0)
					if (ZoneManager.inHotZone(mob.getLoc()))
						xp *= MBServerStatics.HOT_EXP_RATE_MOD;

				// Check for 0 XP due to white mob, otherwise subtract penalty
				// xp
				if (xp == 0) {
					xp = 1;
				} else {
					xp -= (penalty * 0.01) * xp;

					// Errant Penalty Calculation
					if (pc.getGuild().isEmptyGuild())
						xp *= 0.6;
				}

				if (xp == 0)
					xp = 1;

				// Grant the player the EXP
				pc.grantXP((int) Math.floor(xp));
			}

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

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

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

			// Modify for hotzone
			if (ZoneManager.inHotZone(mob.getLoc()))
				xp *= MBServerStatics.HOT_EXP_RATE_MOD;

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

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