package tc.oc.pgm.tracker.trackers;

import java.util.Map;
import java.util.WeakHashMap;
import javax.annotation.Nullable;
import javax.inject.Inject;

import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityCombustByBlockEvent;
import org.bukkit.event.entity.EntityCombustByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import tc.oc.commons.bukkit.util.Materials;
import tc.oc.pgm.events.BlockTransformEvent;
import tc.oc.pgm.events.ParticipantBlockTransformEvent;
import tc.oc.pgm.match.ParticipantState;
import tc.oc.pgm.tracker.EntityResolver;
import tc.oc.pgm.tracker.damage.BlockInfo;
import tc.oc.pgm.tracker.damage.EntityInfo;
import tc.oc.pgm.tracker.damage.FireInfo;
import tc.oc.pgm.tracker.damage.PhysicalInfo;
import tc.oc.pgm.tracker.resolvers.DamageResolver;

/**
 * - Updates the state of owned fire and lava blocks from events
 * - Tracks burning entities that were ignited by players, directly or indirectly
 * - Resolves fire tick damage to those entities
 */
public class FireTracker implements DamageResolver, Listener {

    private final EntityResolver entities;
    private final BlockTracker blocks;
    
    // An entity can be owned by one player but ignited by another, so we need an independent map for burning
    private final Map<Entity, FireInfo> burningEntities = new WeakHashMap<>();

    @Inject FireTracker(EntityResolver entities, BlockTracker blocks) {
        this.entities = entities;
        this.blocks = blocks;
    }

    @Override
    public @Nullable FireInfo resolveDamage(EntityDamageEvent.DamageCause damageType, Entity victim, @Nullable PhysicalInfo damager) {
        switch(damageType) {
            case FIRE_TICK:
                FireInfo info = resolveBurning(victim);
                if(info != null) return info;
                // fall through

            case FIRE:
            case LAVA:
                return new FireInfo(damager);
        }
        return null;
    }

    public @Nullable FireInfo resolveBurning(Entity entity) {
        return burningEntities.get(entity);
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onBlockTransform(BlockTransformEvent event) {
        boolean wasLava = Materials.isLava(event.getOldState());
        boolean isLava = Materials.isLava(event.getNewState());

        if(event.changedFrom(Material.FIRE) || (wasLava && !isLava)) {
            blocks.clearBlock(event.getBlock());
        }
        if(event instanceof ParticipantBlockTransformEvent && (event.changedTo(Material.FIRE) || (!wasLava && isLava))) {
            ParticipantState placer = ((ParticipantBlockTransformEvent) event).getPlayerState();
            blocks.trackBlockState(event.getNewState(), new FireInfo(new BlockInfo(event.getNewState(), placer)));
        }
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onEntityIgnite(EntityCombustByBlockEvent event) {
        if(event.getDuration() == 0) return;

        ParticipantState owner = blocks.getOwner(event.getCombuster());
        if(owner != null) {
            burningEntities.put(event.getEntity(), new FireInfo(blocks.resolveBlock(event.getCombuster())));
        }
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onEntityIgnite(EntityCombustByEntityEvent event) {
        if(event.getDuration() == 0) return;

        FireInfo info = resolveBurning(event.getCombuster());
        if(info != null) {
            // First, try to resolve the player who ignited the combuster
            info = new FireInfo(new EntityInfo(event.getCombuster(), info.getOwner()));
        } else {
            // If an igniter is not found, fall back to the owner of the entity
            info = new FireInfo(entities.resolveEntity(event.getCombuster()));
        }

        burningEntities.put(event.getEntity(), info);
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onEntityExtinguish(EntityExtinguishEvent event) {
        this.burningEntities.remove(event.getEntity());
    }
}
