
#include "pilotfile/pilotfile.h"
#include "ship/ship.h"
#include "stats/medals.h"


pilotfile Pilot;


pilotfile::pilotfile()
{
	m_have_flags = false;
	m_have_info = false;

	m_data_invalid = false;

	m_size_offset = 0;

	cfp = NULL;
	p = NULL;

	plr_ver = PLR_VERSION_INVALID;
	csg_ver = PLR_VERSION_INVALID;
}

pilotfile::~pilotfile()
{
	if (cfp) {
		cfclose(cfp);
	}
}

void pilotfile::startSection(Section section_id)
{
	Assert( cfp );

	const int zero = 0;

	cfwrite_ushort( (ushort)section_id, cfp );

	// to be updated when endSection() is called
	cfwrite_int(zero, cfp);

	// starting offset, for size of section
	m_size_offset = cftell(cfp);
}

void pilotfile::endSection()
{
	Assert( cfp );
	Assert( m_size_offset > 0 );

	size_t cur = cftell(cfp);

	Assert( cur >= m_size_offset );

	size_t section_size = cur - m_size_offset;

	if (section_size) {
		// go back to section size in file and write proper value
		cfseek(cfp, (int)(cur - section_size - sizeof(int)), CF_SEEK_SET);
		cfwrite_int((int)section_size, cfp);

		// go back to previous location for next section
		cfseek(cfp, (int)cur, CF_SEEK_SET);
	}
}

void pilotfile::update_stats(scoring_struct *stats, bool training)
{
	int idx, i, j, list_size;
	index_list_t ilist;
	scoring_special_t *p_stats = NULL;

	if (Game_mode & GM_MULTIPLAYER) {
		p_stats = &multi_stats;
	} else {
		p_stats = &all_time_stats;
	}

	// medals
	if (stats->m_medal_earned >= 0) {
		list_size = (int)p_stats->medals_earned.size();

		j = -1;

		for (idx = 0; idx < list_size; idx++) {
			if ( p_stats->medals_earned[idx].name == Medals[stats->m_medal_earned].name ) {
				j = idx;
				break;
			}
		}

		if (j >= 0) {
			p_stats->medals_earned[j].val++;
		} else {
			ilist.name = Medals[stats->m_medal_earned].name;
			ilist.index = stats->m_medal_earned;
			ilist.val = 1;

			p_stats->medals_earned.push_back(ilist);
		}
	}

	// early out if these stats are from a training mission
	if (training) {
		return;
	}

	if (stats->m_promotion_earned >= 0) {
		p_stats->rank = stats->m_promotion_earned;
	}

	p_stats->score += stats->m_score;

	p_stats->assists += stats->m_assists;
	p_stats->kill_count += stats->m_kill_count;
	p_stats->kill_count_ok += stats->m_kill_count_ok;
	p_stats->bonehead_kills += stats->m_bonehead_kills;

	p_stats->p_shots_fired += stats->mp_shots_fired;
	p_stats->p_shots_hit += stats->mp_shots_hit;
	p_stats->p_bonehead_hits += stats->mp_bonehead_hits;

	p_stats->s_shots_fired += stats->ms_shots_fired;
	p_stats->s_shots_hit += stats->ms_shots_hit;
	p_stats->s_bonehead_hits += stats->ms_bonehead_hits;

	p_stats->flight_time += (unsigned int)f2i(Missiontime);
	p_stats->last_backup = p_stats->last_flown;
	p_stats->last_flown = stats->last_flown;
	p_stats->missions_flown++;

	// badges
	if (stats->m_badge_earned.size()) {
		list_size = (int)p_stats->medals_earned.size();
		for (size_t medal = 0; medal < stats->m_badge_earned.size(); medal++) {
			j = -1;

			for (idx = 0; idx < list_size; idx++) {
				if ( p_stats->medals_earned[idx].name == Medals[stats->m_badge_earned[medal]].name ) {
					j = idx;
					break;
				}
			}

			if (j >= 0) {
				p_stats->medals_earned[j].val = 1;
			} else {
				ilist.name = Medals[stats->m_badge_earned[medal]].name;
				ilist.index = stats->m_badge_earned[medal];
				ilist.val = 1;

				p_stats->medals_earned.push_back(ilist);
				list_size++;
			}
		}
	}

	// ship kills
	idx = 0;
	for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); idx++, ++it) {
		if (stats->m_okKills[idx] > 0) {
			list_size = (int)p_stats->ship_kills.size();

			j = -1;

			for (i = 0; i < list_size; i++) {
				if ( p_stats->ship_kills[i].name == it->name ) {
					j = i;
					break;
				}
			}

			if (j >= 0) {
				p_stats->ship_kills[j].val += stats->m_okKills[idx];
			} else {
				ilist.name = it->name;
				ilist.index = idx;
				ilist.val = stats->m_okKills[idx];

				p_stats->ship_kills.push_back(ilist);
			}
		}
	}
}

void pilotfile::update_stats_backout(scoring_struct *stats, bool training)
{
	int i, j;
	uint idx;
	size_t list_size;
	index_list_t ilist;
	scoring_special_t *p_stats = NULL;

	if (Game_mode & GM_MULTIPLAYER) {
		p_stats = &multi_stats;
	} else {
		p_stats = &all_time_stats;
	}

	// medals
	if (stats->m_medal_earned >= 0) {
		list_size = p_stats->medals_earned.size();

		j = -1;

		for (idx = 0; idx < list_size; idx++) {
			if ( p_stats->medals_earned[idx].name == Medals[stats->m_medal_earned].name ) {
				j = idx;
				break;
			}
		}

		if (j >= 0) {
			p_stats->medals_earned[j].val = std::max(0,p_stats->medals_earned[j].val--);
		} else {
			Assertion(true, "Medal '%s' not found, should have been added by pilotfile::update_stats.", Medals[stats->m_medal_earned].name);
		}
	}

	// only medals can be awarded in training missions
	if (training) {
		return;
	}

	p_stats->score -= stats->m_score;

	p_stats->assists -= stats->m_assists;
	p_stats->kill_count -= stats->m_kill_count;
	p_stats->kill_count_ok -= stats->m_kill_count_ok;
	p_stats->bonehead_kills -= stats->m_bonehead_kills;

	p_stats->p_shots_fired -= stats->mp_shots_fired;
	p_stats->p_shots_hit -= stats->mp_shots_hit;
	p_stats->p_bonehead_hits -= stats->mp_bonehead_hits;

	p_stats->s_shots_fired -= stats->ms_shots_fired;
	p_stats->s_shots_hit -= stats->ms_shots_hit;
	p_stats->s_bonehead_hits -= stats->ms_bonehead_hits;

	p_stats->flight_time -= (unsigned int)f2i(Missiontime);
	p_stats->last_flown = p_stats->last_backup;
	p_stats->missions_flown--;

	if (stats->m_promotion_earned >= 0) {
		// deal with a multi-rank promotion mission
		for (i = 0; i < ((int)Ranks.size()); ++i) {
			if (p_stats->score >= Ranks[i].points) {
				p_stats->rank = i;
			}
		}
		Assertion (p_stats->rank >= 0, "Rank became negative.");
	}

	// badges
	if (stats->m_badge_earned.size()) {
		list_size = p_stats->medals_earned.size();
		for (size_t medal = 0; medal < stats->m_badge_earned.size(); medal++) {
			j = -1;

			for (idx = 0; idx < list_size; idx++) {
				if ( p_stats->medals_earned[idx].name == Medals[stats->m_badge_earned[medal]].name ) {
					j = idx;
					break;
				}
			}

			if (j >= 0) {
				p_stats->medals_earned[j].val = 0;
			} else {
				Assertion (false, "Badge '%s' not found, should have been added by pilotfile::update_stats.", Medals[stats->m_badge_earned[medal]].name);
			}
		}
	}

	// ship kills
	i = 0;
	for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); i++, ++it) {
		if (stats->m_okKills[i] > 0) {
			list_size = p_stats->ship_kills.size();

			j = -1;

			for (idx = 0; idx < list_size; idx++) {
				if ( p_stats->ship_kills[idx].name == it->name ) {
					j = idx;
					break;
				}
			}

			if (j >= 0) {
				p_stats->ship_kills[j].val -= stats->m_okKills[i];
			} else {
				UNREACHABLE("Ship kills of '%s' not found, should have been added by pilotfile::update_stats.", Ship_info[i].name);
			}
		}
	}
}

/**
 * @brief Clear and set multi stats from given scoring struct
 * @note This is really just for use by PXO to set multi stats from tracker
 * 
 * @param stats 
 */
void pilotfile::set_multi_stats(const scoring_struct *stats)
{
	size_t idx;
	index_list_t ilist;

	multi_stats.score = stats->score;
	multi_stats.rank = stats->rank;
	multi_stats.assists = stats->assists;
	multi_stats.kill_count = stats->kill_count;
	multi_stats.kill_count_ok = stats->kill_count_ok;
	multi_stats.bonehead_kills = stats->bonehead_kills;

	multi_stats.p_shots_fired = stats->p_shots_fired;
	multi_stats.p_shots_hit = stats->p_shots_hit;
	multi_stats.p_bonehead_hits = stats->p_bonehead_hits;

	multi_stats.s_shots_fired = stats->s_shots_fired;
	multi_stats.s_shots_hit = stats->s_shots_hit;
	multi_stats.s_bonehead_hits = stats->s_bonehead_hits;

	multi_stats.missions_flown = stats->missions_flown;
	multi_stats.flight_time = stats->flight_time;
	multi_stats.last_flown = stats->last_flown;
	multi_stats.last_backup = stats->last_backup;

	// ship kills
	multi_stats.ship_kills.clear();
	multi_stats.ship_kills.shrink_to_fit();

	auto kills_size = std::min(SDL_arraysize(stats->kills), Ship_info.size());

	for (idx = 0; idx < kills_size; ++idx) {
		if (stats->kills[idx] <= 0) {
			continue;
		}

		ilist.name = Ship_info[idx].name;
		ilist.index = static_cast<int>(idx);
		ilist.val = stats->kills[idx];

		multi_stats.ship_kills.push_back(ilist);
	}

	// medals
	multi_stats.medals_earned.clear();
	multi_stats.medals_earned.shrink_to_fit();

	auto medals_size = std::min(stats->medal_counts.size(), Medals.size());

	for (idx = 0; idx < medals_size; ++idx) {
		if (stats->medal_counts[idx] <= 0) {
			continue;
		}

		ilist.name = Medals[idx].name;
		ilist.index = static_cast<int>(idx);
		ilist.val = stats->medal_counts[idx];

		multi_stats.medals_earned.push_back(ilist);
	}
}

/**
 * Reset stats stored in the .plr file; all_time_stats & multi_stats
 */
void pilotfile::reset_stats()
{
	scoring_special_t *ss_stats[] = {&all_time_stats, &multi_stats};

	for (int i = 0; i < 2; ++i) {
		ss_stats[i]->score = 0;
		ss_stats[i]->assists = 0;
		ss_stats[i]->score = 0;
		ss_stats[i]->rank = 0;
		ss_stats[i]->assists = 0;
		ss_stats[i]->kill_count = 0;
		ss_stats[i]->kill_count_ok = 0;
		ss_stats[i]->bonehead_kills = 0;

		ss_stats[i]->p_shots_fired = 0;
		ss_stats[i]->p_shots_hit = 0;
		ss_stats[i]->p_bonehead_hits = 0;

		ss_stats[i]->s_shots_fired = 0;
		ss_stats[i]->s_shots_hit = 0;
		ss_stats[i]->s_bonehead_hits = 0;

		ss_stats[i]->missions_flown = 0;
		ss_stats[i]->flight_time = 0;
		ss_stats[i]->last_flown = 0;
		ss_stats[i]->last_backup = 0;

		ss_stats[i]->ship_kills.clear();
		ss_stats[i]->medals_earned.clear();
	}
}

/**
 * @brief Export stats to given scoring struct, sanitized for current mod data
 * 
 * @param[out] stats Scoring struct for exported data
 * 
 * @returns true if stats were exported successfully
 */
bool pilotfile::export_stats(scoring_struct *stats)
{
	scoring_special_t *p_stats = nullptr;

	if ( !stats ) {
		return false;
	}

	stats->init();

	if (Game_mode & GM_MULTIPLAYER) {
		p_stats = &multi_stats;
	} else {
		p_stats = &all_time_stats;
	}

	stats->score = p_stats->score;
	stats->rank = p_stats->rank;
	stats->assists = p_stats->assists;
	stats->kill_count = p_stats->kill_count;
	stats->kill_count_ok = p_stats->kill_count_ok;
	stats->bonehead_kills = p_stats->bonehead_kills;

	stats->p_shots_fired = p_stats->p_shots_fired;
	stats->p_shots_hit = p_stats->p_shots_hit;
	stats->p_bonehead_hits = p_stats->p_bonehead_hits;

	stats->s_shots_fired = p_stats->s_shots_fired;
	stats->s_shots_hit = p_stats->s_shots_hit;
	stats->s_bonehead_hits = p_stats->s_bonehead_hits;

	stats->missions_flown = p_stats->missions_flown;
	stats->flight_time = p_stats->flight_time;
	stats->last_flown = p_stats->last_flown;
	stats->last_backup = p_stats->last_backup;

	// only export ships that this mod knows about (should already be index)
	for (auto &item : p_stats->ship_kills) {
		if ( (item.index >= 0) && (item.index < MAX_SHIP_CLASSES) ) {
			stats->kills[item.index] = item.val;
		}
	}

	// same for medals
	for (auto &item : p_stats->medals_earned) {
		if ( SCP_vector_inbounds(stats->medal_counts, item.index) ) {
			stats->medal_counts[item.index] = item.val;
		}
	}

	return true;
}