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


package engine.objects;

import engine.Enum;
import engine.Enum.ModType;
import engine.Enum.SourceType;
import engine.gameManager.ChatManager;
import engine.gameManager.ConfigManager;
import engine.gameManager.PowersManager;
import engine.powers.DamageShield;
import engine.powers.EffectsBase;
import engine.powers.effectmodifiers.AbstractEffectModifier;

import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;


public class PlayerBonuses {

    //First bonus set
    private ConcurrentHashMap<AbstractEffectModifier, Float> bonusFloats = new ConcurrentHashMap<>();
    private ConcurrentHashMap<AbstractEffectModifier, DamageShield> bonusDamageShields = new ConcurrentHashMap<>();
    private ConcurrentHashMap<AbstractEffectModifier, String> bonusStrings = new ConcurrentHashMap<>();
    private ConcurrentHashMap<ModType, HashSet<SourceType>> bonusLists = new ConcurrentHashMap<>();
    private ConcurrentHashMap<ModType, HashMap<SourceType, Boolean>> bonusBools = new ConcurrentHashMap<>();
    private ConcurrentHashMap<SourceType, Float> skillBonuses = new ConcurrentHashMap<>();
    private ConcurrentHashMap<ModType, Float> regens = new ConcurrentHashMap<>();

    //If active == 0 then all gets come from the A list and all puts go to the B list
    //If active == 1 then all gets come from the B list and all puts go to the A list
    //They alternate each time bonuses are calculated so the one being updated isn't read from.


    /**
     * Generic Constructor
     */
    public PlayerBonuses(PlayerCharacter pc) {
    }

    public PlayerBonuses(Mob mob) {
        clearRuneBaseEffects();
    }

    public static void InitializeBonuses(PlayerCharacter player) {
        if (player.bonuses == null)
            return;
        if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER))
            return;

        player.bonuses.calculateRuneBaseEffects(player);
    }

    public static PlayerBonuses grantBonuses(AbstractCharacter ac) {

        if (ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))
            return new PlayerBonuses((PlayerCharacter) ac);
        else if (ac.getObjectType().equals(Enum.GameObjectType.Mob))
            return new PlayerBonuses((Mob) ac);
        else
            return null;
    }

    public void clearRuneBaseEffects() {

        this.bonusBools.clear();
        this.bonusFloats.clear();
        this.bonusStrings.clear();
        this.bonusDamageShields.clear();
        this.bonusLists.clear();
        this.skillBonuses.clear();
        this.regens.put(ModType.HealthRecoverRate, (float) 1);
        this.regens.put(ModType.ManaRecoverRate, (float) 1);
        this.regens.put(ModType.StaminaRecoverRate, (float) 1);
    }


    public void calculateRuneBaseEffects(PlayerCharacter pc) {
        //Clear everything
        clearRuneBaseEffects();

        //recalculate race


        if (pc.getRace() != null) {


            if (pc.getRace().getEffectsList() != null)
                for (MobBaseEffects raceEffect : pc.getRace().getEffectsList()) {
                    EffectsBase eb = PowersManager.getEffectByToken(raceEffect.getToken());

                    if (eb == null)
                        continue;
                    if (pc.getLevel() < raceEffect.getReqLvl())
                        continue;
                    for (AbstractEffectModifier modifier : eb.getModifiers()) {
                        modifier.applyBonus(pc, raceEffect.getRank());
                    }

                }

            if (SkillsBase.runeSkillsCache.containsKey(pc.getRaceID())) {
                for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getRaceID()).keySet()) {
                    float amount = SkillsBase.runeSkillsCache.get(pc.getRaceID()).get(skillToken);

                    SkillsBase sb = SkillsBase.tokenCache.get(skillToken);

                    if (sb == null)
                        continue;
                    if (this.skillBonuses.containsKey(sb.sourceType) == false)
                        this.skillBonuses.put(sb.sourceType, amount);
                    else
                        this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount);

                }
            }
        }

        //calculate baseclass effects
        if (pc.getBaseClass() != null) {

            if (pc.getBaseClass().getEffectsList() != null)
                for (MobBaseEffects classEffect : pc.getBaseClass().getEffectsList()) {
                    EffectsBase eb = PowersManager.getEffectByToken(classEffect.getToken());

                    if (eb == null)
                        continue;
                    if (pc.getLevel() < classEffect.getReqLvl())
                        continue;
                    for (AbstractEffectModifier modifier : eb.getModifiers()) {
                        modifier.applyBonus(pc, classEffect.getRank());
                    }

                }

            if (SkillsBase.runeSkillsCache.containsKey(pc.getBaseClassID())) {
                for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getBaseClassID()).keySet()) {
                    float amount = SkillsBase.runeSkillsCache.get(pc.getBaseClassID()).get(skillToken);

                    SkillsBase sb = SkillsBase.tokenCache.get(skillToken);

                    if (sb == null)
                        continue;
                    if (this.skillBonuses.containsKey(sb.sourceType) == false)
                        this.skillBonuses.put(sb.sourceType, amount);
                    else
                        this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount);
                }
            }

        }

        //calculate promotionClass Effects
        if (pc.getPromotionClass() != null) {
            if (pc.getPromotionClass().getEffectsList() != null)
                for (MobBaseEffects promoEffect : pc.getPromotionClass().getEffectsList()) {
                    EffectsBase eb = PowersManager.getEffectByToken(promoEffect.getToken());

                    if (eb == null)
                        continue;
                    if (pc.getLevel() < promoEffect.getReqLvl())
                        continue;
                    for (AbstractEffectModifier modifier : eb.getModifiers()) {
                        modifier.applyBonus(pc, promoEffect.getRank());
                    }

                }

            if (SkillsBase.runeSkillsCache.containsKey(pc.getPromotionClassID())) {
                for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getPromotionClassID()).keySet()) {
                    float amount = SkillsBase.runeSkillsCache.get(pc.getPromotionClassID()).get(skillToken);

                    SkillsBase sb = SkillsBase.tokenCache.get(skillToken);

                    if (sb == null)
                        continue;
                    if (this.skillBonuses.containsKey(sb.sourceType) == false)
                        this.skillBonuses.put(sb.sourceType, amount);
                    else
                        this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount);

                }
            }

        }

        for (CharacterRune runes : pc.getRunes()) {
            RuneBase characterRune = RuneBase.getRuneBase(runes.getRuneBaseID());

            if (characterRune.getEffectsList() != null)
                for (MobBaseEffects runeEffect : characterRune.getEffectsList()) {
                    EffectsBase eb = PowersManager.getEffectByToken(runeEffect.getToken());

                    if (eb == null)
                        continue;
                    if (pc.getLevel() < runeEffect.getReqLvl())
                        continue;
                    for (AbstractEffectModifier modifier : eb.getModifiers()) {
                        modifier.applyBonus(pc, runeEffect.getRank());
                    }

                }

            if (SkillsBase.runeSkillsCache.containsKey(runes.getRuneBaseID())) {
                for (int skillToken : SkillsBase.runeSkillsCache.get(runes.getRuneBaseID()).keySet()) {
                    float amount = SkillsBase.runeSkillsCache.get(runes.getRuneBaseID()).get(skillToken);

                    SkillsBase sb = SkillsBase.tokenCache.get(skillToken);

                    if (sb == null)
                        continue;
                    if (this.skillBonuses.containsKey(sb.sourceType) == false)
                        this.skillBonuses.put(sb.sourceType, amount);
                    else
                        this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount);

                }
            }

        }

        //Update seeInvis if needed

        float seeInvis = this.getFloat(ModType.SeeInvisible, SourceType.None);
        if (pc.getSeeInvis() < seeInvis)
            pc.setSeeInvis((short) seeInvis);

    }


    public void grantEffect(RuneBaseEffect rbe) {
    }


    public void setFloat(AbstractEffectModifier mod, float val) {
        if (val != 0)
            this.bonusFloats.put(mod, val);
        else
            this.bonusFloats.remove(mod);
    }

    public void setString(AbstractEffectModifier mod, String val) {
        if (!val.isEmpty())
            this.bonusStrings.put(mod, val);
        else
            this.bonusStrings.remove(mod);
    }

    public void setList(ModType mod, HashSet<SourceType> val) {
        if (!val.equals(null))
            this.bonusLists.put(mod, val);
        else
            this.bonusLists.remove(mod);
    }


    public void addFloat(AbstractEffectModifier mod, Float val) {
        if (this.bonusFloats.containsKey(mod) == false)
            this.bonusFloats.put(mod, val);
        else
            this.bonusFloats.put(mod, this.bonusFloats.get(mod) + val);
    }

    public void multFloat(AbstractEffectModifier mod, Float val) {
        if (this.bonusFloats.containsKey(mod) == false)
            this.bonusFloats.put(mod, val);
        else
            this.bonusFloats.put(mod, this.bonusFloats.get(mod) + (val * (this.bonusFloats.get(mod) + val)));
    }

    public void multRegen(ModType mod, Float val) {
        this.regens.put(mod, this.regens.get(mod) + (this.regens.get(mod) * val));
    }


    public boolean getBool(ModType modType, SourceType sourceType) {

        if (this.bonusBools.containsKey(modType) == false)
            return false;

        if (this.bonusBools.get(modType).containsKey(sourceType) == false)
            return false;

        return this.bonusBools.get(modType).get(sourceType);

    }

    public float getSkillBonus(SourceType sourceType) {

        if (this.skillBonuses.containsKey(sourceType) == false)
            return 0;
        return this.skillBonuses.get(sourceType);
    }


    public float getFloat(ModType modType, SourceType sourceType) {
        float amount = 0;
        for (AbstractEffectModifier mod : this.bonusFloats.keySet()) {
            if (mod.getPercentMod() != 0)
                continue;
            if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
                continue;

            if (this.bonusFloats.get(mod) == null)
                continue;

            amount += this.bonusFloats.get(mod);
        }
        return amount;
    }

    public float getFloatPercentPositive(ModType modType, SourceType sourceType) {
        float amount = 0;
        for (AbstractEffectModifier mod : this.bonusFloats.keySet()) {

            if (mod.getPercentMod() == 0 && !modType.equals(ModType.AdjustAboveDmgCap))
                continue;


            if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
                continue;


            if (this.bonusFloats.get(mod) == null)
                continue;

            if (this.bonusFloats.get(mod) < 0)
                continue;
            amount += this.bonusFloats.get(mod);
        }

        return amount;
    }

    public float getFloatPercentAll(ModType modType, SourceType sourceType) {
        float amount = 0;
        for (AbstractEffectModifier mod : this.bonusFloats.keySet()) {

            if (mod.getPercentMod() == 0 && !modType.equals(ModType.AdjustAboveDmgCap))
                continue;


            if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
                continue;

            if (this.bonusFloats.get(mod) == null)
                continue;

            amount += this.bonusFloats.get(mod);
        }

        return amount;
    }

    public float getRegen(ModType modType) {
        return this.regens.get(modType);
    }


    public float getFloatPercentNullZero(ModType modType, SourceType sourceType) {
        float amount = 0;
        for (AbstractEffectModifier mod : this.bonusFloats.keySet()) {

            if (mod.getPercentMod() == 0)
                continue;
            if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
                continue;

            if (this.bonusFloats.get(mod) == null)
                continue;

            amount += this.bonusFloats.get(mod);
        }
        return amount;
    }

    public float getFloatPercentNegative(ModType modType, SourceType sourceType) {
        float amount = 0;
        for (AbstractEffectModifier mod : this.bonusFloats.keySet()) {

            if (mod.getPercentMod() == 0)
                continue;
            if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
                continue;

            if (this.bonusFloats.get(mod) == null)
                continue;

            if (this.bonusFloats.get(mod) > 0)
                continue;


            amount += this.bonusFloats.get(mod);
        }
        return amount;
    }


    public HashSet<SourceType> getList(ModType modType) {
        if (this.bonusLists.containsKey(modType))
            return this.bonusLists.get(modType);
        else
            return null;
    }

    public ConcurrentHashMap<AbstractEffectModifier, DamageShield> getDamageShields() {
        return this.bonusDamageShields;
    }

    public void addDamageShield(AbstractEffectModifier mod, DamageShield ds) {
        this.bonusDamageShields.put(mod, ds);
    }


    public void updateIfHigher(AbstractEffectModifier mod, Float val) {

        if (this.bonusFloats.containsKey(mod) == false) {
            this.bonusFloats.put(mod, val);
            return;
        }
        float oldVal = this.getFloat(mod.modType, mod.sourceType);

        if (oldVal > val)
            return;

        this.bonusFloats.put(mod, val);

    }


    //Read maps

    public void printBonusesToClient(PlayerCharacter pc) {


        for (ModType modType : this.bonusBools.keySet()) {
            for (SourceType sourceType : this.bonusBools.get(modType).keySet()) {
                ChatManager.chatSystemInfo(pc, modType.name() + "-" + sourceType.name() + " = " + this.bonusBools.get(modType).get(sourceType));
            }
        }

        for (ModType modType : ModType.values()) {

            if (modType.equals(ModType.StaminaRecoverRate) || modType.equals(ModType.HealthRecoverRate) || modType.equals(ModType.ManaRecoverRate))
                ChatManager.chatSystemInfo(pc, modType.name() + " = " + this.getRegen(modType));
            else
                for (SourceType sourceType : SourceType.values()) {
                    float amount = this.getFloat(modType, sourceType);
                    float percentAmount = this.getFloatPercentPositive(modType, sourceType);
                    float percentAmountNegative = this.getFloatPercentNegative(modType, sourceType);

                    if (amount != 0)
                        ChatManager.chatSystemInfo(pc, modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + amount);

                    if (percentAmount != 0)
                        ChatManager.chatSystemInfo(pc, "Percent : " + modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + percentAmount);

                    if (percentAmountNegative != 0)
                        ChatManager.chatSystemInfo(pc, "Negative Percent : " + modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + percentAmountNegative);

                }
        }

    }


    public void setBool(ModType modType, SourceType sourceType, boolean val) {
        if (val == true) {

            if (this.bonusBools.get(modType) == null) {
                HashMap<SourceType, Boolean> sourceMap = new HashMap<>();
                this.bonusBools.put(modType, sourceMap);
            }

            this.bonusBools.get(modType).put(sourceType, val);
            return;
        }

        if (this.bonusBools.containsKey(modType))
            this.bonusBools.get(modType).remove(sourceType);
    }

}