#include "CharacterComponent.h"
#include <BitStream.h>
#include "tinyxml2.h"
#include "Game.h"
#include "dLogger.h"
#include "GeneralUtils.h"
#include "dServer.h"
#include "dZoneManager.h"
#include "CDClientManager.h"
#include "InventoryComponent.h"
#include "ControllablePhysicsComponent.h"
#include "EntityManager.h"
#include "PossessorComponent.h"
#include "VehiclePhysicsComponent.h"

CharacterComponent::CharacterComponent(Entity* parent, Character* character) : Component(parent) {
	m_Character = character;

	m_IsRacing = false;
	m_IsGM = false;
	m_IsLanding = false;
	m_IsLEGOClubMember = true;
	m_Level = 1;

	m_DirtyCurrentActivity = false;
	m_DirtyGMInfo = false;
	m_DirtySocialInfo = false;

	m_PvpEnabled = false;
	m_GMLevel = character->GetGMLevel();

	m_EditorEnabled = false;
	m_EditorLevel = m_GMLevel;

	m_CurrentActivity = 0;
	m_CountryCode = 0;
	m_LastUpdateTimestamp = std::time(nullptr);

	LoadFromXML();

	//Check to see if we're landing:
	if (character->GetZoneID() != Game::server->GetZoneID()) {
		m_IsLanding = true;
	}

	if (LandingAnimDisabled(character->GetZoneID()) || LandingAnimDisabled(Game::server->GetZoneID()) || m_LastRocketConfig.empty()) {
		m_IsLanding = false; //Don't make us land on VE/minigames lol
	}
}

bool CharacterComponent::LandingAnimDisabled(int zoneID) {
	switch (zoneID) {
	case 0:
	case 556:
	case 1001:
	case 1101:
	case 1202:
	case 1203:
	case 1204:
	case 1301:
	case 1302:
	case 1303:
	case 1401:
	case 1402:
	case 1403:
	case 1603:
	case 2001:
		return true;

	default:
		return false;
	}

	return false;
}

CharacterComponent::~CharacterComponent() {
}

void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
	outBitStream->Write(m_IsRacing);
	if (m_IsRacing) {
		outBitStream->Write1();
		outBitStream->Write(m_VehicleObjectID);
		outBitStream->Write<uint8_t>(0);
	}
	
	outBitStream->Write1();
	outBitStream->Write(m_Level);
	outBitStream->Write0();
	
	if (bIsInitialUpdate) {
		outBitStream->Write0();
		outBitStream->Write0();
		outBitStream->Write0();
		outBitStream->Write0();
		
		outBitStream->Write(m_Character->GetHairColor());
		outBitStream->Write(m_Character->GetHairStyle());
		outBitStream->Write<uint32_t>(0); //Default "head"
		outBitStream->Write(m_Character->GetShirtColor());
		outBitStream->Write(m_Character->GetPantsColor());
		outBitStream->Write(m_Character->GetShirtStyle());
		outBitStream->Write<uint32_t>(0); //Default "head color"
		outBitStream->Write(m_Character->GetEyebrows());
		outBitStream->Write(m_Character->GetEyes());
		outBitStream->Write(m_Character->GetMouth());
		outBitStream->Write<uint64_t>(0); //AccountID, trying out if 0 works.
		outBitStream->Write(m_Character->GetLastLogin()); //Last login
		outBitStream->Write<uint64_t>(0); //"prop mod last display time"
		outBitStream->Write<uint64_t>(m_Uscore); //u-score
		outBitStream->Write0(); //Not free-to-play (disabled in DLU)

		//Stats:
		outBitStream->Write(m_CurrencyCollected);
		outBitStream->Write(m_BricksCollected);
		outBitStream->Write(m_SmashablesSmashed);
		outBitStream->Write(m_QuickBuildsCompleted);
		outBitStream->Write(m_EnemiesSmashed);
		outBitStream->Write(m_RocketsUsed);
		outBitStream->Write(m_MissionsCompleted);
		outBitStream->Write(m_PetsTamed);
		outBitStream->Write(m_ImaginationPowerUpsCollected);
		outBitStream->Write(m_LifePowerUpsCollected);
		outBitStream->Write(m_ArmorPowerUpsCollected);
		outBitStream->Write(m_MetersTraveled);
		outBitStream->Write(m_TimesSmashed);
		outBitStream->Write(m_TotalDamageTaken);
		outBitStream->Write(m_TotalDamageHealed);
		outBitStream->Write(m_TotalArmorRepaired);
		outBitStream->Write(m_TotalImaginationRestored);
		outBitStream->Write(m_TotalImaginationUsed);
		outBitStream->Write(m_DistanceDriven);
		outBitStream->Write(m_TimeAirborneInCar);
		outBitStream->Write(m_RacingImaginationPowerUpsCollected);
		outBitStream->Write(m_RacingImaginationCratesSmashed);
		outBitStream->Write(m_RacingCarBoostsActivated);
		outBitStream->Write(m_RacingTimesWrecked);
		outBitStream->Write(m_RacingSmashablesSmashed);
		outBitStream->Write(m_RacesFinished);
		outBitStream->Write(m_FirstPlaceRaceFinishes);
		
		outBitStream->Write0();
		outBitStream->Write(m_IsLanding);
		if (m_IsLanding) {
			outBitStream->Write(uint16_t(m_LastRocketConfig.size()));
			for (uint16_t character : m_LastRocketConfig) {
				outBitStream->Write(character);
			}
		}
	}

	outBitStream->Write(m_DirtyGMInfo);
	if (m_DirtyGMInfo) {
		outBitStream->Write(m_PvpEnabled);
		outBitStream->Write(m_IsGM);
		outBitStream->Write(m_GMLevel);
		outBitStream->Write(m_EditorEnabled);
		outBitStream->Write(m_EditorLevel);
	}
	
	outBitStream->Write(m_DirtyCurrentActivity);
	if (m_DirtyCurrentActivity) outBitStream->Write(m_CurrentActivity);
	
	outBitStream->Write(m_DirtySocialInfo);
	if (m_DirtySocialInfo) {
		outBitStream->Write(m_GuildID);
		outBitStream->Write<unsigned char>(static_cast<unsigned char>(m_GuildName.size()));
		if (!m_GuildName.empty())
			outBitStream->WriteBits(reinterpret_cast<const unsigned char*>(m_GuildName.c_str()), static_cast<unsigned char>(m_GuildName.size()) * sizeof(wchar_t) * 8);
		
		outBitStream->Write(m_IsLEGOClubMember);
		outBitStream->Write(m_CountryCode);
	}
}

bool CharacterComponent::GetPvpEnabled() const
{
	return m_PvpEnabled;
}

void CharacterComponent::SetPvpEnabled(const bool value)
{
	m_DirtyGMInfo = true;
	
	m_PvpEnabled = value;
}

void CharacterComponent::HandleLevelUp()
{
	auto* rewardsTable = CDClientManager::Instance()->GetTable<CDRewardsTable>("Rewards");

	const auto& rewards = rewardsTable->GetByLevelID(m_Level);

	auto* parent = m_Character->GetEntity();

	if (parent == nullptr)
	{
		return;
	}

	auto* inventoryComponent = parent->GetComponent<InventoryComponent>();
	auto* controllablePhysicsComponent = parent->GetComponent<ControllablePhysicsComponent>();

	if (inventoryComponent == nullptr || controllablePhysicsComponent == nullptr)
	{
		return;
	}

	for (auto* reward : rewards)
	{
		switch (reward->rewardType)
		{
		case 0:
			inventoryComponent->AddItem(reward->value, reward->count);
			break;

		case 4:
			{
				auto* items = inventoryComponent->GetInventory(ITEMS);
				items->SetSize(items->GetSize() + reward->value);
			}
			break;

		case 9:
			controllablePhysicsComponent->SetSpeedMultiplier(static_cast<float>(reward->value) / 500.0f);
			break;

		case 11:
			break;

		case 12:
			break;
		
		default:
			break;
		}
	}
	
}

void CharacterComponent::SetGMLevel(int gmlevel) {
	m_DirtyGMInfo = true;
	if (gmlevel > 0) m_IsGM = true;
	else m_IsGM = false;
	m_GMLevel = gmlevel;
}

void CharacterComponent::LoadFromXML() {
    if (!m_Character) return;
    
    tinyxml2::XMLDocument* doc = m_Character->GetXMLDoc();
    if (!doc) return;
    
	tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char");
	if (!character) {
		Game::logger->Log("CharacterComponent", "Failed to find char tag while loading XML!\n");
		return;
	}

	character->QueryInt64Attribute("ls", &m_Uscore);

	// Load the statistics
    const auto* statisticsAttribute = character->FindAttribute("stt");
    if (statisticsAttribute) {
        InitializeStatisticsFromString(std::string(statisticsAttribute->Value()));
    } else {
        InitializeEmptyStatistics();
    }

    // Load the zone statistics
    m_ZoneStatistics = {};
    auto zoneStatistics = character->FirstChildElement("zs");

    if (zoneStatistics) {
        auto child = zoneStatistics->FirstChildElement();
        while (child) {
            ZoneStatistics statistics = {};

            child->QueryUnsigned64Attribute("ac", &statistics.m_AchievementsCollected);
            child->QueryUnsigned64Attribute("bc", &statistics.m_BricksCollected);
            child->QueryUnsigned64Attribute("cc", &statistics.m_CoinsCollected);
            child->QueryUnsigned64Attribute("es", &statistics.m_EnemiesSmashed);
            child->QueryUnsigned64Attribute("qbc", &statistics.m_QuickBuildsCompleted);

            uint32_t mapID;
            child->QueryAttribute("map", &mapID);

            m_ZoneStatistics.insert({ (LWOMAPID) mapID, statistics });

            child = child->NextSiblingElement();
        }
    }

	const tinyxml2::XMLAttribute *rocketConfig = character->FindAttribute("lcbp");
	if (rocketConfig) {
		m_LastRocketConfig = GeneralUtils::ASCIIToUTF16(std::string(rocketConfig->Value()));
	}
	else
	{
		m_LastRocketConfig = u"";
	}

    //
    // Begin custom attributes
    //

    // Load the last rocket item ID
    const tinyxml2::XMLAttribute *lastRocketItemID = character->FindAttribute("lrid");
    if (lastRocketItemID) {
        m_LastRocketItemID = lastRocketItemID->Int64Value();
    }

    //
    // End custom attributes
    //

	if (m_GMLevel > 0) {
		m_IsGM = true;
		m_DirtyGMInfo = true;
		m_EditorLevel = m_GMLevel;
		m_EditorEnabled = false; //We're not currently in HF if we're loading in
	}

	//Annoying guild bs:
	const tinyxml2::XMLAttribute *guildName = character->FindAttribute("gn");
	if (guildName) {
		const char* gn = guildName->Value();
		int64_t gid = 0;
		character->QueryInt64Attribute("gid", &gid);
		if (gid != 0) {
			std::string guildname(gn);
			m_GuildName = GeneralUtils::ASCIIToUTF16(guildname);
			m_GuildID = gid;
			m_DirtySocialInfo = true;
		}
	}

	tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl");
	if (!level) {
		Game::logger->Log("CharacterComponent", "Failed to find lvl tag while loading XML!\n");
		return;
	}

	level->QueryAttribute("l", &m_Level);

	if (character->FindAttribute("time")) {
	     character->QueryUnsigned64Attribute("time", &m_TotalTimePlayed);
	} else {
	    m_TotalTimePlayed = 0;
	}
}

void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
    tinyxml2::XMLElement* minifig = doc->FirstChildElement("obj")->FirstChildElement("mf");
	if (!minifig) {
		Game::logger->Log("CharacterComponent", "Failed to find mf tag while updating XML!\n");
		return;
	}

    // write minifig information that might have been changed by commands 

    minifig->SetAttribute("es", m_Character->GetEyebrows());
    minifig->SetAttribute("ess", m_Character->GetEyes());
    minifig->SetAttribute("hc", m_Character->GetHairColor());
    minifig->SetAttribute("hs", m_Character->GetHairStyle());
    minifig->SetAttribute("l", m_Character->GetPantsColor());
    minifig->SetAttribute("lh", m_Character->GetLeftHand());
    minifig->SetAttribute("ms", m_Character->GetMouth());
    minifig->SetAttribute("rh", m_Character->GetRightHand());
    minifig->SetAttribute("t", m_Character->GetShirtColor());

    // done with minifig

	tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char");
	if (!character) {
		Game::logger->Log("CharacterComponent", "Failed to find char tag while updating XML!\n");
		return;
	}

	character->SetAttribute("ls", m_Uscore);
	character->SetAttribute("stt", StatisticsToString().c_str());

	// Set the zone statistics of the form <zs><s/> ... <s/></zs>
	auto zoneStatistics = character->FirstChildElement("zs");
	if (!zoneStatistics) zoneStatistics = doc->NewElement("zs");
	zoneStatistics->DeleteChildren();

	for (auto pair : m_ZoneStatistics) {
	    auto zoneStatistic = doc->NewElement("s");

	    zoneStatistic->SetAttribute("map", pair.first);
	    zoneStatistic->SetAttribute("ac", pair.second.m_AchievementsCollected);
	    zoneStatistic->SetAttribute("bc", pair.second.m_BricksCollected);
	    zoneStatistic->SetAttribute("cc", pair.second.m_CoinsCollected);
	    zoneStatistic->SetAttribute("es", pair.second.m_EnemiesSmashed);
	    zoneStatistic->SetAttribute("qbc", pair.second.m_QuickBuildsCompleted);

	    zoneStatistics->LinkEndChild(zoneStatistic);
	}

	character->LinkEndChild(zoneStatistics);

	if (!m_LastRocketConfig.empty()) {
		std::string config = GeneralUtils::UTF16ToWTF8(m_LastRocketConfig);
		character->SetAttribute("lcbp", config.c_str());
	}
	else
	{
		character->DeleteAttribute("lcbp");
	}

    //
    // Begin custom attributes
    //

    // Store last rocket item ID
    character->SetAttribute("lrid", m_LastRocketItemID);

    //
    // End custom attributes
    //

	tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl");
	if (!level) {
		Game::logger->Log("CharacterComponent", "Failed to find lvl tag while updating XML!\n");
		return;
	}

	level->SetAttribute("l", m_Level);

	auto newUpdateTimestamp = std::time(nullptr);
	Game::logger->Log("TotalTimePlayed", "Time since last save: %d\n", newUpdateTimestamp - m_LastUpdateTimestamp);

	m_TotalTimePlayed += newUpdateTimestamp - m_LastUpdateTimestamp;
	character->SetAttribute("time", m_TotalTimePlayed);

	m_LastUpdateTimestamp = newUpdateTimestamp;
}

void CharacterComponent::SetLastRocketConfig(std::u16string config) {
	m_IsLanding = !config.empty();
	m_LastRocketConfig = config;
}

void CharacterComponent::TrackMissionCompletion(bool isAchievement) {
    UpdatePlayerStatistic(MissionsCompleted);

    // Achievements are tracked separately for the zone
    if (isAchievement) {
        const auto mapID = dZoneManager::Instance()->GetZoneID().GetMapID();
        GetZoneStatisticsForMap(mapID).m_AchievementsCollected++;
    }
}

void CharacterComponent::TrackLOTCollection(LOT lot) {
    switch (lot) {
        // Handle all the imagination powerup lots
        case 935:   // 1 point
        case 4035:  // 2 points
        case 11910: // 3 points
        case 11911: // 5 points
        case 11918: // 10 points
            UpdatePlayerStatistic(ImaginationPowerUpsCollected);
            break;
        // Handle all the armor powerup lots
        case 6431:  // 1 point
        case 11912: // 2 points
        case 11913: // 3 points
        case 11914: // 5 points
        case 11919: // 10 points
            UpdatePlayerStatistic(ArmorPowerUpsCollected);
            break;
        // Handle all the life powerup lots
        case 177:   // 1 point
        case 11915: // 2 points
        case 11916: // 3 points
        case 11917: // 5 points
        case 11920: // 10 points
            UpdatePlayerStatistic(LifePowerUpsCollected);
            break;
        default:
            break;
    }
}

void CharacterComponent::TrackHealthDelta(int32_t health) {
    if (health > 0) {
        UpdatePlayerStatistic(TotalDamageHealed, health);
    } else {
        UpdatePlayerStatistic(TotalDamageTaken, -health);
    }
}

void CharacterComponent::TrackImaginationDelta(int32_t imagination) {
    if (imagination > 0) {
        UpdatePlayerStatistic(TotalImaginationRestored, imagination);
    } else {
        UpdatePlayerStatistic(TotalImaginationUsed, -imagination);
    }
}

void CharacterComponent::TrackArmorDelta(int32_t armor) {
    if (armor > 0) {
        UpdatePlayerStatistic(TotalArmorRepaired, armor);
    }
}

void CharacterComponent::TrackRebuildComplete() {
    UpdatePlayerStatistic(QuickBuildsCompleted);

    const auto mapID = dZoneManager::Instance()->GetZoneID().GetMapID();
    GetZoneStatisticsForMap(mapID).m_QuickBuildsCompleted++;
}

void CharacterComponent::TrackRaceCompleted(bool won) {
    m_RacesFinished++;
    if (won)
        m_FirstPlaceRaceFinishes++;
}

void CharacterComponent::TrackPositionUpdate(const NiPoint3& newPosition) {
    const auto distance = NiPoint3::Distance(newPosition, m_Parent->GetPosition());

    if (m_IsRacing) {
        UpdatePlayerStatistic(DistanceDriven, (uint64_t) distance);
    } else {
        UpdatePlayerStatistic(MetersTraveled, (uint64_t) distance);
    }
}

void CharacterComponent::HandleZoneStatisticsUpdate(LWOMAPID zoneID, const std::u16string &name, int32_t value) {
    auto zoneStatistics = &GetZoneStatisticsForMap(zoneID);

    if (name == u"BricksCollected") {
        m_BricksCollected += value;
        zoneStatistics->m_BricksCollected += value;
    } else if (name == u"CoinsCollected") {
        m_CurrencyCollected += value;
        zoneStatistics->m_CoinsCollected += value;
    } else if (name == u"EnemiesSmashed") {
        m_EnemiesSmashed += value;
        zoneStatistics->m_EnemiesSmashed += value;
    }
}

void CharacterComponent::UpdatePlayerStatistic(StatisticID updateID, uint64_t updateValue) {
    switch (updateID) {
        case CurrencyCollected:
            m_CurrencyCollected += updateValue;
            break;
        case BricksCollected:
            m_BricksCollected += updateValue;
            break;
        case SmashablesSmashed:
            m_SmashablesSmashed += updateValue;
            break;
        case QuickBuildsCompleted:
            m_QuickBuildsCompleted += updateValue;
            break;
        case EnemiesSmashed:
            m_EnemiesSmashed += updateValue;
            break;
        case RocketsUsed:
            m_RocketsUsed += updateValue;
            break;
        case MissionsCompleted:
            m_MissionsCompleted += updateValue;
            break;
        case PetsTamed:
            m_PetsTamed += updateValue;
            break;
        case ImaginationPowerUpsCollected:
            m_ImaginationPowerUpsCollected += updateValue;
            break;
        case LifePowerUpsCollected:
            m_LifePowerUpsCollected += updateValue;
            break;
        case ArmorPowerUpsCollected:
            m_ArmorPowerUpsCollected += updateValue;
            break;
        case MetersTraveled:
            m_MetersTraveled += updateValue;
            break;
        case TimesSmashed:
            m_TimesSmashed += updateValue;
            break;
        case TotalDamageTaken:
            m_TotalDamageTaken += updateValue;
            break;
        case TotalDamageHealed:
            m_TotalDamageHealed += updateValue;
            break;
        case TotalArmorRepaired:
            m_TotalArmorRepaired += updateValue;
            break;
        case TotalImaginationRestored:
            m_TotalImaginationRestored += updateValue;
            break;
        case TotalImaginationUsed:
            m_TotalImaginationUsed += updateValue;
            break;
        case DistanceDriven:
            m_DistanceDriven += updateValue;
            break;
        case TimeAirborneInCar:
            m_TimeAirborneInCar += updateValue;
            break;
        case RacingImaginationPowerUpsCollected:
            m_RacingImaginationPowerUpsCollected += updateValue;
            break;
        case RacingImaginationCratesSmashed:
            m_RacingImaginationCratesSmashed += updateValue;
            break;
        case RacingCarBoostsActivated:
            m_RacingCarBoostsActivated += updateValue;
            break;
        case RacingTimesWrecked:
            m_RacingTimesWrecked += updateValue;
            break;
        case RacingSmashablesSmashed:
            m_RacingSmashablesSmashed += updateValue;
            break;
        case RacesFinished:
            m_RacesFinished += updateValue;
            break;
        case FirstPlaceRaceFinishes:
            m_FirstPlaceRaceFinishes += updateValue;
            break;
        default:
            break;
    }
}

void CharacterComponent::InitializeStatisticsFromString(const std::string &statisticsString) {
    auto split = GeneralUtils::SplitString(statisticsString, ';');

    m_CurrencyCollected = GetStatisticFromSplit(split, 0);
    m_BricksCollected = GetStatisticFromSplit(split, 1);
    m_SmashablesSmashed = GetStatisticFromSplit(split, 2);
    m_QuickBuildsCompleted = GetStatisticFromSplit(split, 3);
    m_EnemiesSmashed = GetStatisticFromSplit(split, 4);
    m_RocketsUsed = GetStatisticFromSplit(split, 5);
    m_MissionsCompleted = GetStatisticFromSplit(split, 6);
    m_PetsTamed = GetStatisticFromSplit(split, 7);
    m_ImaginationPowerUpsCollected = GetStatisticFromSplit(split, 8);
    m_LifePowerUpsCollected = GetStatisticFromSplit(split, 9);
    m_ArmorPowerUpsCollected = GetStatisticFromSplit(split, 10);
    m_MetersTraveled = GetStatisticFromSplit(split, 11);
    m_TimesSmashed = GetStatisticFromSplit(split, 12);
    m_TotalDamageTaken = GetStatisticFromSplit(split, 13);
    m_TotalDamageHealed = GetStatisticFromSplit(split, 14);
    m_TotalArmorRepaired = GetStatisticFromSplit(split, 15);
    m_TotalImaginationRestored = GetStatisticFromSplit(split, 16);
    m_TotalImaginationUsed = GetStatisticFromSplit(split, 17);
    m_DistanceDriven = GetStatisticFromSplit(split, 18);
    m_TimeAirborneInCar = GetStatisticFromSplit(split, 19); // WONTFIX
    m_RacingImaginationPowerUpsCollected = GetStatisticFromSplit(split, 20);
    m_RacingImaginationCratesSmashed = GetStatisticFromSplit(split, 21);
    m_RacingCarBoostsActivated = GetStatisticFromSplit(split, 22);
    m_RacingTimesWrecked = GetStatisticFromSplit(split, 23);
    m_RacingSmashablesSmashed = GetStatisticFromSplit(split, 24);
    m_RacesFinished = GetStatisticFromSplit(split, 25);
    m_FirstPlaceRaceFinishes = GetStatisticFromSplit(split, 26);
}

void CharacterComponent::InitializeEmptyStatistics() {
    m_CurrencyCollected = 0;
    m_BricksCollected = 0;
    m_SmashablesSmashed = 0;
    m_QuickBuildsCompleted = 0;
    m_EnemiesSmashed = 0;
    m_RocketsUsed = 0;
    m_MissionsCompleted = 0;
    m_PetsTamed = 0;
    m_ImaginationPowerUpsCollected = 0;
    m_LifePowerUpsCollected = 0;
    m_ArmorPowerUpsCollected = 0;
    m_MetersTraveled = 0;
    m_TimesSmashed = 0;
    m_TotalDamageTaken = 0;
    m_TotalDamageHealed = 0;
    m_TotalArmorRepaired = 0;
    m_TotalImaginationRestored = 0;
    m_TotalImaginationUsed = 0;
    m_DistanceDriven = 0;
    m_TimeAirborneInCar = 0;
    m_RacingImaginationPowerUpsCollected = 0;
    m_RacingImaginationCratesSmashed = 0;
    m_RacingCarBoostsActivated = 0;
    m_RacingTimesWrecked = 0;
    m_RacingSmashablesSmashed = 0;
    m_RacesFinished = 0;
    m_FirstPlaceRaceFinishes = 0;
}

std::string CharacterComponent::StatisticsToString() const {
    std::stringstream result;
    result << std::to_string(m_CurrencyCollected) << ';'
        << std::to_string(m_BricksCollected) << ';'
        << std::to_string(m_SmashablesSmashed) << ';'
        << std::to_string(m_QuickBuildsCompleted) << ';'
        << std::to_string(m_EnemiesSmashed) << ';'
        << std::to_string(m_RocketsUsed) << ';'
        << std::to_string(m_MissionsCompleted) << ';'
        << std::to_string(m_PetsTamed) << ';'
        << std::to_string(m_ImaginationPowerUpsCollected) << ';'
        << std::to_string(m_LifePowerUpsCollected) << ';'
        << std::to_string(m_ArmorPowerUpsCollected) << ';'
        << std::to_string(m_MetersTraveled) << ';'
        << std::to_string(m_TimesSmashed) << ';'
        << std::to_string(m_TotalDamageTaken) << ';'
        << std::to_string(m_TotalDamageHealed) << ';'
        << std::to_string(m_TotalArmorRepaired) << ';'
        << std::to_string(m_TotalImaginationRestored) << ';'
        << std::to_string(m_TotalImaginationUsed) << ';'
        << std::to_string(m_DistanceDriven) << ';'
        << std::to_string(m_TimeAirborneInCar) << ';'
        << std::to_string(m_RacingImaginationPowerUpsCollected) << ';'
        << std::to_string(m_RacingImaginationCratesSmashed) << ';'
        << std::to_string(m_RacingCarBoostsActivated) << ';'
        << std::to_string(m_RacingTimesWrecked) << ';'
        << std::to_string(m_RacingSmashablesSmashed) << ';'
        << std::to_string(m_RacesFinished) << ';'
        << std::to_string(m_FirstPlaceRaceFinishes) << ';';

    return result.str();
}

uint64_t CharacterComponent::GetStatisticFromSplit(std::vector<std::string> split, uint32_t index) {
    return split.size() > index ? std::stoul(split.at(index)) : 0;
}

ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) {
    auto stats = m_ZoneStatistics.find(mapID);
    if (stats == m_ZoneStatistics.end())
        m_ZoneStatistics.insert({ mapID, {0, 0, 0, 0, 0 } });
    return m_ZoneStatistics.at(mapID);
}