/**
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2015  Mark Samman <mark.samman@gmail.com>
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "otpch.h"

#include "store.h"

#include "pugicast.h"
#include "database.h"

StoreOffer& StoreCategory::createOffer(uint32_t offerId)
{
	offers.emplace_back(offerId);
	return offers.back();
}

Store::Store()
{
	loaded = false;
	runningId = 0;
}

void Store::getTransactionHistory(uint32_t accountId, uint16_t page, uint32_t entriesPerPage, std::vector<StoreTransaction>& out) const
{
	Database* db = Database::getInstance();

	std::ostringstream query;
	query << "SELECT `id`, `coins`, `description`, `timestamp` FROM `store_history` WHERE `account_id` = " << accountId << " ORDER BY `timestamp` DESC LIMIT " << entriesPerPage + 1 << " OFFSET " << (page * entriesPerPage);
	
	DBResult_ptr result = db->storeQuery(query.str());
	if (result) {
		do {
			out.emplace_back(result->getNumber<uint32_t>("id"), result->getNumber<int32_t>("coins"), result->getString("description"), result->getNumber<time_t>("timestamp"));
		} while (result->next());
	}	
}

void Store::onTransactionCompleted(uint32_t accountId, int32_t coins, const std::string& description) const
{
	Database* db = Database::getInstance();

	std::ostringstream query;
	query << "INSERT INTO `store_history` (`account_id`, `coins`, `description`, `timestamp`) VALUES (" << accountId << "," << coins << "," << db->escapeString(description) << "," << time(nullptr) << ")";
	
	db->executeQuery(query.str());
}

bool Store::loadFromXml(bool /*reloading = false*/)
{
	pugi::xml_document doc;
	pugi::xml_parse_result result = doc.load_file("data/store/store.xml");
	if (!result) {
		printXMLError("Error - Store::loadFromXml", "data/store/store.xml", result);
		return false;
	}

	loaded = true;

	std::string name;
	std::string description;
	StringVec icons;

	for (auto categoryNode : doc.child("store").children()) {
		pugi::xml_attribute attr;	

		name.clear();
		description.clear();
		icons.clear();

		if ((attr = categoryNode.attribute("name"))) {
			name = attr.as_string();
		} else {
			std::cout << "[Error - Store::loadFromXml] Missing category name." << std::endl;
			continue;
		}

		if ((attr = categoryNode.attribute("description"))) {
			description = attr.as_string();
		}

		if ((attr = categoryNode.attribute("icons"))) {
			icons = explodeString(attr.as_string(), ";");
		}

		categories.emplace_back(name, description, icons);
		StoreCategory& category = categories.back();

		for (auto offerNode : categoryNode.children()) {
			StoreOffer& offer = category.createOffer(runningId++);

			if ((attr = offerNode.attribute("name"))) {
				offer.name = attr.as_string();
			} else {
				std::cout << "[Error - Store::loadFromXml] Missing offer name in category: " << category.name << std::endl;
				category.offers.pop_back();
				continue;
			}

			if ((attr = offerNode.attribute("description"))) {
				offer.description = attr.as_string();
			}

			if ((attr = offerNode.attribute("message"))) {
				offer.message = attr.as_string();
			}

			if ((attr = offerNode.attribute("icons"))) {
				offer.icons = explodeString(attr.as_string(), ";");
			}

			if ((attr = offerNode.attribute("price"))) {
				offer.price = attr.as_uint();
			}
						
			for (auto subOfferNode : offerNode.children()) {
				name.clear();
				description.clear();
				icons.clear();

				if ((attr = subOfferNode.attribute("name"))) {
					name = attr.as_string();
				}

				if ((attr = subOfferNode.attribute("description"))) {
					description = attr.as_string();
				}

				if ((attr = subOfferNode.attribute("icons"))) {
					icons = explodeString(attr.as_string(), ";");
				}

				offer.subOffers.push_back(SubOffer(name, description, icons));
			}

			if ((attr = offerNode.attribute("script"))) {
				if (!scriptInterface) {
					scriptInterface.reset(new LuaScriptInterface("Store Interface"));
					scriptInterface->initState();
				}

				std::string script = attr.as_string();
				if (scriptInterface->loadFile("data/store/scripts/" + script) == 0) {
					offer.scriptInterface = scriptInterface.get();
					offer.renderEvent = scriptInterface->getEvent("onRender");
					offer.buyEvent = scriptInterface->getEvent("onBuy");
				} else {
					std::cout << "[Warning - Store::loadFromXml] Can not load script: " << script << std::endl;
					std::cout << scriptInterface->getLastLuaError() << std::endl;
				}				
			}			
		}
	}

	return true;
}

bool Store::reload()
{
	categories.clear();

	loaded = false;
	
	scriptInterface.reset();

	return loadFromXml(true);
}

boost::optional<StoreOffer&> Store::getOfferById(uint32_t id)
{
	for (auto& category : getCategories()) {
		for (auto& offer : category.getOffers()) {
			if (offer.getId() == id) {
				return boost::optional<StoreOffer&>(offer);
			}
		}
	}

	return boost::optional<StoreOffer&>();
}

bool Store::executeOnRender(Player* player, StoreOffer* offer)
{
	if (offer->renderEvent != -1) {
		// onRender(player, offer)
		LuaScriptInterface* scriptInterface = offer->scriptInterface;
		if (!scriptInterface->reserveScriptEnv()) {
			std::cout << "[Error - Store::executeOnRender] Call stack overflow" << std::endl;
			return false;
		}

		ScriptEnvironment* env = scriptInterface->getScriptEnv();
		env->setScriptId(offer->renderEvent, scriptInterface);

		lua_State* L = scriptInterface->getLuaState();
		scriptInterface->pushFunction(offer->renderEvent);

		LuaScriptInterface::pushUserdata<Player>(L, player);
		LuaScriptInterface::setMetatable(L, -1, "Player");

		LuaScriptInterface::pushUserdata<StoreOffer>(L, offer);
		LuaScriptInterface::setMetatable(L, -1, "StoreOffer");

		return scriptInterface->callFunction(2);
	}

	return false;
}

bool Store::executeOnBuy(Player* player, StoreOffer* offer, const std::string& param)
{
	if (offer->buyEvent != -1) {
		// onBuy(player, offer, param)
		LuaScriptInterface* scriptInterface = offer->scriptInterface;
		if (!scriptInterface->reserveScriptEnv()) {
			std::cout << "[Error - Store::executeOnBuy] Call stack overflow" << std::endl;
			return false;
		}

		ScriptEnvironment* env = scriptInterface->getScriptEnv();
		env->setScriptId(offer->buyEvent, scriptInterface);

		lua_State* L = scriptInterface->getLuaState();
		scriptInterface->pushFunction(offer->buyEvent);

		LuaScriptInterface::pushUserdata<Player>(L, player);
		LuaScriptInterface::setMetatable(L, -1, "Player");

		LuaScriptInterface::pushUserdata<StoreOffer>(L, offer);
		LuaScriptInterface::setMetatable(L, -1, "StoreOffer");

		if (param.size() == 0) {
			lua_pushnil(L);
		} else {
			LuaScriptInterface::pushString(L, param);			
		}

		return scriptInterface->callFunction(3);
	}

	return false; 
}
