package com.rs.game.minigames.clanwars;

import java.io.Serializable;
import java.util.BitSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

import com.rs.cores.CoresManager;
import com.rs.game.WorldObject;
import com.rs.game.WorldTile;
import com.rs.game.map.MapBuilder;
import com.rs.game.player.Player;
import com.rs.game.player.content.FriendChatsManager;

/**
 * Handles the clan wars activity.
 * 
 * @author Emperor
 * 
 */
public final class ClanWars implements Serializable {

	/**
	 * The list of currently active clan wars.
	 */
	private static final List<ClanWars> currentWars = new CopyOnWriteArrayList<ClanWars>();

	/**
	 * The serial UID.
	 */
	private static final long serialVersionUID = 3329643510646371055L;

	/**
	 * The possible rules.
	 * 
	 * @author Emperor
	 * 
	 */
	public static enum Rules {
		NO_FOOD(5288), NO_POTIONS(5289), NO_PRAYER(5290), NO_MAGIC(-1), NO_MELEE(5284), NO_RANGE(5285), NO_FAMILIARS(5287), ITEMS_LOST(5283);

		/**
		 * The config id.
		 */
		private final int configId;

		/**
		 * Constructs a new {@code Rules} {@code Object}.
		 * 
		 * @param configId The config id.
		 */
		private Rules(int configId) {
			this.configId = configId;
		}
	}

	/**
	 * The first team.
	 */
	private transient final FriendChatsManager firstTeam;

	/**
	 * The second team.
	 */
	private transient final FriendChatsManager secondTeam;

	/**
	 * The list of players ingame, of the first team.
	 */
	private transient final List<Player> firstPlayers = new CopyOnWriteArrayList<Player>();

	/**
	 * The list of players ingame, of the second team.
	 */
	private transient final List<Player> secondPlayers = new CopyOnWriteArrayList<Player>();

	/**
	 * The wall objects list.
	 */
	private transient List<WorldObject> wallObjects;

	/**
	 * The victory type for this war.
	 */
	private transient int victoryType = -1;

	/**
	 * The amount of time left.
	 */
	private transient int timeLeft = -1;

	/**
	 * The current magic rule's counter.
	 */
	private transient int magicRuleCount;

	/**
	 * The current area type.
	 */
	private transient AreaType areaType = AreaType.CLASSIC_AREA;

	/**
	 * A bit set containing the rules which have been activated.
	 */
	private transient final BitSet rules = new BitSet();

	/**
	 * The base location used during this war.
	 */
	private transient WorldTile baseLocation;

	/**
	 * The current clan wars timer instance.
	 */
	private transient ClanWarsTimer timer;

	/**
	 * The amount of kills done.
	 */
	private transient int kills = 0;

	/**
	 * Constructs a new {@code ClanWars} {@code Object}.
	 * 
	 * @param first The first team.
	 * @param second The second team.
	 */
	public ClanWars(FriendChatsManager first, FriendChatsManager second) {
		this.firstTeam = first;
		this.secondTeam = second;
	}

	/**
	 * Flags a rule if the rule was previously inactivated, unflags the rule if the rule was previously activated.
	 * 
	 * @param rule The rule to switch.
	 * @param player The player switching the rule.
	 */
	public void switchRule(Rules rule, Player player) {
		Player other = (Player) player.getTemporaryAttributtes().get("clan_request_p");
		if (other == null || player.getTemporaryAttributtes().get("clan_wars") != other.getTemporaryAttributtes().get("clan_wars")) {
			return;
		}
		if (rule == Rules.NO_MAGIC) {
			if (get(Rules.NO_RANGE) && get(Rules.NO_MELEE)) {
				player.getSocialManager().sendGameMessage("You can't activate all combat style rules, how would you fight?");
				return;
			} else {
				magicRuleCount = ++magicRuleCount % 4;
			}
			sendConfig(player, other, 5286, magicRuleCount);
			return;
		}
		if (magicRuleCount != 0 && ((rule == Rules.NO_MELEE && get(Rules.NO_RANGE)) || (rule == Rules.NO_RANGE && get(Rules.NO_MELEE)))) {
			player.getSocialManager().sendGameMessage("You can't activate all combat style rules, how would you fight?");
		} else {
			rules.set(rule.ordinal(), !rules.get(rule.ordinal()));
		}
		sendConfig(player, other, rule.configId, rules.get(rule.ordinal()) ? 1 : 0);
	}

	/**
	 * Sends a config to both the players.
	 * 
	 * @param player The first player.
	 * @param other The other player.
	 * @param configId The config id.
	 * @param value The value.
	 */
	public static void sendConfig(Player player, Player other, int configId, int value) {
		boolean resetAccept = false;
		if (player.getTemporaryAttributtes().get("accepted_war_terms") == Boolean.TRUE) {
			player.getTemporaryAttributtes().remove("accepted_war_terms");
			resetAccept = true;
		}
		if (other.getTemporaryAttributtes().get("accepted_war_terms") == Boolean.TRUE) {
			other.getTemporaryAttributtes().remove("accepted_war_terms");
			resetAccept = true;
		}
		if (resetAccept) {
			player.getVarsManager().sendVarBit(5293, 0);
			other.getVarsManager().sendVarBit(5293, 0);
		}
		player.getVarsManager().sendVarBit(configId, value);
		other.getVarsManager().sendVarBit(configId, value);
	}

	/**
	 * Sends the victory type configuration.
	 * 
	 * @param p The player.
	 */
	private void sendVictoryConfiguration(Player p) {
		switch (victoryType) {
		case -1:
			p.getVarsManager().sendVarBit(5280, 0);
			break;
		case 25:
			p.getVarsManager().sendVarBit(5280, 1);
			break;
		case 50:
			p.getVarsManager().sendVarBit(5280, 2);
			break;
		case 100:
			p.getVarsManager().sendVarBit(5280, 3);
			break;
		case 200:
			p.getVarsManager().sendVarBit(5280, 4);
			break;
		case 400:
			p.getVarsManager().sendVarBit(5280, 5);
			break;
		case 750:
			p.getVarsManager().sendVarBit(5280, 6);
			break;
		case 1_000:
			p.getVarsManager().sendVarBit(5280, 7);
			break;
		case 2_500:
			p.getVarsManager().sendVarBit(5280, 8);
			break;
		case 5_000:
			p.getVarsManager().sendVarBit(5280, 9);
			break;
		case 10_000:
			p.getVarsManager().sendVarBit(5280, 10);
			break;
		case -2:
			p.getVarsManager().sendVarBit(5280, 15);
			break;
		}
	}

	/**
	 * Sends the time configuration.
	 * 
	 * @param p The player.
	 */
	private void sendTimeConfiguration(Player p) {
		switch (timeLeft) {
		case 500:
			p.getVarsManager().sendVarBit(5281, 1);
			break;
		case 1_000:
			p.getVarsManager().sendVarBit(5281, 2);
			break;
		case 3_000:
			p.getVarsManager().sendVarBit(5281, 3);
			break;
		case 6_000:
			p.getVarsManager().sendVarBit(5281, 4);
			break;
		case 9_000:
			p.getVarsManager().sendVarBit(5281, 5);
			break;
		case 12_000:
			p.getVarsManager().sendVarBit(5281, 6);
			break;
		case 15_000:
			p.getVarsManager().sendVarBit(5281, 7);
			break;
		case 18_000:
			p.getVarsManager().sendVarBit(5281, 8);
			break;
		case 24_000:
			p.getVarsManager().sendVarBit(5281, 9);
			break;
		case 30_000:
			p.getVarsManager().sendVarBit(5281, 10);
			break;
		case 36_000:
			p.getVarsManager().sendVarBit(5281, 11);
			break;
		case 48_000:
			p.getVarsManager().sendVarBit(5281, 12);
			break;
		case -1:
			p.getVarsManager().sendVarBit(5281, 0);
			break;
		}
	}

	/**
	 * Checks if a rule has been activated.
	 * 
	 * @param rule The rule to check.
	 * @return {@code True} if so.
	 */
	public boolean get(Rules rule) {
		return rules.get(rule.ordinal());
	}

	/**
	 * Gets the firstTeam.
	 * 
	 * @return The firstTeam.
	 */
	public FriendChatsManager getFirstTeam() {
		return firstTeam;
	}

	/**
	 * Gets the secondTeam.
	 * 
	 * @return The secondTeam.
	 */
	public FriendChatsManager getSecondTeam() {
		return secondTeam;
	}

	/**
	 * Sends the interface for challenge request.
	 * 
	 * @param p The player to send to interface to.
	 * @param other
	 */
	public void sendInterface(final Player p, final Player other) {
		p.getTemporaryAttributtes().put("clan_wars", this);
		p.getInterfaceManager().sendInterface(791);
		p.getPackets().sendIComponentSettings(791, 141, 0, 63, 0);
		p.getVarsManager().sendVarBit(5291, 0);
		p.getVarsManager().sendVarBit(5292, 0);
		p.getVarsManager().sendVarBit(5293, 0);
		p.setCloseInterfacesEvent(new Runnable() {

			@Override
			public void run() {
				p.getTemporaryAttributtes().remove("accepted_war_terms");
				other.getTemporaryAttributtes().remove("accepted_war_terms");
				p.getTemporaryAttributtes().remove("clan_request_p");
				other.getTemporaryAttributtes().remove("clan_request_p");
				p.getTemporaryAttributtes().remove("clan_wars");
				other.getTemporaryAttributtes().remove("clan_wars");
			}
		});
	}

	/**
	 * Called when the player accepts the challenge terms.
	 * 
	 * @param player The player.
	 */
	public void accept(final Player player) {
		final Player other = (Player) player.getTemporaryAttributtes().get("clan_request_p");
		if (other != null && (Boolean) other.getTemporaryAttributtes().get("accepted_war_terms") == Boolean.TRUE) {
			CoresManager.slowExecutor.submit(new Runnable() {
				@Override
				public void run() {
					player.getTemporaryAttributtes().remove("accepted_war_terms");
					other.getTemporaryAttributtes().remove("accepted_war_terms");
					player.setCloseInterfacesEvent(null);
					other.setCloseInterfacesEvent(null);
					player.getInterfaceManager().removeScreenInterface();
					other.getInterfaceManager().removeScreenInterface();
					for (Player p : firstTeam.getPlayers()) {
						if (p != player && p != other) {
							p.getSocialManager().sendGameMessage("<col=FF0000>Your clan has been challenged to a clan war!</col>");
							p.getSocialManager().sendGameMessage("<col=FF0000>Step through the purple portal in the Challenge Hall.</col>");
							p.getSocialManager().sendGameMessage("<col=FF0000>Battle will commence in 2 minutes.</col>");
						}
					}
					for (Player p : secondTeam.getPlayers()) {
						if (p != player && p != other) {
							p.getSocialManager().sendGameMessage("<col=FF0000>Your clan has been challenged to a clan war!</col>");
							p.getSocialManager().sendGameMessage("<col=FF0000>Step through the purple portal in the Challenge Hall.</col>");
							p.getSocialManager().sendGameMessage("<col=FF0000>Battle will commence in 2 minutes.</col>");
						}
					}
					firstTeam.setClanWars(ClanWars.this);
					secondTeam.setClanWars(ClanWars.this);
					int width = (areaType.getNorthEastTile().getX() - areaType.getSouthWestTile().getX()) / 8 + 1;
					int height = (areaType.getNorthEastTile().getY() - areaType.getSouthWestTile().getY()) / 8 + 1;
					int[] newCoords = MapBuilder.findEmptyChunkBound(width, height);
					MapBuilder.copyAllPlanesMap(areaType.getSouthWestTile().getChunkX(), areaType.getSouthWestTile().getChunkY(), newCoords[0], newCoords[1], width, height);
					baseLocation = new WorldTile(newCoords[0] << 3, newCoords[1] << 3, 0);
					WallHandler.loadWall(ClanWars.this);
					CoresManager.fastExecutor.scheduleAtFixedRate(timer = new ClanWarsTimer(ClanWars.this), 600, 600);
					enter(player);
					enter(other);
					currentWars.add(ClanWars.this);
				}
			});
			return;
		}
		player.getTemporaryAttributtes().put("accepted_war_terms", true);
	}

	/**
	 * Enters the purple portal.
	 * 
	 * @param p The player.
	 */
	public static void enter(Player p) {
		boolean hasWar = p.getCurrentFriendChat() != null && p.getCurrentFriendChat().getClanWars() != null;
		final ClanWars c = hasWar ? p.getCurrentFriendChat().getClanWars() : (ClanWars) p.getTemporaryAttributtes().get("view_clan");
		if (c == null || c.timer == null) {
			return;
		}
		c.sendVictoryConfiguration(p);
		c.sendTimeConfiguration(p);
		p.getPackets().sendGlobalConfig(271, hasWar ? 1 : 0);
		p.getInterfaceManager().setOverlay(265, false);
		if (hasWar && c.timer.isStarted() && c.isKnockOut()) {
			hasWar = false;
			p.getSocialManager().sendGameMessage("The war has already started!");
			p.getTemporaryAttributtes().put("view_prefix", c.firstTeam == p.getCurrentFriendChat() ? 0 : 1);
		}
		if (hasWar) {
			if (c.get(Rules.NO_FAMILIARS) && p.getFamiliar() != null) {
				p.getSocialManager().sendGameMessage("You can't enter the clan war with a familiar.");
				return;
			}
			if (c.get(Rules.NO_PRAYER)) {
				p.getPrayer().closeAllPrayers();
			}
			if (c.firstTeam == p.getCurrentFriendChat()) {
				p.setNextWorldTile(c.baseLocation.transform(c.areaType.getFirstSpawnOffsetX(), c.areaType.getFirstSpawnOffsetY(), 0));
				c.firstPlayers.add(p);
				c.timer.refresh(p, true);
			} else {
				WorldTile northEast = c.baseLocation.transform(c.areaType.getNorthEastTile().getX() - c.areaType.getSouthWestTile().getX(), c.areaType.getNorthEastTile().getY() - c.areaType.getSouthWestTile().getY(), 0);
				p.setNextWorldTile(northEast.transform(c.areaType.getSecondSpawnOffsetX(), c.areaType.getSecondSpawnOffsetY(), 0));
				c.secondPlayers.add(p);
				c.timer.refresh(p, false);
			}
			p.getControlerManager().startControler("clan_war", c);
			CoresManager.slowExecutor.submit(new Runnable() {
				@Override
				public void run() {
					for (Player player : c.firstPlayers) {
						c.timer.refresh(player, true);
					}
					for (Player player : c.secondPlayers) {
						c.timer.refresh(player, false);
					}
				}
			});
			return;
		}
	}

	public void leave(Player p, boolean ingame) {
		if (firstPlayers.contains(p))
			firstPlayers.remove(p);
		else if (secondPlayers.contains(p))
			secondPlayers.remove(p);
		else
			return;
		p.setCanPvp(false);
		p.sendDefaultPlayersOptions();
		p.setNextWorldTile(new WorldTile(2992, 9676, 0));
		p.getInterfaceManager().removeOverlay(false);
		p.setForceMultiArea(false);
		updateWar();
	}

	/**
	 * Updates the war.
	 */
	public void updateWar() {
		if (timer.isStarted() && isKnockOut()) {
			if (firstPlayers.size() < 1 || secondPlayers.size() < 1) {
				timer.cancel();
				endWar();
			}
		} else if (timer.isStarted() && !isMostKills() && ((kills & 0xFFFF) >= victoryType || (kills >> 24 & 0xFFFF) >= victoryType)) {
			timer.cancel();
			endWar();
		} else {
			for (Player p : firstPlayers) {
				timer.refresh(p, true);
			}
			for (Player p : secondPlayers) {
				timer.refresh(p, false);
			}
		}
	}

	/**
	 * Ends the current war.
	 */
	public void endWar() {
		currentWars.remove(this);
		firstTeam.setClanWars(null);
		secondTeam.setClanWars(null);
		WorldTile target = new WorldTile(2992, 9676, 0);
		int firstType;
		int secondType;
		if (timer.isTimeOut()) {
			firstType = 1;
			secondType = 1;
		} else if (isKnockOut() && firstPlayers.size() == secondPlayers.size()) {
			firstType = 3;
			secondType = 3;
		} else if (isMostKills() && (kills >> 24 & 0xFFFF) == (kills & 0xFFFF)) {
			firstType = 2;
			secondType = 2;
		} else if (isKnockOut()) {
			boolean firstWon = firstPlayers.size() > secondPlayers.size();
			firstType = firstWon ? 4 : 8 + (timer.getTimeLeft() == 0 ? 3 : 0);
			secondType = firstWon ? 8 : 4 + (timer.getTimeLeft() == 0 ? 3 : 0);
		} else if (isMostKills()) {
			boolean firstWon = (kills & 0xFFFF) > (kills >> 24 & 0xFFFF);
			firstType = firstWon ? 6 : 10;
			secondType = firstWon ? 10 : 6;
		} else {
			if ((kills & 0xFFFF) >= victoryType) {
				firstType = 5;
				secondType = 9;
			} else if ((kills >> 24 & 0xFFFF) >= victoryType) {
				firstType = 9;
				secondType = 5;
			} else if ((kills >> 24 & 0xFFFF) == (kills & 0xFFFF)) {
				firstType = 2;
				secondType = 2;
			} else if ((kills & 0xFFFF) > (kills >> 24 & 0xFFFF)) {
				firstType = 6;
				secondType = 10;
			} else {
				firstType = 10;
				secondType = 6;
			}
		}
		for (Player player : firstPlayers) {
			player.useStairs(-1, target, 0, 1);
			boolean resized = player.getInterfaceManager().hasRezizableScreen();
			player.getInterfaceManager().removeInterfaceByParent(resized ? 746 : 548, resized ? 1 : 11);
			player.getInterfaceManager().sendInterface(790);
			player.getPackets().sendGlobalConfig(268, firstType);
			player.getControlerManager().startControler("clan_wars_request");
			player.setForceMultiArea(true);
			player.stopAll(true, false);
			player.reset();
		}
		for (Player player : secondPlayers) {
			player.useStairs(-1, target, 0, 1);
			boolean resized = player.getInterfaceManager().hasRezizableScreen();
			player.getInterfaceManager().removeInterfaceByParent(resized ? 746 : 548, resized ? 1 : 11);
			player.getInterfaceManager().sendInterface(790);
			player.getPackets().sendGlobalConfig(268, secondType);
			player.getControlerManager().startControler("clan_wars_request");
			player.setForceMultiArea(true);
			player.stopAll(true, false);
			player.reset();
		}
		String firstMessage = "Your clan " + (firstType < 4 ? "drawed." : firstType < 8 ? "is victorious!" : "has been defeated!");
		String secondMessage = "Your clan " + (secondType < 4 ? "drawed." : secondType < 8 ? "is victorious!" : "has been defeated!");
		for (Player player : firstTeam.getPlayers()) {
			player.getSocialManager().sendGameMessage(firstMessage);
		}
		for (Player player : secondTeam.getPlayers()) {
			player.getSocialManager().sendGameMessage(secondMessage);
		}
		CoresManager.slowExecutor.schedule(new Runnable() {
			@Override
			public void run() {
				int width = (areaType.getNorthEastTile().getX() - areaType.getSouthWestTile().getX()) / 8 + 1;
				int height = (areaType.getNorthEastTile().getY() - areaType.getSouthWestTile().getY()) / 8 + 1;
				MapBuilder.destroyMap(baseLocation.getChunkX(), baseLocation.getChunkY(), width, height);
			}
		}, 1200, TimeUnit.MILLISECONDS);
	}

	/**
	 * Gets the victoryType.
	 * 
	 * @return The victoryType.
	 */
	public int getVictoryType() {
		return victoryType;
	}

	/**
	 * Checks if the victory type is knock-out.
	 * 
	 * @return {@code True} if so (thus victory type equals {@code -1}).
	 */
	public boolean isKnockOut() {
		return victoryType == -1;
	}

	/**
	 * Checks if the victory type is most kills.
	 * 
	 * @return {@code True} if so (thus victory type equals {@code -2}).
	 */
	public boolean isMostKills() {
		return victoryType == -2;
	}

	/**
	 * Sets the victoryType.
	 * 
	 * @param victoryType The victoryType to set.
	 * @param p The player.
	 * @param other The other player.
	 */
	public void setVictoryType(int victoryType, Player p, Player other) {
		this.victoryType = victoryType;
		sendVictoryConfiguration(p);
		sendVictoryConfiguration(other);
	}

	/**
	 * Gets the timeLeft.
	 * 
	 * @return The timeLeft.
	 */
	public int getTimeLeft() {
		return timeLeft;
	}

	/**
	 * Sets the timeLeft.
	 * 
	 * @param timeLeft The timeLeft to set.
	 * @param p The player.
	 * @param other The other player.
	 */
	public void setTimeLeft(int timeLeft, Player p, Player other) {
		this.timeLeft = timeLeft;
		sendTimeConfiguration(p);
		sendTimeConfiguration(other);
	}

	/**
	 * Gets the clan wars timer.
	 * 
	 * @return The clan wars timer.
	 */
	public ClanWarsTimer getTimer() {
		return timer;
	}

	/**
	 * Gets the areaType.
	 * 
	 * @return The areaType.
	 */
	public AreaType getAreaType() {
		return areaType;
	}

	/**
	 * Sets the areaType.
	 * 
	 * @param areaType The areaType to set.
	 */
	public void setAreaType(AreaType areaType) {
		this.areaType = areaType;
	}

	/**
	 * Gets the magicRuleCount.
	 * 
	 * @return The magicRuleCount.
	 */
	public int getMagicRuleCount() {
		return magicRuleCount;
	}

	/**
	 * Sets the magicRuleCount.
	 * 
	 * @param magicRuleCount The magicRuleCount to set.
	 */
	public void setMagicRuleCount(int magicRuleCount) {
		this.magicRuleCount = magicRuleCount;
	}

	/**
	 * Gets the baseLocation.
	 * 
	 * @return The baseLocation.
	 */
	public WorldTile getBaseLocation() {
		return baseLocation;
	}

	/**
	 * Sets the baseLocation.
	 * 
	 * @param baseLocation The baseLocation to set.
	 */
	public void setBaseLocation(WorldTile baseLocation) {
		this.baseLocation = baseLocation;
	}

	/**
	 * Gets the wallObjects.
	 * 
	 * @return The wallObjects.
	 */
	public List<WorldObject> getWallObjects() {
		return wallObjects;
	}

	/**
	 * Sets the wallObjects.
	 * 
	 * @param wallObjects The wallObjects to set.
	 */
	public void setWallObjects(List<WorldObject> wallObjects) {
		this.wallObjects = wallObjects;
	}

	/**
	 * Gets the firstPlayers.
	 * 
	 * @return The firstPlayers.
	 */
	public List<Player> getFirstPlayers() {
		return firstPlayers;
	}

	/**
	 * Gets the secondPlayers.
	 * 
	 * @return The secondPlayers.
	 */
	public List<Player> getSecondPlayers() {
		return secondPlayers;
	}

	/**
	 * Gets the kills.
	 * 
	 * @return The kills.
	 */
	public int getKills() {
		return kills;
	}

	/**
	 * Sets the current kills.
	 * 
	 * @param kills The kills.
	 */
	public void setKills(int kills) {
		this.kills = kills;
	}

	/**
	 * Gets the currentwars.
	 * 
	 * @return The currentwars.
	 */
	public static List<ClanWars> getCurrentwars() {
		return currentWars;
	}
}