package com.aionemu.gameserver.services.player;

import java.sql.Timestamp;
import java.util.List;

import com.aionemu.gameserver.configs.main.CustomConfig;
import com.aionemu.gameserver.configs.main.EventsConfig;
import com.aionemu.gameserver.configs.main.NameConfig;
import com.aionemu.gameserver.controllers.FlyController;
import com.aionemu.gameserver.controllers.effect.PlayerEffectController;
import com.aionemu.gameserver.dao.*;
import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.dataholders.PlayerInitialData;
import com.aionemu.gameserver.dataholders.PlayerInitialData.LocationData;
import com.aionemu.gameserver.dataholders.PlayerInitialData.PlayerCreationData;
import com.aionemu.gameserver.dataholders.PlayerInitialData.PlayerCreationData.ItemType;
import com.aionemu.gameserver.model.account.Account;
import com.aionemu.gameserver.model.account.PlayerAccountData;
import com.aionemu.gameserver.model.gameobjects.Item;
import com.aionemu.gameserver.model.gameobjects.Persistable.PersistentState;
import com.aionemu.gameserver.model.gameobjects.player.Mailbox;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.gameobjects.player.PlayerCommonData;
import com.aionemu.gameserver.model.house.House;
import com.aionemu.gameserver.model.items.ItemSlot;
import com.aionemu.gameserver.model.items.storage.Storage;
import com.aionemu.gameserver.model.skill.PlayerSkillList;
import com.aionemu.gameserver.model.stats.calc.functions.PlayerStatFunctions;
import com.aionemu.gameserver.model.team.legion.LegionMember;
import com.aionemu.gameserver.model.templates.item.ItemTemplate;
import com.aionemu.gameserver.services.BrokerService;
import com.aionemu.gameserver.services.HousingService;
import com.aionemu.gameserver.services.LegionService;
import com.aionemu.gameserver.services.PunishmentService.PunishmentType;
import com.aionemu.gameserver.services.SkillLearnService;
import com.aionemu.gameserver.services.item.ItemFactory;
import com.aionemu.gameserver.world.World;
import com.aionemu.gameserver.world.knownlist.KnownList;

/**
 * This class is designed to do all the work related with loading/storing players.<br>
 * Same with storing, {@link #storePlayer(com.aionemu.gameserver.model.gameobjects.player.Player)} stores all player data like appearance, items,
 * etc...
 *
 * @author SoulKeeper, Saelya, Cura
 */
public class PlayerService {

	/**
	 * @return True if the character name is taken (currently used by another character or the character with this name was recently renamed).
	 */
	public static boolean isNameUsedOrReserved(String oldName, String newName) {
		return isNameUsedOrReserved(oldName, newName, NameConfig.RESERVE_OLD_NAME_DAYS);
	}

	public static boolean isNameUsedOrReserved(String oldName, String newName, int nameReservationDurationDays) {
		return PlayerDAO.isNameUsed(newName) || OldNamesDAO.isNameReserved(oldName, newName, nameReservationDurationDays);
	}

	/**
	 * Stores newly created player
	 *
	 * @param player
	 *          player to store
	 * @return true if character was successful saved.
	 */
	public static boolean storeNewPlayer(Player player, String accountName, int accountId) {
		return PlayerDAO.saveNewPlayer(player, accountId, accountName)
			&& PlayerAppearanceDAO.store(player) && PlayerSkillListDAO.storeSkills(player)
			&& InventoryDAO.store(player);
	}

	/**
	 * Stores player data into db
	 *
	 * @param player
	 */
	public static void storePlayer(Player player) {
		PlayerDAO.storePlayer(player);
		PlayerSkillListDAO.storeSkills(player);
		PlayerSettingsDAO.saveSettings(player);
		PlayerQuestListDAO.store(player);
		AbyssRankDAO.storeAbyssRank(player);
		PlayerPunishmentsDAO.storePlayerPunishment(player, PunishmentType.PRISON);
		PlayerPunishmentsDAO.storePlayerPunishment(player, PunishmentType.GATHER);
		InventoryDAO.store(player);
		for (House house : player.getHouses())
			house.save();
		ItemStoneListDAO.save(player);
		MailDAO.storeMailbox(player);
		PortalCooldownsDAO.storePortalCooldowns(player);
		CraftCooldownsDAO.storeCraftCooldowns(player);
		HouseObjectCooldownsDAO.storeHouseObjectCooldowns(player);
		PlayerNpcFactionsDAO.storeNpcFactions(player);
		AccountPassportsDAO.storePassport(player.getAccount());
		if (EventsConfig.ENABLE_HEADHUNTING)
			HeadhuntingDAO.storeHeadhunter(player.getObjectId());
	}

	public static Player getPlayer(int playerObjId, Account account) {
		// Player common data and appearance should be already loaded in account
		PlayerAccountData playerAccountData = account.getPlayerAccountData(playerObjId);
		PlayerCommonData pcd = playerAccountData.getPlayerCommonData();
		Player player = new Player(playerAccountData, account);
		int oldOwnerId = pcd.getWorldOwnerId();
		player.setPosition(World.getInstance().createPosition(pcd.getMapId(), pcd.getX(), pcd.getY(), pcd.getZ(), pcd.getHeading(), 0));
		pcd.setWorldOwnerId(oldOwnerId);
		LegionMember legionMember = LegionService.getInstance().getLegionMember(player.getObjectId());
		if (legionMember != null) {
			player.setLegionMember(legionMember);
		}

		player.setMacros(PlayerMacrosDAO.loadMacros(playerObjId));
		player.setSkillList(PlayerSkillListDAO.loadSkillList(playerObjId));
		player.setKnownlist(new KnownList(player));
		player.setFriendList(FriendListDAO.load(player));
		player.setBlockList(BlockListDAO.load(playerObjId));
		player.setTitleList(PlayerTitleListDAO.loadTitleList(playerObjId));
		player.setPlayerSettings(PlayerSettingsDAO.loadSettings(playerObjId));
		AbyssRankDAO.loadAbyssRank(player);
		PlayerNpcFactionsDAO.loadNpcFactions(player);
		MotionDAO.loadMotionList(player);
		AccountPassportsDAO.loadPassport(player.getAccount());
		player.setEffectController(new PlayerEffectController(player));
		player.setFlyController(new FlyController(player));
		PlayerStatFunctions.addPredefinedStatFunctions(player);

		player.setQuestStateList(PlayerQuestListDAO.load(playerObjId));
		player.setRecipeList(PlayerRecipesDAO.load(player.getObjectId()));

		account.getAccountWarehouse().setOwner(player);
		InventoryDAO.loadStorage(playerObjId, player.getInventory());
		ItemStoneListDAO.load(player.getInventory().getItems());
		ItemStoneListDAO.load(player.getEquipment().getEquippedItemsWithoutStigma());

		InventoryDAO.loadStorage(playerObjId, player.getWarehouse());
		ItemStoneListDAO.load(player.getWarehouse().getItems());

		for (Storage petBag : player.getPetBags()) {
			InventoryDAO.loadStorage(playerObjId, petBag);
			ItemStoneListDAO.load(petBag.getItems());
		}
		for (Storage cabinet : player.getCabinets()) {
			InventoryDAO.loadStorage(playerObjId, cabinet);
			ItemStoneListDAO.load(cabinet.getItems());
		}

		// Apply equipment stats (items and manastones were loaded in account)
		player.getEquipment().onLoadApplyEquipmentStats();

		PlayerPunishmentsDAO.loadPlayerPunishments(player);

		// load saved effects
		PlayerEffectsDAO.loadPlayerEffects(player);
		// load saved player cooldowns
		PlayerCooldownsDAO.loadPlayerCooldowns(player);
		// load item cooldowns
		ItemCooldownsDAO.loadItemCooldowns(player);
		// load portal cooldowns
		PortalCooldownsDAO.loadPortalCooldowns(player);
		// load house object use cooldowns
		HouseObjectCooldownsDAO.loadHouseObjectCooldowns(player);
		// load bind point
		PlayerBindPointDAO.loadBindPoint(player);
		// load craft cooldowns
		CraftCooldownsDAO.loadCraftCooldowns(player);

		PlayerLifeStatsDAO.loadPlayerLifeStat(player);
		PlayerEmotionListDAO.loadEmotions(player);

		return player;
	}

	/**
	 * This method is used for creating new players
	 *
	 * @param playerAccountData
	 * @param account
	 * @return Player
	 */
	public static Player newPlayer(PlayerAccountData playerAccountData, Account account) {
		PlayerCommonData playerCommonData = playerAccountData.getPlayerCommonData();
		PlayerInitialData playerInitialData = DataManager.PLAYER_INITIAL_DATA;
		LocationData ld = playerInitialData.getSpawnLocation(playerCommonData.getRace());

		playerCommonData.setMapId(ld.getMapId());
		playerCommonData.setX(ld.getX());
		playerCommonData.setY(ld.getY());
		playerCommonData.setZ(ld.getZ());
		playerCommonData.setHeading(ld.getHeading());

		Player newPlayer = new Player(playerAccountData, account);

		// Starting skills
		newPlayer.setSkillList(new PlayerSkillList());
		SkillLearnService.learnNewSkills(newPlayer, 1, newPlayer.getLevel());

		// Starting items
		PlayerCreationData playerCreationData = playerInitialData.getPlayerCreationData(playerCommonData.getPlayerClass());
		if (playerCreationData != null) { // player transfer
			List<ItemType> items = playerCreationData.getItems();
			for (ItemType itemType : items) {
				int itemId = itemType.getTemplate().getTemplateId();
				Item item = ItemFactory.newItem(itemId, itemType.getCount());
				if (item == null) {
					continue;
				}

				// When creating new player - all equipment that has slot values will be equipped
				// Make sure you will not put into xml file more items than possible to equip.
				ItemTemplate itemTemplate = item.getItemTemplate();

				if ((itemTemplate.isArmor() || itemTemplate.isWeapon()) && !newPlayer.getEquipment().isSlotEquipped(itemTemplate.getItemSlot())) {
					item.setEquipped(true);
					ItemSlot itemSlot = ItemSlot.getSlotFor(itemTemplate.getItemSlot());
					item.setEquipmentSlot(itemSlot.getSlotIdMask());
				}
				newPlayer.getInventory().onLoadHandler(item);
			}
		}
		newPlayer.setMailbox(new Mailbox(newPlayer));

		// Mark inventory and equipment as UPDATE_REQUIRED to be saved during character creation
		newPlayer.getInventory().setPersistentState(PersistentState.UPDATE_REQUIRED);
		newPlayer.getEquipment().setPersistentState(PersistentState.UPDATE_REQUIRED);
		return newPlayer;
	}

	public static PlayerCommonData getOrLoadPlayerCommonData(int playerObjId) {
		Player player = World.getInstance().getPlayer(playerObjId);
		if (player == null)
			return PlayerDAO.loadPlayerCommonData(playerObjId);
		return player.getCommonData();
	}

	/**
	 * Cancel Player deletion process if its possible.
	 *
	 * @param accData
	 *          PlayerAccountData
	 * @return True if deletion was successful canceled.
	 */
	public static boolean cancelPlayerDeletion(PlayerAccountData accData) {
		if (accData.getDeletionDate() == null) {
			return true;
		}

		if (accData.getDeletionDate().getTime() > System.currentTimeMillis()) {
			accData.setDeletionDate(null);
			storeDeletionTime(accData);
			return true;
		}
		return false;
	}

	/**
	 * Starts player deletion process if its possible. If deletion is possible character should be deleted after 5 minutes.
	 *
	 * @param accData
	 *          PlayerAccountData
	 */
	public static void deletePlayer(PlayerAccountData accData) {
		if (accData.getDeletionDate() != null) {
			return;
		}

		accData.setDeletionDate(new Timestamp(System.currentTimeMillis() + CustomConfig.CHARACTER_DELETION_TIME_MINUTES * 60 * 1000));
		storeDeletionTime(accData);
	}

	/**
	 * Completely removes player from database
	 *
	 * @param playerId
	 *          id of player to delete from db
	 */
	public static void deletePlayerFromDB(int playerId) {
		deletePlayerFromDB(playerId, true);
	}

	public static void deletePlayerFromDB(int playerId, boolean notifyServices) {
		InventoryDAO.deletePlayerOrLegionItems(playerId);
		PlayerDAO.deletePlayer(playerId);
		if (notifyServices) {
			HousingService.getInstance().onPlayerDeleted(playerId);
			BrokerService.getInstance().onPlayerDeleted(playerId);
		}
	}

	/**
	 * Updates deletion time in database
	 *
	 * @param accData
	 *          PlayerAccountData
	 */
	private static void storeDeletionTime(PlayerAccountData accData) {
		PlayerDAO.updateDeletionTime(accData.getPlayerCommonData().getPlayerObjId(), accData.getDeletionDate());
	}

	/**
	 * @param objectId
	 * @param creationDate
	 */
	public static void storeCreationTime(int objectId, Timestamp creationDate) {
		PlayerDAO.storeCreationTime(objectId, creationDate);
	}

	/**
	 * Add macro for player
	 *
	 * @param player
	 *          Player
	 * @param macroOrder
	 *          Macro order
	 * @param macroXML
	 *          Macro XML
	 */
	public static void addMacro(Player player, int macroOrder, String macroXML) {
		if (player.getMacros().add(macroOrder, macroXML)) {
			PlayerMacrosDAO.addMacro(player.getObjectId(), macroOrder, macroXML);
		} else {
			PlayerMacrosDAO.updateMacro(player.getObjectId(), macroOrder, macroXML);
		}
	}

	/**
	 * Remove macro with specified index from specified player
	 *
	 * @param player
	 *          Player
	 * @param macroOrder
	 *          Macro order index
	 */
	public static void removeMacro(Player player, int macroOrder) {
		if (player.getMacros().remove(macroOrder)) {
			PlayerMacrosDAO.deleteMacro(player.getObjectId(), macroOrder);
		}
	}

	public static String getPlayerName(int objectId) {
		Player player = World.getInstance().getPlayer(objectId);
		if (player != null)
			return player.getName();
		return PlayerDAO.getPlayerNameByObjId(objectId);
	}
}
