/*	EQEMu: Everquest Server Emulator
	Copyright (C) 2001-2006 EQEMu Development Team (http://eqemulator.net)

	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; version 2 of the License.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY except by those people which sell it, which
	are required to give you total support for your newly bought product;
	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, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include "worlddb.h"
#include "../common/strings.h"
#include "../common/eq_packet_structs.h"
#include "../common/inventory_profile.h"
#include "../common/rulesys.h"
#include <iostream>
#include <cstdlib>
#include <vector>
#include "sof_char_create_data.h"
#include "../common/repositories/character_instance_safereturns_repository.h"
#include "../common/repositories/inventory_repository.h"
#include "../common/repositories/criteria/content_filter_criteria.h"
#include "../common/zone_store.h"
#include "../common/repositories/character_data_repository.h"
#include "../common/repositories/character_bind_repository.h"
#include "../common/repositories/character_material_repository.h"
#include "../common/repositories/start_zones_repository.h"

WorldDatabase database;
WorldDatabase content_db;
extern std::vector<RaceClassAllocation> character_create_allocations;
extern std::vector<RaceClassCombos> character_create_race_class_combos;

void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **out_app, uint32 client_version_bit)
{
	EQ::versions::ClientVersion
		   client_version  = EQ::versions::ConvertClientVersionBitToClientVersion(client_version_bit);
	size_t character_limit = EQ::constants::StaticLookup(client_version)->CharacterCreationLimit;

	if (character_limit > EQ::constants::CHARACTER_CREATION_LIMIT) {
		character_limit = EQ::constants::CHARACTER_CREATION_LIMIT;
	}

	// Force Titanium clients to use '8'
	if (client_version == EQ::versions::ClientVersion::Titanium) {
		character_limit = 8;
	}

	auto characters = CharacterDataRepository::GetWhere(
		database,
		fmt::format(
			"`account_id` = {} AND `deleted_at` IS NULL ORDER BY `name` LIMIT {}",
			account_id,
			character_limit
		)
	);

	size_t character_count = characters.size();
	if (characters.empty()) {
		*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
		auto *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
		cs->CharCount  = 0;
		cs->TotalChars = character_limit;
		return;
	}

	std::vector<uint32_t> character_ids;
	for (auto &e: characters) {
		character_ids.push_back(e.id);
	}

	const auto& inventories = InventoryRepository::GetWhere(
		*this,
		fmt::format(
			"`character_id` IN ({}) AND `slot_id` BETWEEN {} AND {}",
			Strings::Join(character_ids, ","),
			EQ::invslot::slotHead,
			EQ::invslot::slotFeet
		)
	);

	const auto& character_binds = CharacterBindRepository::GetWhere(
		*this,
		fmt::format(
			"`id` IN ({}) ORDER BY id, slot",
			Strings::Join(character_ids, ",")
		)
	);

	const auto& character_materials = CharacterMaterialRepository::GetWhere(
		*this,
		fmt::format(
			"`id` IN ({}) ORDER BY id, slot",
			Strings::Join(character_ids, ",")
		)
	);

	size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count);
	*out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size);

	unsigned char *buff_ptr = (*out_app)->pBuffer;
	auto          *cs       = (CharacterSelect_Struct *) buff_ptr;

	cs->CharCount  = character_count;
	cs->TotalChars = character_limit;

	buff_ptr += sizeof(CharacterSelect_Struct);
	for (auto &e: characters) {
		auto                 *cse = (CharacterSelectEntry_Struct *) buff_ptr;
		PlayerProfile_Struct pp;
		EQ::InventoryProfile inv;

		pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
		inv.SetInventoryVersion(client_version);
		inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support

		uint32 character_id = e.id;
		uint8  has_home     = 0;
		uint8  has_bind     = 0;

		memset(&pp, 0, sizeof(PlayerProfile_Struct));
		memset(cse->Name, 0, sizeof(cse->Name));
		strcpy(cse->Name, e.name.c_str());
		cse->Class       = e.class_;
		cse->Race        = e.race;
		cse->Level       = e.level;
		cse->ShroudClass = cse->Class;
		cse->ShroudRace  = cse->Race;
		cse->Zone        = e.zone_id;
		cse->Instance    = 0;
		cse->Gender      = e.gender;
		cse->Face        = e.face;

		for (auto &s: cse->Equip) {
			s.Material        = 0;
			s.Unknown1        = 0;
			s.EliteModel      = 0;
			s.HerosForgeModel = 0;
			s.Unknown2        = 0;
			s.Color           = 0;
		}

		cse->Unknown15       = 0xFF;
		cse->Unknown19       = 0xFF;
		cse->DrakkinTattoo   = e.drakkin_tattoo;
		cse->DrakkinDetails  = e.drakkin_details;
		cse->Deity           = e.deity;
		cse->PrimaryIDFile   = 0;                            // Processed Below
		cse->SecondaryIDFile = 0;                        // Processed Below
		cse->HairColor       = e.hair_color;
		cse->BeardColor      = e.beard_color;
		cse->EyeColor1       = e.eye_color_1;
		cse->EyeColor2       = e.eye_color_2;
		cse->HairStyle       = e.hair_style;
		cse->Beard           = e.beard;
		cse->GoHome          = 0;                                // Processed Below
		cse->Tutorial        = 0;                                // Processed Below
		cse->DrakkinHeritage = e.drakkin_heritage;
		cse->Unknown1        = 0;
		cse->Enabled         = 1;
		cse->LastLogin       = e.last_login;            // RoF2 value: 1212696584
		cse->Unknown2        = 0;

		if (RuleB(World, EnableReturnHomeButton)) {
			int now = time(nullptr);
			if (now - e.last_login >= RuleI(World, MinOfflineTimeToReturnHome)) {
				cse->GoHome = 1;
			}
		}

		if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) {
			cse->Tutorial = 1;
		}

		// binds
		int bind_count = 0;
		for (auto &bind : character_binds) {
			if (bind.id != e.id) {
				continue;
			}

			if (bind.slot == 4) {
				has_home = 1;
				pp.binds[4].zone_id     = bind.zone_id;
				pp.binds[4].instance_id = bind.instance_id;
				pp.binds[4].x           = bind.x;
				pp.binds[4].y           = bind.y;
				pp.binds[4].z           = bind.z;
				pp.binds[4].heading     = bind.heading;
			}

			if (bind.slot == 0) {
				has_bind = 1;
			}

			bind_count++;
		}

		if (has_home == 0 || has_bind == 0) {
			const auto &start_zones = StartZonesRepository::GetWhere(
				content_db,
				fmt::format(
					"`player_class` = {} AND `player_deity` = {} AND `player_race` = {} {}",
					cse->Class,
					cse->Deity,
					cse->Race,
					ContentFilterCriteria::apply().c_str()
				)
			);

			if (!start_zones.empty()) {
				pp.binds[4].zone_id = start_zones[0].zone_id;
				pp.binds[4].x       = start_zones[0].x;
				pp.binds[4].y       = start_zones[0].y;
				pp.binds[4].z       = start_zones[0].z;
				pp.binds[4].heading = start_zones[0].heading;

				if (start_zones[0].bind_id != 0) {
					pp.binds[4].zone_id = start_zones[0].bind_id;
					auto z = GetZone(pp.binds[4].zone_id);
					if (z) {
						pp.binds[4].x       = z->safe_x;
						pp.binds[4].y       = z->safe_y;
						pp.binds[4].z       = z->safe_z;
						pp.binds[4].heading = z->safe_heading;
					}
				} else {
					pp.binds[4].zone_id = start_zones[0].zone_id;
					pp.binds[4].x       = start_zones[0].x;
					pp.binds[4].y       = start_zones[0].y;
					pp.binds[4].z       = start_zones[0].z;
					pp.binds[4].heading = start_zones[0].heading;

					if (pp.binds[4].x == 0 && pp.binds[4].y == 0 && pp.binds[4].z == 0 && pp.binds[4].heading == 0) {
						auto zone = GetZone(pp.binds[4].zone_id);
						if (zone) {
							pp.binds[4].x       = zone->safe_x;
							pp.binds[4].y       = zone->safe_y;
							pp.binds[4].z       = zone->safe_z;
							pp.binds[4].heading = zone->safe_heading;
						}
					}
				}
			}
			else {
				LogError("No start zone found for class [{}] deity [{}] race [{}]", cse->Class, cse->Deity, cse->Race);
			}


			pp.binds[0] = pp.binds[4];

			// If we don't have home set, set it
			if (has_home == 0) {
				auto bind = CharacterBindRepository::NewEntity();
				bind.id          = character_id;
				bind.zone_id     = pp.binds[4].zone_id;
				bind.instance_id = 0;
				bind.x           = pp.binds[4].x;
				bind.y           = pp.binds[4].y;
				bind.z           = pp.binds[4].z;
				bind.heading     = pp.binds[4].heading;
				bind.slot        = 4;
				CharacterBindRepository::ReplaceOne(*this, bind);
			}

			// If we don't have regular bind set, set it
			if (has_bind == 0) {
				auto bind = CharacterBindRepository::NewEntity();
				bind.id          = character_id;
				bind.zone_id     = pp.binds[0].zone_id;
				bind.instance_id = 0;
				bind.x           = pp.binds[0].x;
				bind.y           = pp.binds[0].y;
				bind.z           = pp.binds[0].z;
				bind.heading     = pp.binds[0].heading;
				bind.slot        = 0;
				CharacterBindRepository::ReplaceOne(*this, bind);
			}
		}

		// If our bind count is less than 5, then we have null data that needs to be filled in
		if (bind_count < 5) {
			// we know that home and main bind must be valid here, so we don't check those
			// we also use home to fill in the null data like live does.

			std::vector<CharacterBindRepository::CharacterBind> binds;

			for (int i = 1; i < 4; i++) {
				if (pp.binds[i].zone_id != 0) { // we assume 0 is the only invalid one ...
					continue;
				}

				auto bind = CharacterBindRepository::NewEntity();

				bind.slot        = i;
				bind.id          = character_id;
				bind.zone_id     = pp.binds[4].zone_id;
				bind.instance_id = 0;
				bind.x           = pp.binds[4].x;
				bind.y           = pp.binds[4].y;
				bind.z           = pp.binds[4].z;
				bind.heading     = pp.binds[4].heading;
				binds.emplace_back(bind);
			}

			CharacterBindRepository::ReplaceMany(*this, binds);
		}

		for (auto &cm : character_materials) {
			pp.item_tint.Slot[cm.slot].Red     = cm.red;
			pp.item_tint.Slot[cm.slot].Green   = cm.green;
			pp.item_tint.Slot[cm.slot].Blue    = cm.blue;
			pp.item_tint.Slot[cm.slot].UseTint = cm.use_tint;
			pp.item_tint.Slot[cm.slot].Color   = cm.color;
		}

		if (GetCharSelInventory(inventories, e, &inv)) {
			const EQ::ItemData     *item          = nullptr;
			const EQ::ItemInstance *inst          = nullptr;
			int16                  inventory_slot = 0;

			for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
				inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
				if (inventory_slot == INVALID_INDEX) { continue; }
				inst = inv.GetItem(inventory_slot);
				if (inst == nullptr) {
					continue;
				}

				item = inst->GetItem();
				if (item == nullptr) {
					continue;
				}

				if (matslot > 6) {
					uint32 item_id_file = 0;
					// Weapon Models
					if (inst->GetOrnamentationIDFile() != 0) {
						item_id_file = inst->GetOrnamentationIDFile();
						cse->Equip[matslot].Material = item_id_file;
					}
					else {
						if (strlen(item->IDFile) > 2) {
							item_id_file = Strings::ToInt(&item->IDFile[2]);
							cse->Equip[matslot].Material = item_id_file;
						}
					}

					if (matslot == EQ::textures::weaponPrimary) {
						cse->PrimaryIDFile = item_id_file;
					}
					else {
						cse->SecondaryIDFile = item_id_file;
					}
				}
				else {
					// Armor Materials/Models
					uint32 color = (
						pp.item_tint.Slot[matslot].UseTint ?
							pp.item_tint.Slot[matslot].Color :
							inst->GetColor()
					);
					cse->Equip[matslot].Material        = item->Material;
					cse->Equip[matslot].EliteModel      = item->EliteMaterial;
					cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
					cse->Equip[matslot].Color           = color;
				}
			}
		}
		else {
			printf("Error loading inventory for %s\n", cse->Name);
		}
		buff_ptr += sizeof(CharacterSelectEntry_Struct);
	}
}

int WorldDatabase::MoveCharacterToBind(int character_id, uint8 bind_number)
{
	/*  if an invalid bind point is specified, use the primary bind */
	if (bind_number > 4)
		bind_number = 0;

	std::string query = fmt::format(
		SQL(
			SELECT
			zone_id, instance_id, x, y, z, heading
			FROM
			character_bind
			WHERE
			id = {}
			AND
			slot = {}
			LIMIT 1
		),
		character_id,
		bind_number
	);
	auto results = database.QueryDatabase(query);
	if(!results.Success() || results.RowCount() == 0) {
		return 0;
	}

	int zone_id, instance_id;
	double x, y, z, heading;
	for (auto row = results.begin(); row != results.end(); ++row) {
		zone_id = Strings::ToInt(row[0]);
		instance_id = Strings::ToInt(row[1]);
		x = Strings::ToFloat(row[2]);
		y = Strings::ToFloat(row[3]);
		z = Strings::ToFloat(row[4]);
		heading = Strings::ToFloat(row[5]);
	}

	query = fmt::format(
		SQL(
			UPDATE
			`character_data`
			SET
			`zone_id` = {},
			`zone_instance` = {},
			`x` = {},
			`y` = {},
			`z` = {},
			`heading` = {}
			WHERE `id` = {}
		),
		zone_id,
		instance_id,
		x,
		y,
		z,
		heading,
		character_id
	);

	results = database.QueryDatabase(query);
	if(!results.Success()) {
		return 0;
	}

	return zone_id;
}

int WorldDatabase::MoveCharacterToInstanceSafeReturn(
	int character_id,
	int instance_zone_id,
	int instance_id
)
{
	int zone_id = 0;

	// only moves if safe return is for specified zone instance
	auto entries = CharacterInstanceSafereturnsRepository::GetWhere(
		database,
		fmt::format(
			"character_id = {} AND instance_zone_id = {} AND instance_id = {} AND safe_zone_id > 0",
			character_id, instance_zone_id, instance_id
		)
	);

	if (!entries.empty()) {
		auto entry = entries.front();

		auto results = QueryDatabase(
			fmt::format(
				SQL(
					UPDATE character_data
					SET zone_id = {}, zone_instance = 0, x = {}, y = {}, z = {}, heading = {}
						WHERE id = {};
				),
				entry.safe_zone_id,
				entry.safe_x,
				entry.safe_y,
				entry.safe_z,
				entry.safe_heading,
				character_id
			)
		);

		if (results.Success() && results.RowsAffected() > 0) {
			zone_id = entry.safe_zone_id;
		}
	}

	if (zone_id == 0) {
		zone_id = MoveCharacterToBind(character_id);
	}

	return zone_id;
}

bool WorldDatabase::GetStartZone(
	PlayerProfile_Struct *pp,
	CharCreate_Struct *p_char_create_struct,
	bool is_titanium
)
{
	// SoF doesn't send the player_choice field in character creation, it now sends the real zoneID instead.
	//
	// For SoF, search for an entry in start_zones with a matching zone_id, class, race and deity.
	//
	// For now, if no row matching row is found, send them to Crescent Reach, as that is probably the most likely
	// reason for no match being found.
	//
	if (!pp || !p_char_create_struct) {
		return false;
	}

	pp->x                    = 0;
	pp->y                    = 0;
	pp->z                    = 0;
	pp->heading              = 0;
	pp->zone_id              = 0;
	pp->binds[0].x           = 0;
	pp->binds[0].y           = 0;
	pp->binds[0].z           = 0;
	pp->binds[0].zone_id     = 0;
	pp->binds[0].instance_id = 0;

	// see if we have an entry for start_zone. We can support both titanium & SOF+ by having two entries per class/race/deity combo with different zone_ids
	std::string query;

	if (is_titanium) {
		// Titanium sends player choice (starting city) instead of a zone id
		query = StringFormat(
			"SELECT x, y, z, heading, start_zone, bind_id, bind_x, bind_y, bind_z FROM start_zones WHERE player_choice = %i "
			"AND player_class = %i AND player_deity = %i AND player_race = %i %s",
			p_char_create_struct->start_zone,
			p_char_create_struct->class_,
			p_char_create_struct->deity,
			p_char_create_struct->race,
			ContentFilterCriteria::apply().c_str()
		);
		LogInfo("Titanium Start zone query: [{}]\n", query.c_str());
	}
	else {
		query = StringFormat(
			"SELECT x, y, z, heading, start_zone, bind_id, bind_x, bind_y, bind_z FROM start_zones WHERE zone_id = %i "
			"AND player_class = %i AND player_deity = %i AND player_race = %i %s",
			p_char_create_struct->start_zone,
			p_char_create_struct->class_,
			p_char_create_struct->deity,
			p_char_create_struct->race,
			ContentFilterCriteria::apply().c_str()
		);
		LogInfo("SoF Start zone query: [{}]\n", query.c_str());
	}

    auto results = QueryDatabase(query);
	if(!results.Success()) {
		return false;
	}

	LogInfo("SoF Start zone query: [{}]\n", query.c_str());

	if (results.RowCount() == 0) {
		printf("No start_zones entry in database, using defaults\n");
		is_titanium ? SetTitaniumDefaultStartZone(pp, p_char_create_struct) : SetSoFDefaultStartZone(pp, p_char_create_struct);
    }
    else {
		LogInfo("Found starting location in start_zones");
		auto row = results.begin();
		pp->x                = Strings::ToFloat(row[0]);
		pp->y                = Strings::ToFloat(row[1]);
		pp->z                = Strings::ToFloat(row[2]);
		pp->heading          = Strings::ToFloat(row[3]);
		pp->zone_id          = Strings::ToInt(row[4]);
		pp->binds[0].zone_id = Strings::ToInt(row[5]);
		pp->binds[0].x       = Strings::ToFloat(row[6]);
		pp->binds[0].y       = Strings::ToFloat(row[7]);
		pp->binds[0].z       = Strings::ToFloat(row[8]);
		pp->binds[0].heading = Strings::ToFloat(row[3]);
	}

	if (
		pp->x == 0 &&
		pp->y == 0 &&
		pp->z == 0 &&
		pp->heading == 0
	) {
		auto zone = GetZone(pp->zone_id);
		if (zone) {
			pp->x       = zone->safe_x;
			pp->y       = zone->safe_y;
			pp->z       = zone->safe_z;
			pp->heading = zone->safe_heading;
		}
	}

	if (
		pp->binds[0].x == 0 &&
		pp->binds[0].y == 0 &&
		pp->binds[0].z == 0 &&
		pp->binds[0].heading == 0
	) {
		auto zone = GetZone(pp->binds[0].zone_id);
		if (zone) {
			pp->binds[0].x       = zone->safe_x;
			pp->binds[0].y       = zone->safe_y;
			pp->binds[0].z       = zone->safe_z;
			pp->binds[0].heading = zone->safe_heading;
		}
	}

	return true;
}

void WorldDatabase::SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc){
	int sof_start_zone_id = RuleI(World, SoFStartZoneID);
	if (sof_start_zone_id > 0) {
			in_pp->zone_id = sof_start_zone_id;
			in_cc->start_zone = in_pp->zone_id;
	}
	else if (in_cc->start_zone == RuleI(World, TutorialZoneID)) {
		in_pp->zone_id = in_cc->start_zone;
	}
	else {
		in_pp->x = in_pp->binds[0].x = -51.0f;
		in_pp->y = in_pp->binds[0].y = -20.0f;
		in_pp->z = in_pp->binds[0].z = 0.79f;
		in_pp->heading = in_pp->binds[0].heading = 0.0f;
		in_pp->zone_id = in_pp->binds[0].zone_id = Zones::CRESCENT; // Crescent Reach.
	}
}

void WorldDatabase::SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc)
{
	int titanium_start_zone_id = RuleI(World, TitaniumStartZoneID);
	if (titanium_start_zone_id > 0) {
		in_pp->zone_id = titanium_start_zone_id;
		in_pp->binds[0].zone_id = titanium_start_zone_id;
	} else {
		switch (in_cc->start_zone)
		{
			case StartZoneIndex::Odus:
			{
				if (in_cc->deity == Deity::CazicThule) // Cazic-Thule Erudites go to Paineel
				{
					in_pp->zone_id = Zones::PAINEEL; // paineel
					in_pp->binds[0].zone_id = Zones::PAINEEL;
				}
				else
				{
					in_pp->zone_id = Zones::ERUDNEXT;	// erudnext
					in_pp->binds[0].zone_id = Zones::TOX;	// tox
				}
				break;
			}
			case StartZoneIndex::Qeynos:
			{
				in_pp->zone_id = Zones::QEYNOS2;	// qeynos2
				in_pp->binds[0].zone_id = Zones::QEYNOS2;	// qeynos2
				break;
			}
			case StartZoneIndex::Halas:
			{
				in_pp->zone_id = Zones::HALAS;	// halas
				in_pp->binds[0].zone_id = Zones::EVERFROST;	// everfrost
				break;
			}
			case StartZoneIndex::Rivervale:
			{
				in_pp->zone_id = Zones::RIVERVALE;	// rivervale
				in_pp->binds[0].zone_id = Zones::KITHICOR;	// kithicor
				break;
			}
			case StartZoneIndex::Freeport:
			{
				in_pp->zone_id = Zones::FREPORTW;	// freportw
				in_pp->binds[0].zone_id = Zones::FREPORTW;	// freportw
				break;
			}
			case StartZoneIndex::Neriak:
			{
				in_pp->zone_id = Zones::NERIAKA;	// neriaka
				in_pp->binds[0].zone_id = Zones::NEKTULOS;	// nektulos
				break;
			}
			case StartZoneIndex::Grobb:
			{
				in_pp->zone_id = Zones::GROBB;	// grobb
				in_pp->binds[0].zone_id = Zones::INNOTHULE;	// innothule
				break;
			}
			case StartZoneIndex::Oggok:
			{
				in_pp->zone_id = Zones::OGGOK;	// oggok
				in_pp->binds[0].zone_id = Zones::FEERROTT;	// feerrott
				break;
			}
			case StartZoneIndex::Kaladim:
			{
				in_pp->zone_id = Zones::KALADIMA;	// kaladima
				in_pp->binds[0].zone_id = Zones::BUTCHER;	// butcher
				break;
			}
			case StartZoneIndex::GreaterFaydark:
			{
				in_pp->zone_id = Zones::GFAYDARK;	// gfaydark
				in_pp->binds[0].zone_id = Zones::GFAYDARK;	// gfaydark
				break;
			}
			case StartZoneIndex::Felwithe:
			{
				in_pp->zone_id = Zones::FELWITHEA;	// felwithea
				in_pp->binds[0].zone_id = Zones::GFAYDARK;	// gfaydark
				break;
			}
			case StartZoneIndex::Akanon:
			{
				in_pp->zone_id = Zones::AKANON;	// akanon
				in_pp->binds[0].zone_id = Zones::STEAMFONT;	// steamfont
				break;
			}
			case StartZoneIndex::Cabilis:
			{
				in_pp->zone_id = Zones::CABWEST;	// cabwest
				in_pp->binds[0].zone_id = Zones::FIELDOFBONE;	// fieldofbone
				break;
			}
			case StartZoneIndex::SharVahl:
			{
				in_pp->zone_id = Zones::SHARVAHL;	// sharvahl
				in_pp->binds[0].zone_id = Zones::SHARVAHL;	// sharvahl
				break;
			}
			case StartZoneIndex::RatheMtn:
			{
				in_pp->zone_id = Zones::RATHEMTN;	// rathemtn
				in_pp->binds[0].zone_id = Zones::RATHEMTN;	// rathemtn
				break;
			}
		}
	}
}

void WorldDatabase::GetLauncherList(std::vector<std::string> &rl) {
	rl.clear();

    const std::string query = "SELECT name FROM launcher";
    auto results = QueryDatabase(query);
    if (!results.Success()) {
        LogError("WorldDatabase::GetLauncherList: {}", results.ErrorMessage().c_str());
        return;
    }

    for (auto row = results.begin(); row != results.end(); ++row)
        rl.push_back(row[0]);

}

bool WorldDatabase::GetCharacterLevel(const char *name, int &level)
{
	std::string query = StringFormat("SELECT level FROM character_data WHERE name = '%s'", name);
	auto results = QueryDatabase(query);
	if (!results.Success()) {
        LogError("WorldDatabase::GetCharacterLevel: {}", results.ErrorMessage().c_str());
        return false;
	}

	if (results.RowCount() == 0)
        return false;

    auto row = results.begin();
    level = Strings::ToInt(row[0]);

    return true;
}

bool WorldDatabase::LoadCharacterCreateAllocations()
{
	character_create_allocations.clear();

	std::string query = "SELECT * FROM char_create_point_allocations ORDER BY id";
	auto results = QueryDatabase(query);
	if (!results.Success())
        return false;

    for (auto row = results.begin(); row != results.end(); ++row) {
        RaceClassAllocation allocate;
		allocate.Index = Strings::ToInt(row[0]);
		allocate.BaseStats[0] = Strings::ToInt(row[1]);
		allocate.BaseStats[3] = Strings::ToInt(row[2]);
		allocate.BaseStats[1] = Strings::ToInt(row[3]);
		allocate.BaseStats[2] = Strings::ToInt(row[4]);
		allocate.BaseStats[4] = Strings::ToInt(row[5]);
		allocate.BaseStats[5] = Strings::ToInt(row[6]);
		allocate.BaseStats[6] = Strings::ToInt(row[7]);
		allocate.DefaultPointAllocation[0] = Strings::ToInt(row[8]);
		allocate.DefaultPointAllocation[3] = Strings::ToInt(row[9]);
		allocate.DefaultPointAllocation[1] = Strings::ToInt(row[10]);
		allocate.DefaultPointAllocation[2] = Strings::ToInt(row[11]);
		allocate.DefaultPointAllocation[4] = Strings::ToInt(row[12]);
		allocate.DefaultPointAllocation[5] = Strings::ToInt(row[13]);
		allocate.DefaultPointAllocation[6] = Strings::ToInt(row[14]);

		character_create_allocations.push_back(allocate);
    }

	return true;
}

bool WorldDatabase::LoadCharacterCreateCombos()
{
	character_create_race_class_combos.clear();

	std::string query = "SELECT * FROM char_create_combinations ORDER BY race, class, deity, start_zone";
	auto results = QueryDatabase(query);
	if (!results.Success())
        return false;

	for (auto row = results.begin(); row != results.end(); ++row) {
		RaceClassCombos combo;
		combo.AllocationIndex = Strings::ToInt(row[0]);
		combo.Race = Strings::ToInt(row[1]);
		combo.Class = Strings::ToInt(row[2]);
		combo.Deity = Strings::ToInt(row[3]);
		combo.Zone = Strings::ToInt(row[4]);
		combo.ExpansionRequired = Strings::ToInt(row[5]);

		character_create_race_class_combos.push_back(combo);
	}

	return true;
}

bool WorldDatabase::GetCharSelInventory(
	const std::vector<InventoryRepository::Inventory> &inventories,
	const CharacterDataRepository::CharacterData &character,
	EQ::InventoryProfile *inv
)
{
	if (inventories.empty() || !character.id || !inv) {
		return false;
	}

	for (const auto& e : inventories) {
		if (e.character_id != character.id) {
			continue;
		}

		switch (e.slot_id) {
			case EQ::invslot::slotFace:
			case EQ::invslot::slotEar2:
			case EQ::invslot::slotNeck:
			case EQ::invslot::slotShoulders:
			case EQ::invslot::slotBack:
			case EQ::invslot::slotFinger1:
			case EQ::invslot::slotFinger2:
				continue;
			default:
				break;
		}


		uint32 augment_ids[EQ::invaug::SOCKET_COUNT] = {
			e.augment_one,
			e.augment_two,
			e.augment_three,
			e.augment_four,
			e.augment_five,
			e.augment_six
		};

		const EQ::ItemData* item = content_db.GetItem(e.item_id);
		if (!item) {
			continue;
		}

		EQ::ItemInstance *inst = content_db.CreateBaseItem(item, e.charges);

		if (!inst) {
			continue;
		}

		inst->SetCharges(e.charges);

		if (e.color > 0) {
			inst->SetColor(e.color);
		}

		if (item->IsClassCommon()) {
			for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) {
				if (augment_ids[i]) {
					inst->PutAugment(this, i, augment_ids[i]);
				}
			}
		}

		inst->SetAttuned(e.instnodrop);

		if (!e.custom_data.empty()) {
			inst->SetCustomDataString(e.custom_data);
		}

		if (e.ornament_icon != 0 || e.ornament_idfile != 0 || e.ornament_hero_model != 0) {
			inst->SetOrnamentIcon(e.ornament_icon);
			inst->SetOrnamentationIDFile(e.ornament_idfile);
			inst->SetOrnamentHeroModel(e.ornament_hero_model);
		} else if (item->HerosForgeModel > 0) {
			inst->SetOrnamentHeroModel(item->HerosForgeModel);
		}

		inv->PutItem(e.slot_id, *inst);

		safe_delete(inst);
	}

	return true;
}
