/*
Copyright (C) 2015-2019 Parallel Realities

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; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; 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 "challenges.h"

static void updateTimeChallenge(Challenge *c);
static void updateSurvivalChallenge(Challenge *c);
static void updateAccuracyChallenge(Challenge *c);
static void updateArmourChallenge(Challenge *c);
static void updateLossesChallenge(Challenge *c);
static void updatePlayerKillsChallenge(Challenge *c);
static void updateDisabledChallenge(Challenge *c);
static void updateItemsChallenge(Challenge *c);
static void updateSurrenderChallenge(Challenge *c);
static void updateWaypointChallenge(Challenge *c);
static void updateRescueChallenge(Challenge *c);
static void completeChallenge(void);
static void failChallenge(void);
static int updateChallenges(void);
static char *getFormattedChallengeDescription(const char *format, ...);
char *getChallengeDescription(Challenge *c);
static int challengeFinished(void);
static int alreadyPassed(void);
static void printStats(void);

static char descriptionBuffer[MAX_DESCRIPTION_LENGTH];
static const char *challengeDescription[CHALLENGE_MAX];

void initChallenges(void)
{
	Mission *mission, *tail;
	char **filenames;
	char path[MAX_FILENAME_LENGTH];
	int count, i;

	challengeDescription[CHALLENGE_ARMOUR] = _("Retain at least %d%% armour");
	challengeDescription[CHALLENGE_TIME] = _("Complete challenge in %d seconds or less");
	challengeDescription[CHALLENGE_SURVIVE] = _("Survive for %d seconds");
	challengeDescription[CHALLENGE_SHOT_ACCURACY] = _("Attain a %d%% shot hit accuracy");
	challengeDescription[CHALLENGE_ROCKET_ACCURACY] = _("Attain a %d%% rocket hit accuracy");
	challengeDescription[CHALLENGE_MISSILE_ACCURACY] = _("Attain a %d%% missile hit accuracy");
	challengeDescription[CHALLENGE_NO_LOSSES] = _("Do not lose any team mates");
	challengeDescription[CHALLENGE_1_LOSS] = _("Do not lose more than 1 team mate");
	challengeDescription[CHALLENGE_LOSSES] = _("Do not lose more than %d team mates");
	challengeDescription[CHALLENGE_PLAYER_KILLS] = _("Take down %d enemy targets");
	challengeDescription[CHALLENGE_DISABLE] = _("Disable %d or more enemy fighters");
	challengeDescription[CHALLENGE_ITEMS] = _("Collect %d packages");
	challengeDescription[CHALLENGE_PLAYER_ITEMS] = _("Collect %d packages");
	challengeDescription[CHALLENGE_RESCUE] = _("Rescue %d civilians");
	challengeDescription[CHALLENGE_SURRENDER] = _("Cause %d enemies to surrender");
	challengeDescription[CHALLENGE_WAYPOINTS] = _("Reach %d waypoints");

	tail = &game.challengeMissionHead;

	filenames = getFileList("data/challenges", &count);

	for (i = 0 ; i < count ; i++)
	{
		sprintf(path, "data/challenges/%s", filenames[i]);

		mission = loadMissionMeta(path);

		if (mission)
		{
			tail->next = mission;
			tail = mission;
		}

		free(filenames[i]);
	}

	free(filenames);
}

void loadChallenge(Mission *mission, cJSON *node)
{
	int i;
	Challenge *challenge;

	mission->challengeData.isChallenge = 1;

	/* limits */
	mission->challengeData.timeLimit = getJSONValue(node, "timeLimit", 0) * FPS;
	mission->challengeData.killLimit = getJSONValue(node, "killLimit", 0);
	mission->challengeData.escapeLimit = getJSONValue(node, "escapeLimit", 0);
	mission->challengeData.waypointLimit = getJSONValue(node, "waypointLimit", 0);
	mission->challengeData.itemLimit = getJSONValue(node, "itemLimit", 0);
	mission->challengeData.playerItemLimit = getJSONValue(node, "playerItemLimit", 0);
	mission->challengeData.rescueLimit = getJSONValue(node, "rescueLimit", 0);
	mission->challengeData.disableLimit = getJSONValue(node, "disableLimit", 0);
	mission->challengeData.surrenderLimit = getJSONValue(node, "surrenderLimit", 0);

	/* restrictions */
	mission->challengeData.noMissiles = getJSONValue(node, "noMissiles", 0);
	mission->challengeData.noECM = getJSONValue(node, "noECM", 0);
	mission->challengeData.noBoost = getJSONValue(node, "noBoost", 0);
	mission->challengeData.noGuns = getJSONValue(node, "noGuns", 0);

	if (getJSONValue(node, "noWeapons", 0))
	{
		mission->challengeData.noMissiles = mission->challengeData.noGuns = 1;
	}

	/* misc */
	mission->challengeData.allowPlayerDeath = getJSONValue(node, "allowPlayerDeath", 0);
	mission->challengeData.clearWaypointEnemies = getJSONValue(node, "clearWaypointEnemies", 0);
	mission->challengeData.eliminateThreats = getJSONValue(node, "eliminateThreats", 0);
	mission->challengeData.isDeathMatch = getJSONValue(node, "isDeathMatch", 0);

	node = cJSON_GetObjectItem(node, "challenges");

	if (node)
	{
		node = node->child;

		i = 0;

		while (node && i < MAX_CHALLENGES)
		{
			challenge = malloc(sizeof(Challenge));
			memset(challenge, 0, sizeof(Challenge));

			challenge->type = lookup(cJSON_GetObjectItem(node, "type")->valuestring);
			challenge->value = cJSON_GetObjectItem(node, "value")->valueint;

			mission->challengeData.challenges[i] = challenge;

			node = node->next;

			i++;
		}
	}
}

void doChallenges(void)
{
	int passed;

	if (game.currentMission->challengeData.isChallenge && battle.status == MS_IN_PROGRESS)
	{
		if (challengeFinished())
		{
			passed = 0;

			if (player->health > 0 || (player->health <= 0 && game.currentMission->challengeData.allowPlayerDeath))
			{
				passed = updateChallenges();
			}

			if (passed)
			{
				completeChallenge();
			}
			else
			{
				failChallenge();
			}
		}
	}
}

static int challengeFinished(void)
{
	if (game.currentMission->challengeData.timeLimit > 0 && battle.stats[STAT_TIME] >= game.currentMission->challengeData.timeLimit)
	{
		return 1;
	}

	/* disabled enemies count as killed during challenges - not player exclusive, but no need to worry about AI contributions here */
	if (game.currentMission->challengeData.killLimit > 0 && (battle.stats[STAT_ENEMIES_KILLED_PLAYER] + battle.stats[STAT_CAPITAL_SHIPS_DESTROYED] + battle.stats[STAT_ENEMIES_DISABLED]) >= game.currentMission->challengeData.killLimit)
	{
		return 1;
	}

	if (game.currentMission->challengeData.escapeLimit > 0 && (battle.stats[STAT_ENEMIES_KILLED_PLAYER] + battle.stats[STAT_ENEMIES_ESCAPED]) >= game.currentMission->challengeData.escapeLimit)
	{
		return 1;
	}

	if (game.currentMission->challengeData.waypointLimit > 0 && battle.stats[STAT_WAYPOINTS_VISITED] >= game.currentMission->challengeData.waypointLimit)
	{
		return 1;
	}

	if (game.currentMission->challengeData.itemLimit > 0 && battle.stats[STAT_ITEMS_COLLECTED] + battle.stats[STAT_ITEMS_COLLECTED_PLAYER] >= game.currentMission->challengeData.itemLimit)
	{
		return 1;
	}

	if (game.currentMission->challengeData.playerItemLimit > 0 && battle.stats[STAT_ITEMS_COLLECTED_PLAYER] >= game.currentMission->challengeData.playerItemLimit)
	{
		return 1;
	}

	if (game.currentMission->challengeData.rescueLimit > 0 && (battle.stats[STAT_CIVILIANS_RESCUED] + battle.stats[STAT_CIVILIANS_KILLED]) >= game.currentMission->challengeData.rescueLimit)
	{
		return 1;
	}

	if (game.currentMission->challengeData.surrenderLimit > 0 && battle.stats[STAT_ENEMIES_SURRENDERED] >= game.currentMission->challengeData.surrenderLimit)
	{
		return 1;
	}

	if (game.currentMission->challengeData.waypointLimit > 0 && (battle.stats[STAT_WAYPOINTS_VISITED]) >= game.currentMission->challengeData.waypointLimit)
	{
		return 1;
	}

	if (game.currentMission->challengeData.eliminateThreats && !battle.hasThreats)
	{
		return 1;
	}

	return (player->health <= 0 || player->alive == ALIVE_ESCAPED || battle.scriptedEnd);
}

static int updateChallenges(void)
{
	int i, numPassed;
	Challenge *c;

	updateAccuracyStats(battle.stats);

	numPassed = 0;

	for (i = 0 ; i < MAX_CHALLENGES ; i++)
	{
		c = game.currentMission->challengeData.challenges[i];

		if (c)
		{
			if (!c->passed)
			{
				switch (c->type)
				{
					case CHALLENGE_TIME:
						updateTimeChallenge(c);
						break;

					case CHALLENGE_SURVIVE:
						updateSurvivalChallenge(c);
						break;

					case CHALLENGE_SHOT_ACCURACY:
					case CHALLENGE_ROCKET_ACCURACY:
					case CHALLENGE_MISSILE_ACCURACY:
						updateAccuracyChallenge(c);
						break;

					case CHALLENGE_ARMOUR:
						updateArmourChallenge(c);
						break;

					case CHALLENGE_NO_LOSSES:
					case CHALLENGE_1_LOSS:
					case CHALLENGE_LOSSES:
						updateLossesChallenge(c);
						break;

					case CHALLENGE_PLAYER_KILLS:
						updatePlayerKillsChallenge(c);
						break;

					case CHALLENGE_DISABLE:
						updateDisabledChallenge(c);
						break;

					case CHALLENGE_ITEMS:
					case CHALLENGE_PLAYER_ITEMS:
						updateItemsChallenge(c);
						break;

					case CHALLENGE_SURRENDER:
						updateSurrenderChallenge(c);
						break;

					case CHALLENGE_WAYPOINTS:
						updateWaypointChallenge(c);
						break;

					case CHALLENGE_RESCUE:
						updateRescueChallenge(c);
						break;
				}
			}

			if (c->passed)
			{
				numPassed++;
			}
		}
	}

	if (dev.debug)
	{
		printStats();
	}

	return numPassed;
}

static void printStats(void)
{
	int i;

	for (i = 0 ; i < STAT_MAX ; i++)
	{
		if (battle.stats[i])
		{
			if (i != STAT_TIME)
			{
				printf("DEBUG: %s=%d\n", getLookupName("STAT_", i), battle.stats[i]);
			}
			else
			{
				printf("DEBUG: %s=%s\n", getLookupName("STAT_", i), timeToString(battle.stats[i], 0));
			}
		}
	}
}

static void updateTimeChallenge(Challenge *c)
{
	if (battle.stats[STAT_TIME] / FPS < c->value)
	{
		c->passed = 1;
	}
}

static void updateSurvivalChallenge(Challenge *c)
{
	if (battle.stats[STAT_TIME] / FPS >= c->value)
	{
		c->passed = 1;
	}
}

static void updateAccuracyChallenge(Challenge *c)
{
	float percent;

	switch (c->type)
	{
		case CHALLENGE_SHOT_ACCURACY:
			percent = battle.stats[STAT_SHOT_ACCURACY];
			break;

		case CHALLENGE_ROCKET_ACCURACY:
			percent = battle.stats[STAT_ROCKET_ACCURACY];
			break;

		case CHALLENGE_MISSILE_ACCURACY:
			percent = battle.stats[STAT_MISSILE_ACCURACY];
			break;

		default:
			percent = 0;
			break;
	}

	if (percent >= c->value)
	{
		c->passed = 1;
	}
}

static void updateArmourChallenge(Challenge *c)
{
	float percent;

	percent = player->health;
	percent /= player->maxHealth;
	percent *= 100;

	if (percent >= c->value)
	{
		c->passed = 1;
	}
}

static void updateLossesChallenge(Challenge *c)
{
	if (!c->passed)
	{
		c->passed = battle.stats[STAT_ALLIES_KILLED] <= c->value;
	}
}

static void updatePlayerKillsChallenge(Challenge *c)
{
	if (!c->passed)
	{
		c->passed = battle.stats[STAT_ENEMIES_KILLED_PLAYER] >= c->value;
	}
}

static void updateDisabledChallenge(Challenge *c)
{
	if (!c->passed)
	{
		c->passed = battle.stats[STAT_ENEMIES_DISABLED] >= c->value;
	}
}

static void updateItemsChallenge(Challenge *c)
{
	if (!c->passed)
	{
		if (c->type == CHALLENGE_ITEMS)
		{
			c->passed = battle.stats[STAT_ITEMS_COLLECTED] + battle.stats[STAT_ITEMS_COLLECTED_PLAYER] >= c->value;
		}
		else
		{
			c->passed = battle.stats[STAT_ITEMS_COLLECTED_PLAYER] >= c->value;
		}
	}
}

static void updateSurrenderChallenge(Challenge *c)
{
	if (!c->passed)
	{
		c->passed = battle.stats[STAT_ENEMIES_SURRENDERED] >= c->value;
	}
}

static void updateWaypointChallenge(Challenge *c)
{
	if (!c->passed)
	{
		c->passed = battle.stats[STAT_WAYPOINTS_VISITED] >= c->value;
	}
}

static void updateRescueChallenge(Challenge *c)
{
	if (!c->passed)
	{
		c->passed = battle.stats[STAT_CIVILIANS_RESCUED] >= c->value;
	}
}

char *getChallengeDescription(Challenge *c)
{
	if (c->type == CHALLENGE_TIME)
	{
		if (c->value <= 90)
		{
			return getFormattedChallengeDescription(challengeDescription[CHALLENGE_TIME], c->value);
		}
		else
		{
			return getFormattedChallengeDescription(_("Complete challenge in %s or less"), timeToString(c->value * FPS, 0));
		}
	}

	return getFormattedChallengeDescription(challengeDescription[c->type], c->value);
}

Challenge *getChallenge(Mission *mission, int type, int value)
{
	int i;
	Challenge *c;

	for (i = 0 ; i < MAX_CHALLENGES ; i++)
	{
		c = mission->challengeData.challenges[i];

		if (c->type == type && c->value == value)
		{
			return c;
		}
	}

	return NULL;
}

static char *getFormattedChallengeDescription(const char *format, ...)
{
	va_list args;

	memset(&descriptionBuffer, '\0', sizeof(descriptionBuffer));

	va_start(args, format);
	vsprintf(descriptionBuffer, format, args);
	va_end(args);

	return descriptionBuffer;
}

void updateChallengeMissions(void)
{
	int i;
	Mission *m;
	Challenge *c;

	for (m = game.challengeMissionHead.next ; m != NULL ; m = m->next)
	{
		m->totalChallenges = m->completedChallenges = 0;

		for (i = 0 ; i < MAX_CHALLENGES ; i++)
		{
			c = m->challengeData.challenges[i];

			if (c)
			{
				m->totalChallenges++;

				if (c->passed)
				{
					m->completedChallenges++;
				}
			}
		}
	}
}

static void completeChallenge(void)
{
	if (battle.status == MS_IN_PROGRESS)
	{
		battle.status = MS_COMPLETE;
		battle.missionFinishedTimer = FPS;
		selectWidget("continue", "battleWon");

		game.stats[STAT_CHALLENGES_COMPLETED]++;

		player->flags |= EF_IMMORTAL;

		retreatAllies();

		retreatEnemies();

		awardStatsTrophies();

		awardCraftTrophy();
	}
}

static void failChallenge(void)
{
	if (battle.status == MS_IN_PROGRESS)
	{
		battle.status = MS_FAILED;
		battle.missionFinishedTimer = FPS;
		selectWidget("retry", "battleLost");

		player->flags |= EF_IMMORTAL;

		if (alreadyPassed())
		{
			battle.status = MS_TIME_UP;
		}

		retreatAllies();

		retreatEnemies();

		awardStatsTrophies();
	}
}

static int alreadyPassed(void)
{
	int i;
	Challenge *c;

	for (i = 0 ; i < MAX_CHALLENGES ; i++)
	{
		c = game.currentMission->challengeData.challenges[i];

		if (c && c->passed)
		{
			return 1;
		}
	}

	return 0;
}
