package tc.oc.pgm.spawns.states;

import com.google.common.collect.Sets;
import java.util.List;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.event.Event;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.material.Door;
import org.bukkit.permissions.PermissionAttachment;
import tc.oc.pgm.api.Config;
import tc.oc.pgm.api.PGM;
import tc.oc.pgm.api.match.event.MatchStartEvent;
import tc.oc.pgm.api.party.Competitor;
import tc.oc.pgm.api.player.MatchPlayer;
import tc.oc.pgm.events.PlayerJoinPartyEvent;
import tc.oc.pgm.spawns.ObserverToolFactory;
import tc.oc.pgm.spawns.Spawn;
import tc.oc.pgm.spawns.SpawnMatchModule;
import tc.oc.pgm.spawns.events.ObserverKitApplyEvent;
import tc.oc.pgm.util.block.BlockVectors;

public class Observing extends State {

  // A set of item types which, when used to interact with the match environment by non-playing
  // users, can potentially cause client-server de-sync
  private static final Set<Material> BAD_TYPES =
      Sets.newHashSet(
          Material.WATER_LILY, Material.BUCKET, Material.LAVA_BUCKET, Material.WATER_BUCKET);

  private final boolean reset;
  private final boolean teleport;
  private PermissionAttachment permissionAttachment;

  public Observing(SpawnMatchModule smm, MatchPlayer player, boolean reset, boolean teleport) {
    super(smm, player);
    this.reset = reset;
    this.teleport = teleport;
  }

  @Override
  public void enterState() {
    super.enterState();

    permissionAttachment = bukkit.addAttachment(PGM.get());
    for (Config.Group group : PGM.get().getConfiguration().getGroups()) {
      if (bukkit.hasPermission(group.getPermission())) {
        permissionAttachment.setPermission(group.getObserverPermission(), true);
      }
    }

    if (reset) player.reset();
    player.setDead(false);
    player.resetGamemode();

    Spawn spawn = smm.getDefaultSpawn();

    if (teleport) {
      Location location = spawn.getSpawn(player);
      if (location != null) {
        PlayerRespawnEvent event = new PlayerRespawnEvent(player.getBukkit(), location, false);
        player.getMatch().callEvent(event);

        player.getBukkit().teleport(event.getRespawnLocation());
      }
    }

    if (reset) {
      // Give basic observer items
      ObserverToolFactory toolFactory = smm.getObserverToolFactory();
      player.getInventory().setItem(0, toolFactory.getTeleportTool(bukkit));

      if (toolFactory.canUseEditWand(bukkit)) {
        player.getInventory().setItem(1, toolFactory.getEditWand(bukkit));
      }

      // Let other modules give observer items
      player.getMatch().callEvent(new ObserverKitApplyEvent(player));

      // Apply observer spawn kit, if there is one
      spawn.applyKit(player);
    }

    player.getBukkit().updateInventory();
    player.setVisible(true);
    player.resetGamemode();

    // The player is not standing on anything, turn their flying on
    if (bukkit.getAllowFlight()) {
      Block block = bukkit.getLocation().subtract(0, 0.1, 0).getBlock();
      if (block == null || !BlockVectors.isSupportive(block.getType())) {
        bukkit.setFlying(true);
      }
    }
  }

  @Override
  public void leaveState(List<Event> events) {
    super.leaveState(events);
    if (permissionAttachment != null) bukkit.removeAttachment(permissionAttachment);
  }

  @Override
  public void onEvent(MatchStartEvent event) {
    super.onEvent(event);
    if (player.isParticipating()) {
      transition(new Joining(smm, player));
    }
  }

  @Override
  public void onEvent(PlayerJoinPartyEvent event) {
    if (event.getNewParty() instanceof Competitor && event.getMatch().isRunning()) {
      transition(new Joining(smm, player));
    }
  }

  @Override
  public void onEvent(EntityDamageEvent event) {
    super.onEvent(event);
    event.setCancelled(true);
  }

  @Override
  public void onEvent(InventoryClickEvent event) {
    super.onEvent(event);

    if (!(event.getClickedInventory() instanceof PlayerInventory) || event.getCursor() == null)
      return;

    ItemStack item = event.getCursor();
    if (BAD_TYPES.contains(item.getType()) || item.getData() instanceof Door) {
      event.setCancelled(true);
    }
  }
}