package com.aionemu.gameserver.services.player;

import static com.aionemu.gameserver.configs.main.SecurityConfig.MultiClientingRestrictionMode.SAME_FACTION;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.aionemu.gameserver.GameServer;
import com.aionemu.gameserver.cache.HTMLCache;
import com.aionemu.gameserver.configs.administration.AdminConfig;
import com.aionemu.gameserver.configs.main.*;
import com.aionemu.gameserver.custom.pvpmap.PvpMapService;
import com.aionemu.gameserver.dao.*;
import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.dataholders.PlayerInitialData;
import com.aionemu.gameserver.model.ChatType;
import com.aionemu.gameserver.model.Expirable;
import com.aionemu.gameserver.model.TaskId;
import com.aionemu.gameserver.model.account.Account;
import com.aionemu.gameserver.model.account.CharacterBanInfo;
import com.aionemu.gameserver.model.account.CharacterPasskey.ConnectType;
import com.aionemu.gameserver.model.account.PlayerAccountData;
import com.aionemu.gameserver.model.gameobjects.HouseObject;
import com.aionemu.gameserver.model.gameobjects.Item;
import com.aionemu.gameserver.model.gameobjects.Persistable.PersistentState;
import com.aionemu.gameserver.model.gameobjects.player.BindPointPosition;
import com.aionemu.gameserver.model.gameobjects.player.FriendList.Status;
import com.aionemu.gameserver.model.gameobjects.player.Macros;
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.storage.IStorage;
import com.aionemu.gameserver.model.items.storage.Storage;
import com.aionemu.gameserver.model.items.storage.StorageType;
import com.aionemu.gameserver.model.siege.FortressLocation;
import com.aionemu.gameserver.model.skill.PlayerSkillEntry;
import com.aionemu.gameserver.model.team.alliance.PlayerAllianceService;
import com.aionemu.gameserver.model.team.group.PlayerGroupService;
import com.aionemu.gameserver.model.templates.housing.HouseAddress;
import com.aionemu.gameserver.model.templates.housing.HouseType;
import com.aionemu.gameserver.model.vortex.VortexLocation;
import com.aionemu.gameserver.network.aion.AionConnection;
import com.aionemu.gameserver.network.aion.serverpackets.*;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ENTER_WORLD_CHECK.Msg;
import com.aionemu.gameserver.network.aion.skillinfo.SkillEntryWriter;
import com.aionemu.gameserver.questEngine.QuestEngine;
import com.aionemu.gameserver.services.*;
import com.aionemu.gameserver.services.PunishmentService.PunishmentType;
import com.aionemu.gameserver.services.abyss.AbyssSkillService;
import com.aionemu.gameserver.services.craft.RelinquishCraftStatus;
import com.aionemu.gameserver.services.event.EventService;
import com.aionemu.gameserver.services.instance.InstanceService;
import com.aionemu.gameserver.services.mail.MailService;
import com.aionemu.gameserver.services.panesterra.PanesterraService;
import com.aionemu.gameserver.services.reward.AdventService;
import com.aionemu.gameserver.services.reward.VeteranRewardService;
import com.aionemu.gameserver.services.teleport.BindPointTeleportService;
import com.aionemu.gameserver.services.teleport.TeleportService;
import com.aionemu.gameserver.services.toypet.PetService;
import com.aionemu.gameserver.skillengine.SkillEngine;
import com.aionemu.gameserver.skillengine.model.SkillTemplate;
import com.aionemu.gameserver.taskmanager.tasks.ExpireTimerTask;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.PositionUtil;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.utils.audit.GMService;
import com.aionemu.gameserver.utils.collections.DynamicServerPacketBodySplitList;
import com.aionemu.gameserver.utils.collections.FixedElementCountSplitList;
import com.aionemu.gameserver.utils.collections.SplitList;
import com.aionemu.gameserver.utils.stats.AbyssRankEnum;
import com.aionemu.gameserver.world.World;

/**
 * @author ATracer, Neon
 */
public final class PlayerEnterWorldService {

	private static final Logger log = LoggerFactory.getLogger("GAMECONNECTION_LOG");
	private static final String VERSION_INFO = "Server " + GameServer.versionInfo.getBuildInfo(GSConfig.TIME_ZONE_ID);
	private static final ConcurrentLinkedQueue<Integer> enteringWorld = new ConcurrentLinkedQueue<>();

	public static void enterWorld(final AionConnection client, int objectId) {
		Account account = client.getAccount();
		PlayerAccountData playerAccData = account.getPlayerAccountData(objectId);
		if (playerAccData == null) {
			log.warn("Player enterWorld fail: character obj ID {} was not found on account ID {}.", objectId, account.getId());
			client.sendPacket(new SM_ENTER_WORLD_CHECK(Msg.CONNECTION_ERROR));
			return;
		}

		PlayerCommonData pcd = playerAccData.getPlayerCommonData();
		if (pcd == null) {
			log.warn("Player enterWorld fail: CommonData for character obj ID {} is null.", objectId);
			client.sendPacket(new SM_ENTER_WORLD_CHECK(Msg.CONNECTION_ERROR));
			return;
		}

		if (pcd.isOnline()) { // character should soon leave the world (due to previous client crash)
			if (PlayerDAO.isOnline(objectId)) {
				client.sendPacket(new SM_ENTER_WORLD_CHECK(Msg.REENTRY_TIME));
				return;
			} else { // reload pcd, appearance, acc warehouse, ... since char was saved after acc logged in (delayed kick)
				playerAccData = AccountService.loadPlayerAccountData(objectId);
				pcd = playerAccData.getPlayerCommonData();
				account.addPlayerAccountData(playerAccData);
				account.setAccountWarehouse(AccountService.loadAccountWarehouse(account));
			}
		}

		if (World.getInstance().isInWorld(objectId)) {
			log.warn("Player enterWorld fail: Duplicate character obj ID {} found in world.", objectId);
			client.sendPacket(new SM_ENTER_WORLD_CHECK(Msg.CONNECTION_ERROR));
			return;
		}

		// check if char is banned
		CharacterBanInfo cbi = playerAccData.getCharBanInfo();
		if (cbi != null) {
			if (cbi.getEnd() >= System.currentTimeMillis() / 1000) {
				client.sendPacket(new SM_ENTER_WORLD_CHECK(Msg.CONNECTION_ERROR));
				return;
			} else {
				PlayerPunishmentsDAO.unpunishPlayer(objectId, PunishmentType.CHARBAN);
			}
		}

		// passkey check
		if (SecurityConfig.PASSKEY_ENABLE && !account.getCharacterPasskey().isPass()) {
			account.getCharacterPasskey().setConnectType(ConnectType.ENTER);
			account.getCharacterPasskey().setObjectId(objectId);
			boolean isExistPasskey = PlayerPasskeyDAO.existCheckPlayerPasskey(account.getId());
			client.sendPacket(new SM_CHARACTER_SELECT(!isExistPasskey ? 0 : 1));
			return;
		}

		Timestamp lastOnline = pcd.getLastOnline();
		if (!pcd.isInEditMode() && lastOnline != null && System.currentTimeMillis() - lastOnline.getTime() < (GSConfig.CHARACTER_REENTRY_TIME * 1000)) {
			client.sendPacket(new SM_ENTER_WORLD_CHECK(Msg.REENTRY_TIME));
			return;
		}

		Player player = PlayerService.getPlayer(objectId, account);
		if (!enteringWorld.contains(objectId) && enteringWorld.add(objectId)) {
			try {
				if (player.isStaff() || MultiClientingService.tryEnterWorld(player, client)) {
					enterWorld(client, player);
				} else {
					Msg msg = SecurityConfig.MULTI_CLIENTING_RESTRICTION_MODE == SAME_FACTION ? Msg.BOTH_FACTIONS : Msg.CONNECTION_ERROR;
					client.sendPacket(new SM_ENTER_WORLD_CHECK(msg));
				}
			} catch (Throwable ex) {
				player.getController().delete();
				pcd.setOnline(false);
				PlayerDAO.onlinePlayer(player, false);
				player.setClientConnection(null);
				client.setActivePlayer(null);
				client.sendPacket(new SM_ENTER_WORLD_CHECK(Msg.CONNECTION_ERROR));
				log.error("Error during enter world of " + player, ex);
			} finally {
				enteringWorld.remove(objectId);
			}
		}
	}

	private static void enterWorld(AionConnection client, Player player) {
		Account account = player.getAccount();
		PlayerCommonData pcd = player.getCommonData();

		client.resetPingFailCount();
		activatePassiveSkillEffects(player); // before setClientConnection to avoid packet spam
		player.setClientConnection(client);
		if (!client.setActivePlayer(player))
			throw new IllegalStateException("Couldn't set active player");
		pcd.setOnline(true);
		player.getFriendList().setStatus(Status.ONLINE, pcd);
		PlayerDAO.onlinePlayer(player, true);
		PlayerDAO.storeLastOnlineTime(player.getObjectId(), new Timestamp(System.currentTimeMillis()));
		log.info("Player " + player.getName() + " (" + account + ") logged on");
		pcd.setInEditMode(false);

		World.getInstance().storeObject(player);

		// change player position if he isn't allowed to spawn in the current zone
		if (validateFortressZone(player)) // only check vortex zone if fortress check was ok (otherwise, the player is already set to bind point)
			validateVortexZone(player);

		// if player skipped some levels offline, learn missing skills and stuff
		player.getController().onLevelChange(PlayerDAO.getOldCharacterLevel(player.getObjectId()), player.getLevel());

		// Energy of Repose must be calculated before sending SM_STATS_INFO
		if (pcd.getLastOnline() != null) {
			long secondsOffline = (System.currentTimeMillis() - pcd.getLastOnline().getTime()) / 1000;
			if (secondsOffline > 10 * 60) // 10 mins offline = 0 salvation points
				pcd.resetSalvationPoints();

			updateEnergyOfRepose(player, secondsOffline);

			if (secondsOffline > 5 * 60)
				pcd.setDp(0);
		}

		client.sendPacket(new SM_UNK_3_5_1());
		StigmaService.onPlayerLogin(player);
		client.sendPacket(new SM_ENTER_WORLD_CHECK());

		InstanceService.onPlayerLogin(player);
		// Update player skills first!!!
		if (player.hasAccess(AdminConfig.GM_SKILLS))
			GMService.getInstance().addGmSkills(player);
		AbyssSkillService.updateSkills(player);
		SplitList<PlayerSkillEntry> skillEntrySplitList = new DynamicServerPacketBodySplitList<>(player.getSkillList().getAllSkills(), false,
			SM_SKILL_LIST.STATIC_BODY_SIZE, SkillEntryWriter.DYNAMIC_BODY_PART_SIZE_CALCULATOR);
		skillEntrySplitList.forEach(part -> PacketSendUtility.sendPacket(player, new SM_SKILL_LIST(part)));
		if (player.getSkillCoolDowns() != null)
			client.sendPacket(new SM_SKILL_COOLDOWN(player.getSkillCoolDowns(), true));

		if (!player.getItemCoolDowns().isEmpty())
			client.sendPacket(new SM_ITEM_COOLDOWN(player.getItemCoolDowns()));

		QuestEngine.getInstance().sendCompletedQuests(player);
		client.sendPacket(new SM_QUEST_LIST(player.getQuestStateList().getUncompletedQuests()));
		client.sendPacket(new SM_TITLE_INFO(pcd.getTitleId()));
		if (pcd.getBonusTitleId() != 0) {
			player.getTitleList().setBonusTitle(pcd.getBonusTitleId());
		}
		client.sendPacket(new SM_MOTION(player.getMotions().getMotions().values()));
		client.sendPacket(new SM_AFTER_TIME_CHECK_4_7_5());// it is also sent after enter world check

		byte[] uiSettings = player.getPlayerSettings().getUiSettings();
		byte[] shortcuts = player.getPlayerSettings().getShortcuts();
		byte[] houseBuddies = player.getPlayerSettings().getHouseBuddies();

		if (uiSettings != null)
			client.sendPacket(new SM_UI_SETTINGS(uiSettings, 0));

		if (shortcuts != null)
			client.sendPacket(new SM_UI_SETTINGS(shortcuts, 1));

		if (houseBuddies != null)
			client.sendPacket(new SM_UI_SETTINGS(houseBuddies, 2));

		sendItemInfos(client, player);

		client.sendPacket(new SM_CHANNEL_INFO(player.getPosition()));

		KiskService.getInstance().onLogin(player);
		TeleportService.sendObeliskBindPoint(player);
		TeleportService.sendKiskBindPoint(player);

		PanesterraService.getInstance().onEnterPanesterra(player);

		// ----------------------------- Retail sequence -----------------------------
		client.sendPacket(new SM_PLAYER_SPAWN(player));
		// SM_WEATHER miss on login (but he 'live' in CM_LEVEL_READY.. need investigate)
		client.sendPacket(new SM_GAME_TIME());
		if (player.isLegionMember())
			LegionService.getInstance().onLogin(player);
		sendWarehouseItemInfos(client, player);
		client.sendPacket(new SM_TITLE_INFO(player));
		client.sendPacket(new SM_EMOTION_LIST((byte) 0, player.getEmotions().getEmotions()));
		// SM_BD_UNK h 0
		SiegeService.getInstance().onPlayerLogin(player);
		client.sendPacket(new SM_PRICES());
		if (!player.getCraftCooldowns().isEmpty())
			client.sendPacket(new SM_RECIPE_COOLDOWN(player, 1));
		BindPointTeleportService.onLogin(player);
		client.sendPacket(new SM_FRIEND_LIST());
		client.sendPacket(new SM_BLOCK_LIST());
		if (AutoGroupConfig.AUTO_GROUP_ENABLE) {
			AutoGroupService.getInstance().onPlayerLogin(player);
		}
		client.sendPacket(new SM_INSTANCE_INFO((byte) 2, player));
		client.sendPacket(new SM_ABYSS_RANK(player));
		client.sendPacket(new SM_STATS_INFO(player));
		// ----------------------------- Retail sequence -----------------------------

		if (player.hasAccess(AdminConfig.REVISION_INFO_ON_LOGIN))
			PacketSendUtility.sendMessage(player, VERSION_INFO, ChatType.WHITE);

		if (account.getMembership() > 0 && account.getMembership() <= MembershipConfig.MEMBERSHIP_TYPES.length) {
			String accountType = MembershipConfig.MEMBERSHIP_TYPES[account.getMembership() - 1];
			client.sendPacket(new SM_MESSAGE(0, null, "Your account is " + accountType, ChatType.GOLDEN_YELLOW));
		}

		// Alliance Packet after SetBindPoint
		PlayerAllianceService.onPlayerLogin(player);

		PunishmentService.updatePrisonStatus(player);

		PlayerGroupService.onPlayerLogin(player);
		PetService.getInstance().onPlayerLogin(player);

		// ----------------------------- Retail sequence -----------------------------
		client.sendPacket(new SM_LEGION_DOMINION_LOC_INFO());
		MailService.onPlayerLogin(player);
		HousingBidService.getInstance().onPlayerLogin(player); // must ensure player mailbox is initialized first
		AtreianPassportService.getInstance().onLogin(player);
		sendMacroList(client, player);
		client.sendPacket(new SM_RECIPE_LIST(player.getRecipeList().getRecipeList()));
		BrokerService.getInstance().onPlayerLogin(player);
		HousingService.getInstance().onPlayerLogin(player); // must ensure player mailbox is initialized first
		// ----------------------------- Retail sequence -----------------------------
		if (CustomConfig.ENABLE_SIMPLE_2NDCLASS)
			ClassChangeService.showClassChangeDialog(player);

		GMService.getInstance().onPlayerLogin(player);

		if (player.getAbyssRank().getRank().getId() >= AbyssRankEnum.STAR1_OFFICER.getId()) {
			client.sendPacket(SM_SYSTEM_MESSAGE.STR_MSG_GLORY_POINT_LOSE_COMMON());
			client.sendPacket(SM_SYSTEM_MESSAGE.STR_MSG_GLORY_POINT_LOSE_PERSONAL(player.getName(), player.getAbyssRank().getRank().getGpLossPerDay()));
		}

		// Trigger restore services on login.
		player.getLifeStats().updateCurrentStats();
		player.getObserveController().notifyHPChangeObservers(player.getLifeStats().getCurrentHp());

		if (HTMLConfig.ENABLE_HTML_WELCOME)
			HTMLService.showHTML(player, HTMLCache.getInstance().getHTML("welcome.xhtml"));

		AdventService.getInstance().onLogin(player);

		player.getNpcFactions().sendDailyQuest();

		if (HTMLConfig.ENABLE_GUIDES)
			HTMLService.onPlayerLogin(player);

		player.getEquipment().checkRankLimitItems(); // Remove items after offline changed rank

		List<Expirable> expirables = new ArrayList<>();
		for (StorageType st : StorageType.values()) {
			if (st == StorageType.LEGION_WAREHOUSE)
				continue;
			IStorage storage = player.getStorage(st.getId());
			if (storage != null)
				expirables.addAll(storage.getItems());
		}
		expirables.addAll(player.getEquipment().getEquippedItems());
		expirables.addAll(player.getMotions().getMotions().values());
		expirables.addAll(player.getEmotions().getEmotions());
		expirables.addAll(player.getTitleList().getTitles());
		ExpireTimerTask.getInstance().registerExpirables(expirables, player);

		if (player.getActiveHouse() != null) {
			for (HouseObject<?> obj : player.getActiveHouse().getRegistry().getObjects()) {
				if (obj.getPersistentState() != PersistentState.DELETED)
					ExpireTimerTask.getInstance().registerExpirable(obj, player);
			}
		}
		// scheduler periodic update
		player.getController().addTask(TaskId.PLAYER_UPDATE, ThreadPoolManager.getInstance().scheduleAtFixedRate(
			new GeneralUpdateTask(player.getObjectId()), PeriodicSaveConfig.PLAYER_GENERAL * 1000, PeriodicSaveConfig.PLAYER_GENERAL * 1000));
		player.getController().addTask(TaskId.INVENTORY_UPDATE, ThreadPoolManager.getInstance()
			.scheduleAtFixedRate(new ItemUpdateTask(player.getObjectId()), PeriodicSaveConfig.PLAYER_ITEMS * 1000, PeriodicSaveConfig.PLAYER_ITEMS * 1000));

		SurveyService.getInstance().showAvailable(player);
		EventService.getInstance().onPlayerLogin(player);

		if (CraftConfig.DELETE_EXCESS_CRAFT_ENABLE)
			RelinquishCraftStatus.removeExcessCraftStatus(player, false);

		// try to send bonus pack (if mailbox was full on lvlup)
		BonusPackService.getInstance().addPlayerCustomReward(player);
		FactionPackService.getInstance().addPlayerCustomReward(player);
		VeteranRewardService.getInstance().tryReward(player);

		PvpMapService.getInstance().onLogin(player);
	}

	@SuppressWarnings("lossy-conversions")
	private static void updateEnergyOfRepose(Player player, long secondsOffline) {
		player.getCommonData().updateMaxRepose();
		if (player.getCommonData().isReadyForReposeEnergy() && secondsOffline > 4 * 3600) { // more than 4 hours offline: start counting Repose Energy
																																												// addition
			double hours = secondsOffline / 3600d;
			// 48 hours offline = 100% Repose Energy (~1% each 30mins source: http://forums.na.aiononline.com/na/showthread.php?t=105940)
			long addReposeEnergy = Math.round((hours / 48) * player.getCommonData().getMaxReposeEnergy());
			// Additional Energy of Repose bonus if inside house
			House house = player.getActiveHouse();
			if (house != null) {
				HouseAddress hPos = house.getAddress();
				if (player.getWorldId() == hPos.getMapId()
					&& PositionUtil.isInRange(player.getX(), player.getY(), player.getZ(), hPos.getX(), hPos.getY(), hPos.getZ(), 7))
					addReposeEnergy *= house.getHouseType() == HouseType.STUDIO ? 1.05f : 1.10f; // apartment = 5% bonus, other houses 10%
			}
			player.getCommonData().addReposeEnergy(addReposeEnergy);
		}
	}

	private static void activatePassiveSkillEffects(Player player) {
		for (PlayerSkillEntry skillEntry : player.getSkillList().getAllSkills()) {
			SkillTemplate skillTemplate = DataManager.SKILL_DATA.getSkillTemplate(skillEntry.getSkillId());
			if (skillTemplate.isPassive())
				SkillEngine.getInstance().applyEffectDirectly(skillTemplate, skillEntry.getSkillLevel(), player, player);
		}
	}

	private static boolean validateFortressZone(Player player) {
		FortressLocation fortress = SiegeService.getInstance().findFortress(player.getWorldId(), player.getX(), player.getY(), player.getZ());
		if (fortress != null && fortress.isVulnerable() && fortress.isEnemy(player)) {
			long lastOnlineMillis = player.getCommonData().getLastOnline() == null ? 0 : player.getCommonData().getLastOnline().getTime();
			// only relocate if the player logged out before siege start (online enemies automatically get teleported outside the fortress)
			if (lastOnlineMillis < SiegeService.getInstance().getSiege(fortress).getStartTime()) {
				BindPointPosition bind = player.getBindPoint();
				if (bind != null) {
					World.getInstance().setPosition(player, bind.getMapId(), bind.getX(), bind.getY(), bind.getZ(), bind.getHeading());
				} else {
					PlayerInitialData.LocationData start = DataManager.PLAYER_INITIAL_DATA.getSpawnLocation(player.getRace());
					World.getInstance().setPosition(player, start.getMapId(), start.getX(), start.getY(), start.getZ(), start.getHeading());
				}
				return false;
			}
		}
		return true;
	}

	/**
	 * Checks if the player is allowed to be in the current vortex zone. He will be sent to the locations home point if not.
	 */
	private static void validateVortexZone(Player player) {
		VortexLocation loc = VortexService.getInstance().getLocationByWorld(player.getWorldId());
		if (loc != null && player.getRace().equals(loc.getInvadersRace())) {
			if (loc.isInsideLocation(player) && loc.isActive() && loc.getVortexController().getPassedPlayers().containsKey(player.getObjectId()))
				return;

			int mapId = loc.getHomeWorldId();
			float x = loc.getHomePoint().getX();
			float y = loc.getHomePoint().getY();
			float z = loc.getHomePoint().getZ();
			byte h = loc.getHomePoint().getHeading();
			World.getInstance().setPosition(player, mapId, x, y, z, h);
		}
	}

	private static void sendItemInfos(AionConnection client, Player player) {
		player.setCubeLimit();
		player.setWarehouseLimit();
		// items
		Storage inventory = player.getInventory();
		List<Item> allItems = new ArrayList<>();
		if (inventory.getKinah() == 0) {
			inventory.increaseKinah(0); // create an empty object with value 0
		}
		allItems.add(inventory.getKinahItem()); // always included even with 0 count, and first in the packet !
		allItems.addAll(player.getEquipment().getEquippedItems());
		allItems.addAll(inventory.getItems());

		SplitList<Item> inventoryItemSplitList = new FixedElementCountSplitList<>(allItems, true, 10);
		inventoryItemSplitList.forEach(part -> client.sendPacket(new SM_INVENTORY_INFO(part.isFirst(), part, player)));
		client.sendPacket(new SM_INVENTORY_INFO(false, Collections.emptyList(), player));
	}

	private static void sendWarehouseItemInfos(AionConnection client, Player player) {
		WarehouseService.sendWarehouseInfo(player, true);
		// from 30 to 49, from 60 to 79
		for (int i = StorageType.PET_BAG_MIN - 2; i <= StorageType.HOUSE_WH_MAX; i++) {
			if (i >= 50 && i < StorageType.HOUSE_WH_MIN)
				continue;
			IStorage storage = player.getStorage(i);
			if (storage == null || storage.getItemsWithKinah().size() == 0) {
				client.sendPacket(new SM_WAREHOUSE_INFO(null, i, 0, true, player));
				continue;
			}
			SplitList<Item> warehouseItemSplitList = new FixedElementCountSplitList<>(storage.getItemsWithKinah(), true, 10);
			int storageType = i;
			warehouseItemSplitList.forEach(part -> client.sendPacket(new SM_WAREHOUSE_INFO(part, storageType, 0, part.isFirst(), player)));
			client.sendPacket(new SM_WAREHOUSE_INFO(null, storageType, 0, false, player));
			client.sendPacket(new SM_WAREHOUSE_INFO(null, i, 0, false, player));
		}
	}

	private static void sendMacroList(AionConnection client, Player player) {
		SplitList<Macros.Macro> macroSplitList = new DynamicServerPacketBodySplitList<>(player.getMacros().getAll(), true, SM_MACRO_LIST.STATIC_BODY_SIZE,
			SM_MACRO_LIST.DYNAMIC_BODY_PART_SIZE_CALCULATOR);
		macroSplitList.forEach(part -> PacketSendUtility.sendPacket(player, new SM_MACRO_LIST(player.getObjectId(), part, part.isFirst())));
	}
}

class GeneralUpdateTask implements Runnable {

	private static final Logger log = LoggerFactory.getLogger(GeneralUpdateTask.class);
	private final int playerId;

	GeneralUpdateTask(int playerId) {
		this.playerId = playerId;
	}

	@Override
	public void run() {
		Player player = World.getInstance().getPlayer(playerId);
		if (player != null) {
			try {
				AbyssRankDAO.storeAbyssRank(player);
				PlayerSkillListDAO.storeSkills(player);
				PlayerQuestListDAO.store(player);
				PlayerDAO.storePlayer(player);
				for (House house : player.getHouses())
					house.save();
			} catch (Exception ex) {
				log.error("Exception during periodic saving of player " + player.getName(), ex);
			}
		}
	}
}

class ItemUpdateTask implements Runnable {

	private static final Logger log = LoggerFactory.getLogger(ItemUpdateTask.class);
	private final int playerId;

	ItemUpdateTask(int playerId) {
		this.playerId = playerId;
	}

	@Override
	public void run() {
		Player player = World.getInstance().getPlayer(playerId);
		if (player != null) {
			try {
				InventoryDAO.store(player);
				ItemStoneListDAO.save(player);
			} catch (Exception ex) {
				log.error("Exception during periodic saving of player items " + player.getName(), ex);
			}
		}
	}
}
