/*
 * This file is part of the L2J Mobius project.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see .
 */
package com.l2jmobius.gameserver.model.ceremonyofchaos;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import com.l2jmobius.gameserver.enums.CeremonyOfChaosResult;
import com.l2jmobius.gameserver.instancemanager.CeremonyOfChaosManager;
import com.l2jmobius.gameserver.instancemanager.InstanceManager;
import com.l2jmobius.gameserver.model.L2Party;
import com.l2jmobius.gameserver.model.L2Party.MessageType;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.L2Npc;
import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.actor.appearance.PcAppearance;
import com.l2jmobius.gameserver.model.actor.instance.L2MonsterInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.eventengine.AbstractEvent;
import com.l2jmobius.gameserver.model.events.EventDispatcher;
import com.l2jmobius.gameserver.model.events.EventType;
import com.l2jmobius.gameserver.model.events.ListenerRegisterType;
import com.l2jmobius.gameserver.model.events.annotations.RegisterEvent;
import com.l2jmobius.gameserver.model.events.annotations.RegisterType;
import com.l2jmobius.gameserver.model.events.impl.ceremonyofchaos.OnCeremonyOfChaosMatchResult;
import com.l2jmobius.gameserver.model.events.impl.character.OnCreatureDeath;
import com.l2jmobius.gameserver.model.holders.ItemHolder;
import com.l2jmobius.gameserver.model.holders.SkillHolder;
import com.l2jmobius.gameserver.model.instancezone.Instance;
import com.l2jmobius.gameserver.model.instancezone.InstanceTemplate;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.DeleteObject;
import com.l2jmobius.gameserver.network.serverpackets.ExUserInfoAbnormalVisualEffect;
import com.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
import com.l2jmobius.gameserver.network.serverpackets.SkillCoolTime;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import com.l2jmobius.gameserver.network.serverpackets.appearance.ExCuriousHouseMemberUpdate;
import com.l2jmobius.gameserver.network.serverpackets.ceremonyofchaos.ExCuriousHouseEnter;
import com.l2jmobius.gameserver.network.serverpackets.ceremonyofchaos.ExCuriousHouseLeave;
import com.l2jmobius.gameserver.network.serverpackets.ceremonyofchaos.ExCuriousHouseMemberList;
import com.l2jmobius.gameserver.network.serverpackets.ceremonyofchaos.ExCuriousHouseObserveMode;
import com.l2jmobius.gameserver.network.serverpackets.ceremonyofchaos.ExCuriousHouseRemainTime;
import com.l2jmobius.gameserver.network.serverpackets.ceremonyofchaos.ExCuriousHouseResult;
/**
 * @author UnAfraid
 */
public class CeremonyOfChaosEvent extends AbstractEvent
{
	private static final Logger LOGGER = Logger.getLogger(CeremonyOfChaosEvent.class.getName());
	
	private final int _id;
	private final Instance _instance;
	private final Set _monsters = ConcurrentHashMap.newKeySet();
	private long _battleStartTime = 0;
	
	public CeremonyOfChaosEvent(int id, InstanceTemplate template)
	{
		_id = id;
		_instance = InstanceManager.getInstance().createInstance(template, null);
		if (_instance.getEnterLocations().size() < CeremonyOfChaosManager.getInstance().getMaxPlayersInArena())
		{
			LOGGER.warning("There are more member slots: " + _instance.getEnterLocations().size() + " then instance entrance positions: " + CeremonyOfChaosManager.getInstance().getMaxPlayersInArena() + "!");
		}
	}
	
	public int getId()
	{
		return _id;
	}
	
	public int getInstanceId()
	{
		return _instance.getId();
	}
	
	public Instance getInstance()
	{
		return _instance;
	}
	
	public Set getMonsters()
	{
		return _monsters;
	}
	
	public void preparePlayers()
	{
		final ExCuriousHouseMemberList membersList = new ExCuriousHouseMemberList(_id, CeremonyOfChaosManager.getInstance().getMaxPlayersInArena(), getMembers().values());
		final NpcHtmlMessage msg = new NpcHtmlMessage(0);
		
		int index = 0;
		for (CeremonyOfChaosMember member : getMembers().values())
		{
			final L2PcInstance player = member.getPlayer();
			
			if (player.inObserverMode())
			{
				player.leaveObserverMode();
			}
			
			if (player.isInDuel())
			{
				player.setIsInDuel(0);
			}
			
			// Remember player's last location
			player.setLastLocation();
			
			// Hide player information
			final PcAppearance app = player.getAppearance();
			app.setVisibleName("Challenger" + member.getPosition());
			app.setVisibleTitle("");
			app.setVisibleClanData(0, 0, 0, 0, 0);
			
			// Register the event instance
			player.registerOnEvent(this);
			
			// Load the html
			msg.setFile(player.getHtmlPrefix(), "data/html/CeremonyOfChaos/started.htm");
			
			// Remove buffs
			player.stopAllEffectsExceptThoseThatLastThroughDeath();
			
			// Player shouldn't be able to move and is hidden
			player.setIsImmobilized(true);
			player.setInvisible(true);
			
			// Same goes for summon
			player.getServitors().values().forEach(s ->
			{
				s.stopAllEffectsExceptThoseThatLastThroughDeath();
				s.setInvisible(true);
				s.setIsImmobilized(true);
			});
			
			if (player.isFlyingMounted())
			{
				player.untransform();
			}
			
			// If player is dead, revive it
			if (player.isDead())
			{
				player.doRevive();
			}
			
			// If player is sitting, stand up
			if (player.isSitting())
			{
				player.standUp();
			}
			
			// If player in party, leave it
			final L2Party party = player.getParty();
			if (party != null)
			{
				party.removePartyMember(player, MessageType.EXPELLED);
			}
			
			// Cancel any started action
			player.abortAttack();
			player.abortCast();
			player.stopMove(null);
			player.setTarget(null);
			
			// Unsummon pet
			final L2Summon pet = player.getPet();
			if (pet != null)
			{
				pet.unSummon(player);
			}
			
			// Unsummon agathion
			if (player.getAgathionId() > 0)
			{
				player.setAgathionId(0);
			}
			
			// The character’s HP, MP, and CP are fully recovered.
			player.setCurrentHp(player.getMaxHp());
			player.setCurrentMp(player.getMaxMp());
			player.setCurrentCp(player.getMaxCp());
			
			// Skill reuse timers for all skills that have less than 15 minutes of cooldown time are reset.
			for (Skill skill : player.getAllSkills())
			{
				if (skill.getReuseDelay() <= 900000)
				{
					player.enableSkill(skill);
				}
			}
			
			player.sendSkillList();
			player.sendPacket(new SkillCoolTime(player));
			
			// Apply the Energy of Chaos skill
			for (SkillHolder holder : CeremonyOfChaosManager.getInstance().getVariables().getList(CeremonyOfChaosManager.INITIAL_BUFF_KEY, SkillHolder.class))
			{
				holder.getSkill().activateSkill(player, player);
			}
			
			// Send Enter packet
			player.sendPacket(ExCuriousHouseEnter.STATIC_PACKET);
			
			// Send all members
			player.sendPacket(membersList);
			
			// Send the entrance html
			player.sendPacket(msg);
			
			// Send support items to player
			for (ItemHolder holder : CeremonyOfChaosManager.getInstance().getRewards(CeremonyOfChaosManager.INITIAL_ITEMS_KEY).calculateDrops())
			{
				player.addItem("CoC", holder, null, true);
			}
			
			// Teleport player to the arena
			player.teleToLocation(_instance.getEnterLocations().get(index++), 0, _instance);
		}
		
		getTimers().addTimer("match_start_countdown", StatsSet.valueOf("time", 60), 100, null, null);
		
		getTimers().addTimer("teleport_message1", 10000, null, null);
		getTimers().addTimer("teleport_message2", 14000, null, null);
		getTimers().addTimer("teleport_message3", 18000, null, null);
	}
	
	public void startFight()
	{
		for (CeremonyOfChaosMember member : getMembers().values())
		{
			final L2PcInstance player = member.getPlayer();
			if (player != null)
			{
				player.sendPacket(SystemMessageId.THE_MATCH_HAS_STARTED_FIGHT);
				player.setIsImmobilized(false);
				player.setInvisible(false);
				player.broadcastInfo();
				player.sendPacket(new ExUserInfoAbnormalVisualEffect(player));
				player.getServitors().values().forEach(s ->
				{
					s.setInvisible(false);
					s.setIsImmobilized(false);
					s.broadcastInfo();
				});
			}
		}
		_battleStartTime = System.currentTimeMillis();
		getTimers().addRepeatingTimer("update", 1000, null, null);
	}
	
	public void stopFight()
	{
		getMembers().values().stream().filter(p -> p.getLifeTime() == 0).forEach(this::updateLifeTime);
		validateWinner();
		
		final List winners = getWinners();
		final List members = new ArrayList<>(getMembers().size());
		final SystemMessage msg;
		if (winners.isEmpty() || (winners.size() > 1))
		{
			msg = SystemMessage.getSystemMessage(SystemMessageId.THERE_IS_NO_VICTOR_THE_MATCH_ENDS_IN_A_TIE);
		}
		else
		{
			msg = SystemMessage.getSystemMessage(SystemMessageId.CONGRATULATIONS_C1_YOU_WIN_THE_MATCH);
			msg.addCharName(winners.get(0).getPlayer());
		}
		
		for (CeremonyOfChaosMember member : getMembers().values())
		{
			final L2PcInstance player = member.getPlayer();
			if (player != null)
			{
				// Send winner message
				player.sendPacket(msg);
				
				// Send result
				player.sendPacket(new ExCuriousHouseResult(member.getResultType(), this));
				
				members.add(member);
			}
		}
		getTimers().cancelTimer("update", null, null);
		getTimers().addTimer("match_end_countdown", StatsSet.valueOf("time", 30), 30 * 1000, null, null);
		
		EventDispatcher.getInstance().notifyEvent(new OnCeremonyOfChaosMatchResult(winners, members));
	}
	
	private void teleportPlayersOut()
	{
		for (CeremonyOfChaosMember member : getMembers().values())
		{
			final L2PcInstance player = member.getPlayer();
			if (player != null)
			{
				// Leaves observer mode
				if (player.inObserverMode())
				{
					player.setObserving(false);
				}
				
				// Revive the player
				player.doRevive();
				
				// Remove Energy of Chaos
				for (SkillHolder holder : CeremonyOfChaosManager.getInstance().getVariables().getList(CeremonyOfChaosManager.INITIAL_BUFF_KEY, SkillHolder.class))
				{
					player.stopSkillEffects(holder.getSkill());
				}
				
				// Apply buffs on players
				for (SkillHolder holder : CeremonyOfChaosManager.getInstance().getVariables().getList(CeremonyOfChaosManager.END_BUFFS_KEYH, SkillHolder.class))
				{
					holder.getSkill().activateSkill(player, player);
				}
				
				// Remove quit button
				player.sendPacket(ExCuriousHouseLeave.STATIC_PACKET);
				
				// Remove spectator mode
				player.setObserving(false);
				player.sendPacket(ExCuriousHouseObserveMode.STATIC_DISABLED);
				
				// Teleport player back
				player.teleToLocation(player.getLastLocation(), null);
				
				// Restore player information
				final PcAppearance app = player.getAppearance();
				app.setVisibleName(null);
				app.setVisibleTitle(null);
				app.setVisibleClanData(-1, -1, -1, -1, -1);
				
				// Remove player from event
				player.removeFromEvent(this);
			}
		}
		
		getMembers().clear();
		_instance.destroy();
	}
	
	private void updateLifeTime(CeremonyOfChaosMember member)
	{
		member.setLifeTime(((int) (System.currentTimeMillis() - _battleStartTime) / 1000));
	}
	
	public List getWinners()
	{
		final List winners = new ArrayList<>();
		//@formatter:off
		final OptionalInt winnerLifeTime = getMembers().values().stream()
			.mapToInt(CeremonyOfChaosMember::getLifeTime)
			.max();
		
		if(winnerLifeTime.isPresent())
		{
			getMembers().values().stream()
				.sorted(Comparator.comparingLong(CeremonyOfChaosMember::getLifeTime)
					.reversed()
					.thenComparingInt(CeremonyOfChaosMember::getScore)
					.reversed())
				.filter(member -> member.getLifeTime() == winnerLifeTime.getAsInt())
				.collect(Collectors.toCollection(() -> winners));
		}
		
		//@formatter:on
		return winners;
	}
	
	private void validateWinner()
	{
		final List winners = getWinners();
		winners.forEach(winner -> winner.setResultType(winners.size() > 1 ? CeremonyOfChaosResult.TIE : CeremonyOfChaosResult.WIN));
	}
	
	@Override
	public void onTimerEvent(String event, StatsSet params, L2Npc npc, L2PcInstance player)
	{
		switch (event)
		{
			case "update":
			{
				final int time = (int) CeremonyOfChaosManager.getInstance().getScheduler("stopFight").getRemainingTime(TimeUnit.SECONDS);
				broadcastPacket(new ExCuriousHouseRemainTime(time));
				getMembers().values().forEach(p -> broadcastPacket(new ExCuriousHouseMemberUpdate(p)));
				
				// Validate winner
				if (getMembers().values().stream().filter(member -> !member.isDefeated()).count() <= 1)
				{
					stopFight();
				}
				break;
			}
			case "teleport_message1":
			{
				broadcastPacket(SystemMessage.getSystemMessage(SystemMessageId.PROVE_YOUR_ABILITIES));
				break;
			}
			case "teleport_message2":
			{
				broadcastPacket(SystemMessage.getSystemMessage(SystemMessageId.THERE_ARE_NO_ALLIES_HERE_EVERYONE_IS_AN_ENEMY));
				break;
			}
			case "teleport_message3":
			{
				broadcastPacket(SystemMessage.getSystemMessage(SystemMessageId.IT_WILL_BE_A_LONELY_BATTLE_BUT_I_WISH_YOU_VICTORY));
				break;
			}
			case "match_start_countdown":
			{
				final int time = params.getInt("time", 0);
				
				final SystemMessage countdown = SystemMessage.getSystemMessage(SystemMessageId.THE_MATCH_WILL_START_IN_S1_SECOND_S);
				countdown.addByte(time);
				broadcastPacket(countdown);
				
				// Reschedule
				if (time == 60)
				{
					getTimers().addTimer(event, params.set("time", 30), 30 * 1000, null, null);
				}
				else if ((time == 30) || (time == 20))
				{
					getTimers().addTimer(event, params.set("time", time - 10), 10 * 1000, null, null);
				}
				else if (time == 10)
				{
					getTimers().addTimer(event, params.set("time", 5), 5 * 1000, null, null);
				}
				else if ((time > 1) && (time <= 5))
				{
					getTimers().addTimer(event, params.set("time", time - 1), 1000, null, null);
				}
				break;
			}
			case "match_end_countdown":
			{
				final int time = params.getInt("time", 0);
				final SystemMessage countdown = SystemMessage.getSystemMessage(SystemMessageId.IN_S1_SECOND_S_YOU_WILL_BE_MOVED_TO_WHERE_YOU_WERE_BEFORE_PARTICIPATING_IN_THE_CEREMONY_OF_CHAOS);
				countdown.addByte(time);
				broadcastPacket(countdown);
				
				// Reschedule
				if ((time == 30) || (time == 20))
				{
					getTimers().addTimer(event, params.set("time", time - 10), 10 * 1000, null, null);
				}
				else if ((time > 0) && (time <= 10))
				{
					getTimers().addTimer(event, params.set("time", time - 1), 1000, null, null);
				}
				else if (time == 0)
				{
					teleportPlayersOut();
				}
				break;
			}
		}
	}
	
	@RegisterEvent(EventType.ON_CREATURE_DEATH)
	@RegisterType(ListenerRegisterType.GLOBAL_PLAYERS)
	public void onPlayerDeath(OnCreatureDeath event)
	{
		if (event.getAttacker().isPlayer() && event.getTarget().isPlayer())
		{
			final L2PcInstance attackerPlayer = event.getAttacker().getActingPlayer();
			final L2PcInstance targetPlayer = event.getTarget().getActingPlayer();
			
			final CeremonyOfChaosMember attackerMember = getMembers().get(attackerPlayer.getObjectId());
			final CeremonyOfChaosMember targetMember = getMembers().get(targetPlayer.getObjectId());
			
			final DeleteObject deleteObject = new DeleteObject(targetPlayer);
			
			if ((attackerMember != null) && (targetMember != null))
			{
				attackerMember.incrementScore();
				updateLifeTime(targetMember);
				
				// Mark player as defeated
				targetMember.setDefeated(true);
				
				// Delete target player
				//@formatter:off
				getMembers().values().stream()
					.filter(member -> member.getObjectId() != targetPlayer.getObjectId())
					.map(CeremonyOfChaosMember::getPlayer)
					.forEach(deleteObject::sendTo);
				//@formatter:on
				
				// Make the target observer
				targetPlayer.setObserving(true);
				
				// Make the target spectator
				targetPlayer.sendPacket(ExCuriousHouseObserveMode.STATIC_ENABLED);
			}
		}
	}
}