package tc.oc.commons.bukkit.util;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.bukkit.entity.AreaEffectCloud;
import org.bukkit.entity.ThrownPotion;
import org.bukkit.entity.TippedArrow;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;

import static tc.oc.commons.bukkit.util.PotionUtils.effects;

/**
 * Classification for potions that represents their harmfulness (or lack thereof). Not explicitly limited to {@link
 * org.bukkit.entity.ThrownPotion}s; other logical use cases include representation of {@link
 * org.bukkit.potion.PotionEffect}s and {@link org.bukkit.potion.PotionEffectType}s.
 */
public enum PotionClassification {
    /** Beneficial, has positive implications */
    BENEFICIAL(1),
    /** Unknown, positiveness of implications is not determinable */
    UNKNOWN(0),
    /** Harmful, has negative implications */
    HARMFUL(-1);

    private final int score;

    /** @param score Score of implications' positiveness */
    PotionClassification(final int score) {
        this.score = score;
    }

    /** An integer representation of this classification */
    public int getScore() {
        return this.score;
    }

    public PotionClassification inverse() {
        switch(this) {
            case BENEFICIAL: return HARMFUL;
            case HARMFUL: return BENEFICIAL;
            default: return this;
        }
    }

    private static final ImmutableSet<PotionEffectType> BUFFS = ImmutableSet.<PotionEffectType>builder()
        .add(PotionEffectType.ABSORPTION)
        .add(PotionEffectType.DAMAGE_RESISTANCE)
        .add(PotionEffectType.FAST_DIGGING)
        .add(PotionEffectType.FIRE_RESISTANCE)
        .add(PotionEffectType.HEAL)
        .add(PotionEffectType.HEALTH_BOOST)
        .add(PotionEffectType.INCREASE_DAMAGE)
        .add(PotionEffectType.INVISIBILITY)
        .add(PotionEffectType.JUMP)
        .add(PotionEffectType.LUCK)
        .add(PotionEffectType.NIGHT_VISION)
        .add(PotionEffectType.REGENERATION)
        .add(PotionEffectType.SATURATION)
        .add(PotionEffectType.SPEED)
        .add(PotionEffectType.WATER_BREATHING)
        .build();

    private static final ImmutableSet<PotionEffectType> BANES = ImmutableSet.<PotionEffectType>builder()
        .add(PotionEffectType.BLINDNESS)
        .add(PotionEffectType.CONFUSION)
        .add(PotionEffectType.HARM)
        .add(PotionEffectType.HUNGER)
        .add(PotionEffectType.POISON)
        .add(PotionEffectType.LEVITATION)
        .add(PotionEffectType.SLOW)
        .add(PotionEffectType.SLOW_DIGGING)
        .add(PotionEffectType.UNLUCK)
        .add(PotionEffectType.WEAKNESS)
        .add(PotionEffectType.WITHER)
        .build();

    private static final ImmutableMap<PotionEffectType, PotionClassification> CLASSIFICATIONS;

    static {
        final ImmutableMap.Builder<PotionEffectType, PotionClassification> builder = ImmutableMap.builder();
        for(PotionEffectType buff : PotionClassification.BUFFS) builder.put(buff, PotionClassification.BENEFICIAL);
        for(PotionEffectType buff : PotionClassification.BANES) builder.put(buff, PotionClassification.HARMFUL);
        CLASSIFICATIONS = builder.build();
    }

    /**
     * Scores the specified {@link PotionEffect}.
     *
     * @param effect The effect to score.
     * @return The score.
     */
    private static double getScore(final PotionEffect effect) throws IllegalArgumentException {
        return classify(effect.getType(), effect.getAmplifier()).getScore()
               * (effect.getAmplifier() < 0 ? -effect.getAmplifier() : effect.getAmplifier() + 1)
               * ((double) effect.getDuration()) / 20d;
    }

    /**
     * Scores the specified {@link PotionEffect}s.
     *
     * @param effects The potion effects to score.
     * @return The score.
     */
    private static double getScore(final Iterable<PotionEffect> effects) {
        double score = 0;
        for (PotionEffect effect : effects) {
            score += getScore(effect);
        }
        return score;
    }

    /**
     * Gets a classification for the given score. {@link #BENEFICIAL} if > 0, {@link #UNKNOWN} if == 0, {@link
     * #HARMFUL} if < 0.
     */
    private static PotionClassification fromScore(final double score) {
        return (score > 0.0 ? BENEFICIAL : (score == 0.0 ? UNKNOWN : HARMFUL));
    }

    public static PotionClassification classify(PotionEffectType type) {
        final PotionClassification c = CLASSIFICATIONS.get(type);
        return c == null ? UNKNOWN : c;
    }

    public static PotionClassification classify(PotionEffectType type, int level) {
        final PotionClassification c = classify(type);
        return level < 0 ? c.inverse() : c;
    }

    /**
     * Classifies the provided {@link PotionEffect}s as either {@link PotionClassification#BENEFICIAL}, {@link
     * PotionClassification#UNKNOWN}, or {@link PotionClassification#HARMFUL}.
     */
    public static PotionClassification classify(final Iterable<PotionEffect> effects) {
        return fromScore(getScore(effects));
    }

    public static PotionClassification classify(PotionData potion) {
        return classify(effects(potion));
    }

    public static PotionClassification classify(PotionBrew potion) {
        return classify(potion.effects());
    }

    public static PotionClassification classify(PotionMeta potion) {
        return classify(effects(potion));
    }

    public static PotionClassification classify(ItemStack potion) {
        return classify(effects(potion));
    }

    public static PotionClassification classify(final ThrownPotion potion) {
        return classify(potion.getEffects());
    }

    public static PotionClassification classify(AreaEffectCloud cloud) {
        return classify(effects(cloud));
    }

    public static PotionClassification classify(TippedArrow arrow) {
        return classify(effects(arrow));
    }
}
