package com.massivecraft.factions.listeners;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; 
import org.bukkit.util.NumberConversions;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;

import com.massivecraft.factions.Factions;
import com.massivecraft.factions.entity.MConf;
import com.massivecraft.mcore.util.SenderUtil;
import com.massivecraft.mcore.util.Txt;


public class FactionsListenerExploit implements Listener
{
	// -------------------------------------------- //
	// INSTANCE & CONSTRUCT
	// -------------------------------------------- //
	
	private static FactionsListenerExploit i = new FactionsListenerExploit();
	public static FactionsListenerExploit get() { return i; }
	
	// -------------------------------------------- //
	// SETUP
	// -------------------------------------------- //
	
	public void setup()
	{
		Bukkit.getPluginManager().registerEvents(this, Factions.get());
	}
	
	// -------------------------------------------- //
	// INTERACT SPAM
	// -------------------------------------------- //
	// TODO: Now that I decoupled this one it may be to sensitive.
	
	@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
	public void interactSpam(PlayerInteractEvent event)
	{
		if (!MConf.get().handleExploitInteractionSpam) return;
		
		Player player = event.getPlayer();
		String playerId = SenderUtil.getSenderId(player);
		
		InteractAttemptSpam attempt = interactSpammers.get(playerId);
		if (attempt == null)
		{
			attempt = new InteractAttemptSpam();
			interactSpammers.put(playerId, attempt);
		}
		
		int count = attempt.increment();
		if (count >= 10)
		{
			player.sendMessage(Txt.parse("<b>Ouch, that is starting to hurt. You should give it a rest."));
			player.damage(NumberConversions.floor((double)count / 10));
		}
	}
	
	// for handling people who repeatedly spam attempts to open a door (or similar) in another faction's territory
	private Map<String, InteractAttemptSpam> interactSpammers = new HashMap<String, InteractAttemptSpam>();
	private static class InteractAttemptSpam
	{
		private int attempts = 0;
		private long lastAttempt = System.currentTimeMillis();

		// returns the current attempt count
		public int increment()
		{
			long Now = System.currentTimeMillis();
			if (Now > lastAttempt + 2000)
				attempts = 1;
			else
				attempts++;
			lastAttempt = Now;
			return attempts;
		}
	}
	
	// -------------------------------------------- //
	// OBSIDIAN GENERATORS
	// -------------------------------------------- //
	
	@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
	public void obsidianGenerators(BlockFromToEvent event)
	{
		if (!MConf.get().handleExploitObsidianGenerators) return;

		// thanks to ObGenBlocker and WorldGuard for this method
		Block block = event.getToBlock();
		int source = event.getBlock().getTypeId();
		int target = block.getTypeId();
		if ((target == 55 || target == 132) && (source == 0 || source == 10 || source == 11))
		{
			block.setTypeId(0);
		}
	}

	// -------------------------------------------- //
	// ENDER PEARL CLIPPING
	// -------------------------------------------- //
	
	@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
	public void enderPearlClipping(PlayerTeleportEvent event)
	{
		if (!MConf.get().handleExploitEnderPearlClipping) return;
		if (event.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) return;

		// this exploit works when the target location is within 0.31 blocks or so of a door or glass block or similar...
		Location target = event.getTo();
		Location from = event.getFrom();

		// blocks who occupy less than 1 block width or length wise need to be handled differently
		Material mat = event.getTo().getBlock().getType();
		if (
				((mat == Material.THIN_GLASS || mat == Material.IRON_FENCE) && clippingThrough(target, from, 0.65))
			 || ((mat == Material.FENCE || mat == Material.NETHER_FENCE) && clippingThrough(target, from, 0.45))
			)
		{            
			event.setTo(from);
			return;
		}

		// simple fix otherwise: ender pearl target locations are standardized to be in the center (X/Z) of the target block, not at the edges
		target.setX(target.getBlockX() + 0.5);
		target.setZ(target.getBlockZ() + 0.5);
		event.setTo(target);
		
	}

	public static boolean clippingThrough(Location target, Location from, double thickness)
	{
		return
		(
			(from.getX() > target.getX() && (from.getX() - target.getX() < thickness))
		 || (target.getX() > from.getX() && (target.getX() - from.getX() < thickness))
		 || (from.getZ() > target.getZ() && (from.getZ() - target.getZ() < thickness))
		 || (target.getZ() > from.getZ() && (target.getZ() - from.getZ() < thickness))
		);
	}
	
	// -------------------------------------------- //
	// TNT WATERLOG
	// -------------------------------------------- //
	// TNT in water/lava doesn't normally destroy any surrounding blocks, which is usually desired behavior, but...
	// this optional change below provides workaround for waterwalling providing perfect protection,
	// and makes cheap (non-obsidian) TNT cannons require minor maintenance between shots
	
	@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
	public void tntWaterlog(EntityExplodeEvent event)
	{
		if (!MConf.get().handleExploitEnderPearlClipping) return;
		if (!(event.getEntity() instanceof TNTPrimed)) return;
		
		Block center = event.getLocation().getBlock();
		if (!center.isLiquid()) return;
		
		// a single surrounding block in all 6 directions is broken if the material is weak enough
		List<Block> targets = new ArrayList<Block>();
		targets.add(center.getRelative(0, 0, 1));
		targets.add(center.getRelative(0, 0, -1));
		targets.add(center.getRelative(0, 1, 0));
		targets.add(center.getRelative(0, -1, 0));
		targets.add(center.getRelative(1, 0, 0));
		targets.add(center.getRelative(-1, 0, 0));
		for (Block target : targets)
		{
			int id = target.getTypeId();
			// ignore air, bedrock, water, lava, obsidian, enchanting table, etc.... too bad we can't get a blast resistance value through Bukkit yet
			if (id != 0 && (id < 7 || id > 11) && id != 49 && id != 90 && id != 116 && id != 119 && id != 120 && id != 130)
			{
				target.breakNaturally();
			}
		}
	}
}
