/*
Copyright © 2011-2012 Clint Bellanger
Copyright © 2012 Igor Paliychuk
Copyright © 2012 Stefan Beller
Copyright © 2013 Henrik Andersson

This file is part of FLARE.

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

FLARE 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
FLARE.  If not, see http://www.gnu.org/licenses/
*/

/**
 * Save and Load functions for the GameStatePlay.
 *
 * I put these in a separate cpp file just to keep GameStatePlay.cpp devoted to its core.
 *
 * class GameStatePlay
 */

#include "CommonIncludes.h"
#include "FileParser.h"
#include "GameStatePlay.h"
#include "MapRenderer.h"
#include "MenuActionBar.h"
#include "MenuCharacter.h"
#include "Menu.h"
#include "MenuInventory.h"
#include "MenuManager.h"
#include "MenuStash.h"
#include "MenuTalker.h"
#include "Settings.h"
#include "UtilsFileSystem.h"
#include "UtilsParsing.h"
#include "SharedGameResources.h"

using namespace std;

/**
 * Before exiting the game, save to file
 */
void GameStatePlay::saveGame() {

	// game slots are currently 1-4
	if (game_slot == 0) return;

	// remove items with zero quantity from inventory
	menu->inv->inventory[EQUIPMENT].clean();
	menu->inv->inventory[CARRIED].clean();

	ofstream outfile;

	stringstream ss;
	ss.str("");
	ss << PATH_USER;
	if (GAME_PREFIX.length() > 0)
		ss << GAME_PREFIX << "_";
	ss << "save" << game_slot << ".txt";

	outfile.open(ss.str().c_str(), ios::out);

	if (outfile.is_open()) {

		// hero name
		outfile << "name=" << pc->stats.name << "\n";

		// permadeath
		outfile << "permadeath=" << pc->stats.permadeath << "\n";

		// hero visual option
		outfile << "option=" << pc->stats.gfx_base << "," << pc->stats.gfx_head << "," << pc->stats.gfx_portrait << "\n";

		// hero class
		outfile << "class=" << pc->stats.character_class << "\n";

		// current experience
		outfile << "xp=" << pc->stats.xp << "\n";

		// hp and mp
		if (SAVE_HPMP) outfile << "hpmp=" << pc->stats.hp << "," << pc->stats.mp << "\n";

		// stat spec
		outfile << "build=" << pc->stats.physical_character << "," << pc->stats.mental_character << "," << pc->stats.offense_character << "," << pc->stats.defense_character << "\n";

		// equipped gear
		outfile << "equipped_quantity=" << menu->inv->inventory[EQUIPMENT].getQuantities() << "\n";
		outfile << "equipped=" << menu->inv->inventory[EQUIPMENT].getItems() << "\n";

		// carried items
		outfile << "carried_quantity=" << menu->inv->inventory[CARRIED].getQuantities() << "\n";
		outfile << "carried=" << menu->inv->inventory[CARRIED].getItems() << "\n";

		// spawn point
		outfile << "spawn=" << mapr->respawn_map << "," << (int)mapr->respawn_point.x << "," << (int)mapr->respawn_point.y << "\n";

		// action bar
		outfile << "actionbar=";
		for (int i=0; i<12; i++) {
			if (pc->stats.transformed) outfile << menu->act->actionbar[i];
			else outfile << menu->act->hotkeys[i];
			if (i<11) outfile << ",";
		}
		outfile << "\n";

		//shapeshifter value
		if (pc->stats.transform_type == "untransform" || pc->stats.transform_duration != -1) outfile << "transformed=" << "\n";
		else outfile << "transformed=" << pc->stats.transform_type << "," << pc->stats.manual_untransform << "\n";

		// restore hero powers
		if (pc->stats.transformed && pc->hero_stats) {
			pc->stats.powers_list = pc->hero_stats->powers_list;
		}

		// enabled powers
		outfile << "powers=";
		for (unsigned int i=0; i<pc->stats.powers_list.size(); i++) {
			if (i < pc->stats.powers_list.size()-1) {
				if (pc->stats.powers_list[i] > 0)
					outfile << pc->stats.powers_list[i] << ",";
			}
			else {
				if (pc->stats.powers_list[i] > 0)
					outfile << pc->stats.powers_list[i];
			}
		}
		outfile << "\n";

		// restore transformed powers
		if (pc->stats.transformed && pc->charmed_stats) {
			pc->stats.powers_list = pc->charmed_stats->powers_list;
		}

		// campaign data
		outfile << "campaign=";
		outfile << camp->getAll();

		outfile << endl;

		if (outfile.bad()) fprintf(stderr, "Unable to save the game. No write access or disk is full!\n");
		outfile.close();
		outfile.clear();
	}

	// Save stash
	ss.str("");
	ss << PATH_USER;
	if (GAME_PREFIX.length() > 0)
		ss << GAME_PREFIX << "_";
	ss << "stash";
	if (pc->stats.permadeath)
		ss << "_HC" << game_slot;
	ss << ".txt";

	outfile.open(ss.str().c_str(), ios::out);

	if (outfile.is_open()) {
		outfile << "quantity=" << menu->stash->stock.getQuantities() << "\n";
		outfile << "item=" << menu->stash->stock.getItems() << "\n";

		outfile << endl;

		if (outfile.bad()) fprintf(stderr, "Unable to save stash. No write access or disk is full!\n");
		outfile.close();
		outfile.clear();
	}
}

/**
 * When loading the game, load from file if possible
 */
void GameStatePlay::loadGame() {
	int saved_hp = 0;
	int saved_mp = 0;
	int currency = 0;

	// game slots are currently 1-4
	if (game_slot == 0) return;

	FileParser infile;
	int hotkeys[12];

	for (int i=0; i<12; i++) {
		hotkeys[i] = -1;
	}

	stringstream ss;
	ss.str("");
	ss << PATH_USER;
	if (GAME_PREFIX.length() > 0)
		ss << GAME_PREFIX << "_";
	ss << "save" << game_slot << ".txt";

	if (infile.open(ss.str(), false)) {
		while (infile.next()) {
			if (infile.key == "name") pc->stats.name = infile.val;
			else if (infile.key == "permadeath") {
				pc->stats.permadeath = toBool(infile.val);
			}
			else if (infile.key == "option") {
				pc->stats.gfx_base = infile.nextValue();
				pc->stats.gfx_head = infile.nextValue();
				pc->stats.gfx_portrait = infile.nextValue();
			}
			else if (infile.key == "class") {
				pc->stats.character_class = infile.nextValue();
			}
			else if (infile.key == "xp") {
				pc->stats.xp = toUnsignedLong(infile.val);
			}
			else if (infile.key == "hpmp") {
				saved_hp = toInt(infile.nextValue());
				saved_mp = toInt(infile.nextValue());
			}
			else if (infile.key == "build") {
				pc->stats.physical_character = toInt(infile.nextValue());
				pc->stats.mental_character = toInt(infile.nextValue());
				pc->stats.offense_character = toInt(infile.nextValue());
				pc->stats.defense_character = toInt(infile.nextValue());
				if (pc->stats.physical_character < 0 || pc->stats.physical_character > pc->stats.max_points_per_stat ||
						pc->stats.mental_character < 0 || pc->stats.mental_character > pc->stats.max_points_per_stat ||
						pc->stats.offense_character < 0 || pc->stats.offense_character > pc->stats.max_points_per_stat ||
						pc->stats.defense_character < 0 || pc->stats.defense_character > pc->stats.max_points_per_stat) {

					fprintf(stderr, "Some basic stats are out of bounds, setting to zero\n");
					pc->stats.physical_character = 0;
					pc->stats.mental_character = 0;
					pc->stats.offense_character = 0;
					pc->stats.defense_character = 0;
				}
			}
			else if (infile.key == "currency") {
				currency = toInt(infile.val);
			}
			else if (infile.key == "equipped") {
				menu->inv->inventory[EQUIPMENT].setItems(infile.val);
			}
			else if (infile.key == "equipped_quantity") {
				menu->inv->inventory[EQUIPMENT].setQuantities(infile.val);
			}
			else if (infile.key == "carried") {
				menu->inv->inventory[CARRIED].setItems(infile.val);
			}
			else if (infile.key == "carried_quantity") {
				menu->inv->inventory[CARRIED].setQuantities(infile.val);
			}
			else if (infile.key == "spawn") {
				mapr->teleport_mapname = infile.nextValue();
				if (fileExists(mods->locate("maps/" + mapr->teleport_mapname))) {
					mapr->teleport_destination.x = toInt(infile.nextValue()) + 0.5f;
					mapr->teleport_destination.y = toInt(infile.nextValue()) + 0.5f;
					mapr->teleportation = true;
					// prevent spawn.txt from putting us on the starting map
					mapr->clearEvents();
				}
				else {
					fprintf(stderr, "Unable to find maps/%s, loading spawn.txt\n", mapr->teleport_mapname.c_str());
					mapr->teleport_mapname = "spawn.txt";
					mapr->teleport_destination.x = 1;
					mapr->teleport_destination.y = 1;
					mapr->teleportation = true;
				}
			}
			else if (infile.key == "actionbar") {
				for (int i=0; i<12; i++) {
					hotkeys[i] = toInt(infile.nextValue());
					if (hotkeys[i] < 0) {
						fprintf(stderr, "Hotkey power on position %d has negative id, skipping\n", i);
						hotkeys[i] = 0;
					}
					else if ((unsigned)hotkeys[i] > powers->powers.size()-1) {
						fprintf(stderr, "Hotkey power id (%d) out of bounds 1-%d, skipping\n", hotkeys[i], (int)powers->powers.size());
						hotkeys[i] = 0;
					}
					else if (hotkeys[i] != 0 && powers->powers[hotkeys[i]].name == "") {
						fprintf(stderr, "Hotkey power with id=%d, found on position %d does not exist, skipping\n", hotkeys[i], i);
						hotkeys[i] = 0;
					}
				}
				menu->act->set(hotkeys);
			}
			else if (infile.key == "transformed") {
				pc->stats.transform_type = infile.nextValue();
				if (pc->stats.transform_type != "") {
					pc->stats.transform_duration = -1;
					pc->stats.manual_untransform = toBool(infile.nextValue());
				}
			}
			else if (infile.key == "powers") {
				string power;
				while ( (power = infile.nextValue()) != "") {
					if (toInt(power) > 0)
						pc->stats.powers_list.push_back(toInt(power));
				}
			}
			else if (infile.key == "campaign") camp->setAll(infile.val);
		}

		infile.close();
	}
	else fprintf(stderr, "Unable to open %s!\n", ss.str().c_str());


	menu->inv->inventory[EQUIPMENT].fillEquipmentSlots();
	menu->inv->addCurrency(currency);

	// remove items with zero quantity from inventory
	menu->inv->inventory[EQUIPMENT].clean();
	menu->inv->inventory[CARRIED].clean();

	// Load stash
	loadStash();

	// initialize vars
	pc->stats.recalc();
	menu->inv->applyEquipment(menu->inv->inventory[EQUIPMENT].storage);
	// trigger passive effects here? Saved HP/MP values might depend on passively boosted HP/MP
	// powers->activatePassives(pc->stats);
	pc->stats.logic(); // run stat logic once to apply items bonuses
	if (SAVE_HPMP) {
		if (saved_hp < 0 || saved_hp > pc->stats.get(STAT_HP_MAX)) {
			fprintf(stderr, "HP value is out of bounds, setting to maximum\n");
			pc->stats.hp = pc->stats.get(STAT_HP_MAX);
		}
		else pc->stats.hp = saved_hp;

		if (saved_mp < 0 || saved_mp > pc->stats.get(STAT_MP_MAX)) {
			fprintf(stderr, "MP value is out of bounds, setting to maximum\n");
			pc->stats.mp = pc->stats.get(STAT_MP_MAX);
		}
		else pc->stats.mp = saved_mp;
	}
	else {
		pc->stats.hp = pc->stats.get(STAT_HP_MAX);
		pc->stats.mp = pc->stats.get(STAT_MP_MAX);
	}

	// reset character menu
	menu->chr->refreshStats();

	// just for aesthetics, turn the hero to face the camera
	pc->stats.direction = 6;

	// set up MenuTalker for this hero
	menu->talker->setHero(pc->stats.name, pc->stats.character_class, pc->stats.gfx_portrait);

	// load sounds (gender specific)
	pc->loadSounds();

	// apply power upgrades 
	menu->pow->applyPowerUpgrades();
}

/**
 * Load a class definition, index
 */
void GameStatePlay::loadClass(int index) {
	// game slots are currently 1-4
	if (game_slot == 0) return;

	pc->stats.character_class = HERO_CLASSES[index].name;
	pc->stats.physical_character += HERO_CLASSES[index].physical;
	pc->stats.mental_character += HERO_CLASSES[index].mental;
	pc->stats.offense_character += HERO_CLASSES[index].offense;
	pc->stats.defense_character += HERO_CLASSES[index].defense;
	menu->inv->addCurrency(HERO_CLASSES[index].currency);
	menu->inv->inventory[EQUIPMENT].setItems(HERO_CLASSES[index].equipment);
	for (unsigned i=0; i<HERO_CLASSES[index].powers.size(); i++) {
		pc->stats.powers_list.push_back(HERO_CLASSES[index].powers[i]);
	}
	for (unsigned i=0; i<HERO_CLASSES[index].statuses.size(); i++) {
		camp->setStatus(HERO_CLASSES[index].statuses[i]);
	}
	menu->act->set(HERO_CLASSES[index].hotkeys);

	menu->inv->inventory[EQUIPMENT].fillEquipmentSlots();

	// initialize vars
	pc->stats.recalc();
	menu->inv->applyEquipment(menu->inv->inventory[EQUIPMENT].storage);

	// reset character menu
	menu->chr->refreshStats();

}

/*
 * This is used to load the stash when starting a new game
 */
void GameStatePlay::loadStash() {
	// Load stash
	FileParser infile;
	stringstream ss;
	ss.str("");
	ss << PATH_USER;
	if (GAME_PREFIX.length() > 0)
		ss << GAME_PREFIX << "_";
	ss << "stash";
	if (pc->stats.permadeath)
		ss << "_HC" << game_slot;
	ss << ".txt";

	if (infile.open(ss.str(), false)) {
		while (infile.next()) {
			if (infile.key == "item") {
				menu->stash->stock.setItems(infile.val);
			}
			else if (infile.key == "quantity") {
				menu->stash->stock.setQuantities(infile.val);
			}
		}
		infile.close();
	}
	else fprintf(stderr, "Unable to open %s!\n", ss.str().c_str());
}
