package tc.oc.pgm.pickup;

import com.sk89q.minecraft.util.commands.ChatColor;
import org.bukkit.*;
import org.bukkit.entity.EnderCrystal;
import org.bukkit.entity.Entity;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.player.PlayerMoveEvent;

import java.time.Duration;
import java.time.Instant;
import tc.oc.commons.bukkit.geometry.Capsule;
import tc.oc.commons.bukkit.geometry.Sphere;
import tc.oc.pgm.cooldown.CooldownPlayerFacet;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.features.Feature;
import tc.oc.pgm.kits.KitPlayerFacet;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.Repeatable;

import java.util.Optional;

import static tc.oc.commons.core.util.TimeUtils.isEqualOrBeforeNow;
import static tc.oc.commons.core.util.TimeUtils.isInfPositive;
import static tc.oc.commons.core.util.Utils.ifInstance;

@ListenerScope(MatchScope.RUNNING)
public class Pickup implements Feature<PickupDefinition>, Listener {

    private final Match match;
    private final World world;
    private final PickupDefinition data;
    private Optional<Entity> entity;
    private Optional<Instant> spawnAt;

    public Pickup(Match match, PickupDefinition data) {
        this.match = match;
        this.world = match.getWorld();
        this.data = data;
        this.entity = Optional.empty();
        this.spawnAt = Optional.of(match.getInstantNow());
    }

    public boolean isSpawned() {
        return entity.isPresent();
    }

    public void spawn() {
        Location location = data.region().getRandom(match.getRandom()).toLocation(world);
        effects(Sound.BLOCK_NOTE_BASEDRUM, Particle.CLOUD);
        Entity entity = world.spawn(location, (Class) data.appearance().getEntityClass());
        entity.setKnockbackReduction(1);
        data.name().ifPresent(name -> {
            entity.setCustomName(ChatColor.translateAlternateColorCodes('`', name));
            entity.setCustomNameVisible(true);
        });
        switch(data.appearance()) { // TODO: Support more appearances later
            case ENDER_CRYSTAL:
                ifInstance(entity, EnderCrystal.class, crystal -> crystal.setShowingBottom(false)); break;
            case PRIMED_TNT:
                ifInstance(entity, TNTPrimed.class, tnt -> tnt.setFuseTicks(Integer.MAX_VALUE)); break;
        }
        this.entity = Optional.of(entity);
        spawnAt = Optional.empty();
    }

    public void despawn(boolean delay) {
        if(!data.respawn().equals(Duration.ZERO)) {
            entity.ifPresent(entity -> {
                entity.remove();
                effects(Sound.BLOCK_LAVA_POP, Particle.SMOKE_LARGE);
                this.entity = Optional.empty();
            });
            spawnAt = Optional.ofNullable(delay ? isInfPositive(data.respawn()) ? null : match.getInstantNow().plus(data.respawn()) : match.getInstantNow());
        } else {
            entity.ifPresent(entity -> effects(Sound.BLOCK_LAVA_POP, Particle.SLIME));
        }
    }

    private void effects(Sound sound, Particle particle) {
        entity.ifPresent(entity -> {
            if(data.effects()) {
                Cuboid box = entity.getBoundingBox();
                for(int i = 0; i < box.volume(); i++) {
                    world.spawnParticle(particle, box.randomPointInside(match.getRandom()).toLocation(world), 3, 1, 1, 1, 0);
                }
            }
            if(data.sounds()) {
                world.playSound(entity.getLocation(), sound, 1, 1);
            }
        });
    }

    @Repeatable(scope = MatchScope.LOADED)
    public void tick() {
        if(isSpawned()) {
            if(data.visible().query(match).isDenied()) {
                despawn(false);
            }
        } else {
            if(spawnAt.isPresent() && isEqualOrBeforeNow(match.getInstantNow(), spawnAt.get()) && data.visible().query(match).isAllowed()) {
                spawn();
            }
        }
    }

    @EventHandler(ignoreCancelled = true)
    public void onMove(PlayerMoveEvent event) {
        if(!isSpawned()) return;
        MatchPlayer player = match.getPlayer(event.getPlayer());
        if(player != null) {
            CooldownPlayerFacet cooler = player.facet(CooldownPlayerFacet.class);
            if(player.canInteract() &&
               cooler.isNotCooling(this) &&
               Capsule.fromEndpointsAndRadius(event.getFrom().position(),
                                              event.getTo().position(),
                                              0.5)
                      .intersects(Sphere.fromCircumscribedCuboid(entity.get().getBoundingBox())) &&
               data.pickup().query(player).isAllowed()) {

                cooler.coolFor(this, data.cooldown());
                despawn(true);
                player.facet(KitPlayerFacet.class).applyKit(data.kit(), false);
            }
        }
    }


    @EventHandler(ignoreCancelled = true)
    public void onDamage(EntityDamageEvent event) {
        if(isSpawned() && event.getEntity().equals(entity.get())) {
            event.setCancelled(true);
        }
    }

    @EventHandler(ignoreCancelled = true)
    public void onDeath(EntityDeathEvent event) {
        if(isSpawned() && event.getEntity().equals(entity.get())) {
            event.setDroppedExp(0);
            event.getDrops().clear();
            despawn(false);
        }
    }

    @Override
    public PickupDefinition getDefinition() {
        return data;
    }

}
