/*
 * 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 <http://www.gnu.org/licenses/>.
 */
package net.sf.l2j.gameserver.instancemanager;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.l2j.L2DatabaseFactory;
import net.sf.l2j.gameserver.datatables.NpcTable;
import net.sf.l2j.gameserver.idfactory.IdFactory;
import net.sf.l2j.gameserver.model.L2ItemInstance;
import net.sf.l2j.gameserver.model.L2World;
import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
import net.sf.l2j.gameserver.model.actor.instance.L2SiegeGuardInstance;
import net.sf.l2j.gameserver.model.entity.Castle;
import net.sf.l2j.gameserver.templates.chars.L2NpcTemplate;

/**
 * This class is similar to the SiegeGuardManager, except it handles the loading of the mercenary tickets that are dropped on castle floors by the castle lords.<br>
 * <br>
 * These tickets (aka badges) need to be readed after each server reboot except when the server crashed in the middle of an ongoig siege. In addition, this class keeps track of the added tickets, in order to properly limit the number of mercenaries in each castle and the number of mercenaries from
 * each mercenary type.<br>
 * <br>
 * Finally, we provide auxilary functions to identify the castle in which each item (and its corresponding NPC) belong to, in order to help avoid mixing them up.
 * @author yellowperil & Fulminus
 */
public class MercTicketManager
{
	protected static Logger _log = Logger.getLogger(MercTicketManager.class.getName());
	
	public static final MercTicketManager getInstance()
	{
		return SingletonHolder._instance;
	}
	
	private static final List<L2ItemInstance> _droppedTickets = new CopyOnWriteArrayList<>();
	
	// max tickets per merc type = 10 + (castleid * 2)?
	// max tickets per castle = 40 + (castleid * 20)?
	private static final int[] MAX_MERC_PER_TYPE =
	{
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10, // Gludio
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15,
		15, // Dion
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10, // Giran
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10,
		10, // Oren
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20, // Aden
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20, // Innadril
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20, // Goddard
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20, // Rune
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20,
		20
	// Schuttgart
	};
	
	private static final int[] MERCS_MAX_PER_CASTLE =
	{
		100, // Gludio
		150, // Dion
		200, // Giran
		300, // Oren
		400, // Aden
		400, // Innadril
		400, // Goddard
		400, // Rune
		400
	// Schuttgart
	};
	
	private static final int[] ITEM_IDS =
	{
		3960,
		3961,
		3962,
		3963,
		3964,
		3965,
		3966,
		3967,
		3968,
		3969,
		6038,
		6039,
		6040,
		6041,
		6042,
		6043,
		6044,
		6045,
		6046,
		6047, // Gludio
		3973,
		3974,
		3975,
		3976,
		3977,
		3978,
		3979,
		3980,
		3981,
		3982,
		6051,
		6052,
		6053,
		6054,
		6055,
		6056,
		6057,
		6058,
		6059,
		6060, // Dion
		3986,
		3987,
		3988,
		3989,
		3990,
		3991,
		3992,
		3993,
		3994,
		3995,
		6064,
		6065,
		6066,
		6067,
		6068,
		6069,
		6070,
		6071,
		6072,
		6073, // Giran
		3999,
		4000,
		4001,
		4002,
		4003,
		4004,
		4005,
		4006,
		4007,
		4008,
		6077,
		6078,
		6079,
		6080,
		6081,
		6082,
		6083,
		6084,
		6085,
		6086, // Oren
		4012,
		4013,
		4014,
		4015,
		4016,
		4017,
		4018,
		4019,
		4020,
		4021,
		6090,
		6091,
		6092,
		6093,
		6094,
		6095,
		6096,
		6097,
		6098,
		6099, // Aden
		5205,
		5206,
		5207,
		5208,
		5209,
		5210,
		5211,
		5212,
		5213,
		5214,
		6105,
		6106,
		6107,
		6108,
		6109,
		6110,
		6111,
		6112,
		6113,
		6114, // Innadril
		6779,
		6780,
		6781,
		6782,
		6783,
		6784,
		6785,
		6786,
		6787,
		6788,
		6792,
		6793,
		6794,
		6795,
		6796,
		6797,
		6798,
		6799,
		6800,
		6801, // Goddard
		7973,
		7974,
		7975,
		7976,
		7977,
		7978,
		7979,
		7980,
		7981,
		7982,
		7988,
		7989,
		7990,
		7991,
		7992,
		7993,
		7994,
		7995,
		7996,
		7997, // Rune
		7918,
		7919,
		7920,
		7921,
		7922,
		7923,
		7924,
		7925,
		7926,
		7927,
		7931,
		7932,
		7933,
		7934,
		7935,
		7936,
		7937,
		7938,
		7939,
		7940
	// Schuttgart
	};
	
	private static final int[] NPC_IDS =
	{
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039, // Gludio
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039, // Dion
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039, // Giran
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039, // Oren
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039, // Aden
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039, // Innadril
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039, // Goddard
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039, // Rune
		35010,
		35011,
		35012,
		35013,
		35014,
		35015,
		35016,
		35017,
		35018,
		35019,
		35030,
		35031,
		35032,
		35033,
		35034,
		35035,
		35036,
		35037,
		35038,
		35039
	// Schuttgart
	};
	
	protected MercTicketManager()
	{
		load();
	}
	
	/**
	 * @param itemId The itemID to check.
	 * @return the castleId for the passed ticket item id.
	 */
	public static int getTicketCastleId(int itemId)
	{
		if ((itemId >= ITEM_IDS[0] && itemId <= ITEM_IDS[9]) || (itemId >= ITEM_IDS[10] && itemId <= ITEM_IDS[19]))
			return 1; // Gludio
			
		if ((itemId >= ITEM_IDS[20] && itemId <= ITEM_IDS[29]) || (itemId >= ITEM_IDS[30] && itemId <= ITEM_IDS[39]))
			return 2; // Dion
			
		if ((itemId >= ITEM_IDS[40] && itemId <= ITEM_IDS[49]) || (itemId >= ITEM_IDS[50] && itemId <= ITEM_IDS[59]))
			return 3; // Giran
			
		if ((itemId >= ITEM_IDS[60] && itemId <= ITEM_IDS[69]) || (itemId >= ITEM_IDS[70] && itemId <= ITEM_IDS[79]))
			return 4; // Oren
			
		if ((itemId >= ITEM_IDS[80] && itemId <= ITEM_IDS[89]) || (itemId >= ITEM_IDS[90] && itemId <= ITEM_IDS[99]))
			return 5; // Aden
			
		if ((itemId >= ITEM_IDS[100] && itemId <= ITEM_IDS[109]) || (itemId >= ITEM_IDS[110] && itemId <= ITEM_IDS[119]))
			return 6; // Innadril
			
		if ((itemId >= ITEM_IDS[120] && itemId <= ITEM_IDS[129]) || (itemId >= ITEM_IDS[130] && itemId <= ITEM_IDS[139]))
			return 7; // Goddard
			
		if ((itemId >= ITEM_IDS[140] && itemId <= ITEM_IDS[149]) || (itemId >= ITEM_IDS[150] && itemId <= ITEM_IDS[159]))
			return 8; // Rune
			
		if ((itemId >= ITEM_IDS[160] && itemId <= ITEM_IDS[169]) || (itemId >= ITEM_IDS[170] && itemId <= ITEM_IDS[179]))
			return 9; // Schuttgart
			
		return -1;
	}
	
	public void reload()
	{
		_droppedTickets.clear();
		load();
	}
	
	private final static void load()
	{
		try (Connection con = L2DatabaseFactory.getInstance().getConnection())
		{
			PreparedStatement statement = con.prepareStatement("SELECT * FROM castle_siege_guards WHERE isHired=1");
			ResultSet rs = statement.executeQuery();
			
			int npcId;
			int itemId;
			int x, y, z;
			
			// start index to begin the search for the itemId corresponding to this NPC :
			// a) skip unnecessary iterations in the search loop
			// b) avoid finding the wrong itemId whenever tickets of different spawn the same npc!
			int startindex = 0;
			
			while (rs.next())
			{
				npcId = rs.getInt("npcId");
				x = rs.getInt("x");
				y = rs.getInt("y");
				z = rs.getInt("z");
				
				Castle castle = CastleManager.getInstance().getCastle(x, y, z);
				if (castle != null)
					startindex = 10 * (castle.getCastleId() - 1);
				
				// find the FIRST ticket itemId with spawns the saved NPC in the saved location
				for (int i = startindex; i < NPC_IDS.length; i++)
				{
					if (NPC_IDS[i] == npcId) // Find the index of the item used
					{
						// only handle tickets if a siege is not ongoing in this npc's castle
						if ((castle != null) && !(castle.getSiege().getIsInProgress()))
						{
							itemId = ITEM_IDS[i];
							// create the ticket in the gameworld
							L2ItemInstance dropticket = new L2ItemInstance(IdFactory.getInstance().getNextId(), itemId);
							dropticket.setLocation(L2ItemInstance.ItemLocation.INVENTORY);
							dropticket.dropMe(null, x, y, z);
							dropticket.setDropTime(0); // avoids it from beeing removed by the auto item destroyer
							L2World.getInstance().storeObject(dropticket);
							_droppedTickets.add(dropticket);
						}
						break;
					}
				}
			}
			statement.close();
		}
		catch (Exception e)
		{
			_log.log(Level.WARNING, "Exception: loadMercenaryData(): " + e.getMessage(), e);
		}
		_log.info("MercTicketManager: Loaded " + _droppedTickets.size() + " tickets.");
	}
	
	/**
	 * @param itemId The ticket id to make checks on.
	 * @return true if the passed item has reached the limit of number of dropped tickets that this SPECIFIC item may have in its castle.
	 */
	public boolean isAtTypeLimit(int itemId)
	{
		int limit = -1;
		// find the max value for this item
		for (int i = 0; i < ITEM_IDS.length; i++)
			if (ITEM_IDS[i] == itemId) // Find the index of the item used
			{
				limit = MAX_MERC_PER_TYPE[i];
				break;
			}
		
		if (limit <= 0)
			return true;
		
		int count = 0;
		for (L2ItemInstance ticket : _droppedTickets)
		{
			if (ticket != null && ticket.getItemId() == itemId)
				count++;
		}
		
		if (count >= limit)
			return true;
		
		return false;
	}
	
	/**
	 * @param itemId The ticket id to make checks on.
	 * @return true if the passed item belongs to a castle which has reached its limit of number of dropped tickets.
	 */
	public boolean isAtCasleLimit(int itemId)
	{
		int castleId = getTicketCastleId(itemId);
		if (castleId <= 0)
			return true;
		
		int limit = MERCS_MAX_PER_CASTLE[castleId - 1];
		if (limit <= 0)
			return true;
		
		int count = 0;
		for (L2ItemInstance ticket : _droppedTickets)
		{
			if ((ticket != null) && (getTicketCastleId(ticket.getItemId()) == castleId))
				count++;
		}
		
		if (count >= limit)
			return true;
		
		return false;
	}
	
	public int getMaxAllowedMerc(int castleId)
	{
		return MERCS_MAX_PER_CASTLE[castleId - 1];
	}
	
	public boolean isTooCloseToAnotherTicket(int x, int y, int z)
	{
		for (L2ItemInstance item : _droppedTickets)
		{
			double dx = x - item.getX();
			double dy = y - item.getY();
			double dz = z - item.getZ();
			
			if ((dx * dx + dy * dy + dz * dz) < 25 * 25)
				return true;
		}
		return false;
	}
	
	/**
	 * Adda mercenary ticket.
	 * <ul>
	 * <li>find the npc that needs to be saved in the mercenary spawns, given this item</li>
	 * <li>Use the passed character's location info to add the spawn</li>
	 * <li>create a copy of the item to drop in the world returns the id of the mercenary npc that was added to the spawn returns -1 if this fails.</li>
	 * </ul>
	 * @param itemId
	 * @param activeChar
	 * @return
	 */
	public int addTicket(int itemId, L2PcInstance activeChar)
	{
		int x = activeChar.getX();
		int y = activeChar.getY();
		int z = activeChar.getZ();
		int heading = activeChar.getHeading();
		
		Castle castle = CastleManager.getInstance().getCastle(activeChar);
		if (castle == null) // this should never happen at this point
			return -1;
		
		for (int i = 0; i < ITEM_IDS.length; i++)
		{
			if (ITEM_IDS[i] == itemId) // Find the index of the item used
			{
				spawnMercenary(NPC_IDS[i], x, y, z, 3000);
				
				// Hire merc for this castle. NpcId is at the same index as the item used.
				castle.getSiege().getSiegeGuardManager().hireMerc(x, y, z, heading, NPC_IDS[i]);
				
				// create the ticket in the gameworld
				L2ItemInstance dropticket = new L2ItemInstance(IdFactory.getInstance().getNextId(), itemId);
				dropticket.setLocation(L2ItemInstance.ItemLocation.INVENTORY);
				dropticket.dropMe(null, x, y, z);
				dropticket.setDropTime(0); // avoids it from beeing removed by the auto item destroyer
				L2World.getInstance().storeObject(dropticket); // add to the world
				// and keep track of this ticket in the list
				_droppedTickets.add(dropticket);
				
				return NPC_IDS[i];
			}
		}
		return -1;
	}
	
	private static void spawnMercenary(int npcId, int x, int y, int z, int despawnDelay)
	{
		L2NpcTemplate template = NpcTable.getInstance().getTemplate(npcId);
		if (template != null)
		{
			final L2SiegeGuardInstance npc = new L2SiegeGuardInstance(IdFactory.getInstance().getNextId(), template);
			npc.setCurrentHpMp(npc.getMaxHp(), npc.getMaxMp());
			npc.setDecayed(false);
			npc.spawnMe(x, y, (z + 20));
			
			if (despawnDelay > 0)
				npc.scheduleDespawn(despawnDelay);
		}
	}
	
	/**
	 * Delete all tickets from a castle; remove the items from the world and remove references to them from this class
	 * @param castleId
	 */
	public void deleteTickets(int castleId)
	{
		Iterator<L2ItemInstance> it = _droppedTickets.iterator();
		while (it.hasNext())
		{
			L2ItemInstance item = it.next();
			if (item != null && getTicketCastleId(item.getItemId()) == castleId)
			{
				item.decayMe();
				L2World.getInstance().removeObject(item);
				it.remove();
			}
		}
	}
	
	/**
	 * remove a single ticket and its associated spawn from the world (used when the castle lord picks up a ticket, for example)
	 * @param item
	 */
	public void removeTicket(L2ItemInstance item)
	{
		int itemId = item.getItemId();
		int npcId = -1;
		
		// find the FIRST ticket itemId with spawns the saved NPC in the saved location
		for (int i = 0; i < ITEM_IDS.length; i++)
			if (ITEM_IDS[i] == itemId) // Find the index of the item used
			{
				npcId = NPC_IDS[i];
				break;
			}
		// find the castle where this item is
		Castle castle = CastleManager.getInstance().getCastleById(getTicketCastleId(itemId));
		
		if (npcId > 0 && castle != null)
		{
			(new SiegeGuardManager(castle)).removeMerc(npcId, item.getX(), item.getY(), item.getZ());
		}
		
		_droppedTickets.remove(item);
	}
	
	public int[] getItemIds()
	{
		return ITEM_IDS;
	}
	
	private static class SingletonHolder
	{
		protected static final MercTicketManager _instance = new MercTicketManager();
	}
}