package tc.oc.pgm.core;

import static tc.oc.pgm.api.map.MapProtos.MODES_IMPLEMENTATION_VERSION;

import java.util.List;
import java.util.concurrent.TimeUnit;
import net.kyori.text.TranslatableComponent;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.minecart.ExplosiveMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.util.Vector;
import tc.oc.pgm.api.event.BlockTransformEvent;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.match.MatchModule;
import tc.oc.pgm.api.match.MatchScope;
import tc.oc.pgm.api.party.Competitor;
import tc.oc.pgm.api.player.MatchPlayer;
import tc.oc.pgm.api.player.ParticipantState;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.events.ParticipantBlockTransformEvent;
import tc.oc.pgm.goals.events.GoalCompleteEvent;
import tc.oc.pgm.goals.events.GoalStatusChangeEvent;
import tc.oc.pgm.modes.ObjectiveModeChangeEvent;
import tc.oc.pgm.util.block.BlockVectors;

@ListenerScope(MatchScope.RUNNING)
public class CoreMatchModule implements MatchModule, Listener {

  protected final Match match;
  protected final List<Core> cores;

  public CoreMatchModule(Match match, List<Core> cores) {
    this.match = match;
    this.cores = cores;
  }

  @Override
  public void enable() {
    if (this.match.getMap().getProto().isOlderThan(MODES_IMPLEMENTATION_VERSION)) {
      CoreConvertMonitor ccm = new CoreConvertMonitor(this);

      match.getExecutor(MatchScope.RUNNING).schedule(ccm, 15, TimeUnit.MINUTES);
      match.getExecutor(MatchScope.RUNNING).schedule(ccm, 20, TimeUnit.MINUTES);
    }
  }

  @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
  public void leakCheck(final BlockTransformEvent event) {
    if (event.getWorld() != this.match.getWorld()) return;

    if (event.getNewState().getType() == Material.STATIONARY_LAVA) {
      Vector blockVector = BlockVectors.center(event.getNewState()).toVector();
      for (Core core : this.cores) {
        if (!core.hasLeaked() && core.getLeakRegion().contains(blockVector)) {
          // core has leaked
          core.markLeaked();
          this.match.callEvent(new CoreLeakEvent(this.match, core, event.getNewState()));
          this.match.callEvent(
              new GoalCompleteEvent(
                  this.match, core, core.getOwner(), false, core.getContributions()));
        }
      }
    }
  }

  @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
  public void breakCheck(final BlockTransformEvent event) {
    if (event.getWorld() != this.match.getWorld()) return;
    ParticipantState player = ParticipantBlockTransformEvent.getPlayerState(event);

    Vector blockVector = BlockVectors.center(event.getNewState()).toVector();

    for (Core core : this.cores) {
      if (!core.hasLeaked() && core.getCasingRegion().contains(blockVector)) {
        if (event.getNewState().getType() == Material.AIR) {
          if (player != null) {
            Competitor team = player.getParty();

            if (team == core.getOwner()) {
              event.setCancelled(true, TranslatableComponent.of("core.damageOwn"));
            } else if (event.getOldState().getData().equals(core.getMaterial())) {
              this.match.callEvent(new CoreBlockBreakEvent(core, player, event.getOldState()));
              core.touch(player);

              // Note: team may not have touched a broken core if a different team broke it
              if (!core.isCompleted(team) && !core.hasTouched(team)) {
                this.match.callEvent(new GoalStatusChangeEvent(this.match, core));
              }
            }
          } else if (event.getCause() instanceof EntityExplodeEvent) {
            // this is a temp fix until there is a tracker for placed minecarts (only dispensed are
            // tracked right now)
            if (((EntityExplodeEvent) event.getCause()).getEntity() instanceof ExplosiveMinecart) {
              event.setCancelled(true);
            }
          } else if (event.getCause() instanceof BlockPistonRetractEvent) {
            event.setCancelled(true);
          }
        } else if (event.getCause() instanceof BlockPistonExtendEvent) {
          event.setCancelled(true);
        } else if (event.getCause() instanceof BlockDispenseEvent) {
          event.setCancelled(true);
        }
        break;
      }
    }
  }

  @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
  public void damageCheck(BlockDamageEvent event) {
    Block block = event.getBlock();
    if (block.getWorld() != this.match.getWorld()) return;
    MatchPlayer player = this.match.getPlayer(event.getPlayer());
    Vector center = BlockVectors.center(block).toVector();

    for (Core core : this.cores) {
      if (!core.hasLeaked()
          && core.getCasingRegion().contains(center)
          && player.getParty() == core.getOwner()) {
        event.setCancelled(true);
        player.sendWarning(TranslatableComponent.of("core.damageOwn"));
      }
    }
  }

  @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
  public void lavaProtection(final BlockTransformEvent event) {
    if (event.getWorld() != this.match.getWorld()) return;

    Vector blockVector = BlockVectors.center(event.getNewState()).toVector();
    for (Core core : this.cores) {
      if (core.getLavaRegion().contains(blockVector)) {
        event.setCancelled(true);
      }
    }
  }

  @EventHandler(priority = EventPriority.MONITOR)
  public void onObjectiveModeSwitch(final ObjectiveModeChangeEvent event) {
    for (Core core : this.cores) {
      if (core.isAffectedByModeChanges()) {
        core.replaceBlocks(event.getMode().getMaterialData());
      }
    }
  }
}