// ==============================================================
//	This file is part of Glest (www.glest.org)
//
//	Copyright (C) 2001-2008 Martiño Figueroa
//
//	You can redistribute this code 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
// ==============================================================

#include "tech_tree.h"

#include <cassert>

#include "util.h"
#include "resource.h"
#include "faction_type.h"
#include "logger.h"
#include "xml_parser.h"
#include "platform_util.h"
#include "game_util.h"
#include "window.h"
#include "common_scoped_ptr.h"
#include "leak_dumper.h"

using namespace Shared::Util;
using namespace Shared::Xml;

namespace Glest{ namespace Game{

// =====================================================
// 	class TechTree
// =====================================================

TechTree::TechTree(const vector<string> pathList) {
	SkillType::resetNextAttackBoostId();

	name="";
	treePath="";
	this->pathList.assign(pathList.begin(), pathList.end());

    resourceTypes.clear();
    factionTypes.clear();
	armorTypes.clear();
	attackTypes.clear();
	translatedTechNames.clear();
	translatedTechFactionNames.clear();
	languageUsedForCache = "";
	isValidationModeEnabled = false;
}

string TechTree::getNameUntranslated() const {
	return name;
}

string TechTree::getName(bool translatedValue) {
	if(translatedValue == false) {
		return getNameUntranslated();
	}

	bool foundTranslation = false;
	Lang &lang = Lang::getInstance();
	if(lang.getTechNameLoaded() != name ||
		lang.getLanguage() != languageUsedForCache) {
		//printf("Line: %d Tech [%s]\n",__LINE__,name.c_str());

		foundTranslation = lang.loadTechTreeStrings(name,lang.getLanguage() != languageUsedForCache);
		languageUsedForCache = lang.getLanguage();
		translatedTechFactionNames.erase(name);
		translatedTechNames.erase(name);
	}

	string result = name;
	if(foundTranslation == true) {
		result = lang.getTechTreeString("TechTreeName",name.c_str());
	}
	else {
		result = formatString(result);
	}


	//printf("Line: %d Tech [%s] result [%s]\n",__LINE__,name.c_str(),result.c_str());
	return result;
}

string TechTree::getTranslatedName(string techName, bool forceLoad, bool forceTechtreeActiveFile) {
	string result = techName;

	//printf("Line: %d Tech [%s] forceLoad = %d forceTechtreeActiveFile = %d\n",__LINE__,techName.c_str(),forceLoad,forceTechtreeActiveFile);

	Lang &lang = Lang::getInstance();
	if(forceTechtreeActiveFile == false &&
			translatedTechNames.find(techName) != translatedTechNames.end() &&
			lang.getLanguage() == languageUsedForCache) {
		result = translatedTechNames[techName];
	}
	else {
		name = "";
		string path = findPath(techName);
		if(path != "") {
			string currentPath = path;
			endPathWithSlash(currentPath);
			treePath = currentPath;
			name= lastDir(currentPath);

			lang.loadTechTreeStrings(name,lang.getLanguage() != languageUsedForCache);
			languageUsedForCache = lang.getLanguage();

			translatedTechFactionNames.erase(techName);
			translatedTechNames.erase(techName);

			result = getName(true);

			//printf("techName [%s] name [%s] result [%s]\n",techName.c_str(),name.c_str(),result.c_str());
			translatedTechNames[name] = result;
		}

	}
	return result;
}

string TechTree::getTranslatedFactionName(string techName, string factionName) {
	//printf("Line: %d Tech [%s] name [%s] factionName [%s]\n",__LINE__,techName.c_str(),name.c_str(),factionName.c_str());

	Lang &lang = Lang::getInstance();
	if(lang.getTechNameLoaded() != techName ||
		lang.getLanguage() != languageUsedForCache) {
		//printf("Line: %d Tech [%s] name [%s] lang.getTechNameLoaded() [%s] factionName [%s]\n",__LINE__,techName.c_str(),name.c_str(),lang.getTechNameLoaded().c_str(),factionName.c_str());

		lang.loadTechTreeStrings(techName,lang.getLanguage() != languageUsedForCache);
		languageUsedForCache = lang.getLanguage();

		translatedTechFactionNames.erase(techName);
	}

	std::map<string,std::map<string,string> >::iterator iterMap = translatedTechFactionNames.find(techName);
	if(iterMap != translatedTechFactionNames.end()) {
		if(iterMap->second.find(factionName) != iterMap->second.end()) {
			//printf("Line: %d Tech [%s] factionName [%s]\n",__LINE__,techName.c_str(),factionName.c_str());

			return iterMap->second.find(factionName)->second;
		}
	}

	//printf("Line: %d Tech [%s] factionName [%s]\n",__LINE__,techName.c_str(),factionName.c_str());

	getTranslatedName(techName,false,true);

	string result = lang.getTechTreeString("FactionName_" + factionName,formatString(factionName).c_str());
	//printf(">>result = %s\n",result.c_str());
	translatedTechFactionNames[techName][factionName] = result;

	//printf("Line: %d Translated faction for Tech [%s] faction [%s] result [%s]\n",__LINE__,techName.c_str(),factionName.c_str(),result.c_str());

	return result;
}

Checksum TechTree::loadTech(const string &techName,
		set<string> &factions, Checksum* checksum,
		std::map<string,vector<pair<string, string> > > &loadedFileList,
		bool validationMode) {
	name = "";
	isValidationModeEnabled = validationMode;
    Checksum techtreeChecksum;
    string path=findPath(techName);
    if(path!="") {
    	//printf(">>> path=%s\n",path.c_str());
        load(path, factions, checksum, &techtreeChecksum, loadedFileList,
        		validationMode);
    }
    else {
    	printf(">>> techtree [%s] path not found.\n",techName.c_str());
    }
    return techtreeChecksum;
}

bool TechTree::exists(const string &techName, const vector<string> &pathTechList) {
	bool techFound = false;
	auto_ptr<TechTree> techTree(new TechTree(pathTechList));
    string path = techTree->findPath(techName);
    if(path != "") {
    	techFound = true;
    }
	return techFound;
}

string TechTree::findPath(const string &techName) const {
	return findPath(techName,pathList);
}
string TechTree::findPath(const string &techName, const vector<string> &pathTechList) {
    for(unsigned int idx = 0; idx < pathTechList.size(); ++idx) {
    	string currentPath = (pathTechList)[idx];
    	endPathWithSlash(currentPath);

        string path = currentPath + techName;

        //printf(">>> test path=%s\n",path.c_str());
        if(isdir(path.c_str()) == true) {
            return path;
            //break;
        }
    }
    //return "no path found for tech: \""+techname+"\"";
    return "";
}


void TechTree::load(const string &dir, set<string> &factions, Checksum* checksum,
		Checksum *techtreeChecksum,
		std::map<string,vector<pair<string, string> > > &loadedFileList,
		bool validationMode) {
	if(SystemFlags::getSystemSettingType(SystemFlags::debugSystem).enabled) SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__);

	string currentPath = dir;
	endPathWithSlash(currentPath);
	treePath = currentPath;
	name= lastDir(currentPath);

    Lang &lang = Lang::getInstance();
    lang.loadTechTreeStrings(name, true);
    languageUsedForCache = lang.getLanguage();

	char szBuf[8096]="";
	snprintf(szBuf,8096,Lang::getInstance().getString("LogScreenGameLoadingTechtree","",true).c_str(),formatString(getName(true)).c_str());
	Logger::getInstance().add(szBuf, true);

	vector<string> filenames;
	//load resources
	string str= currentPath + "resources/*.";

    try {
        findAll(str, filenames);
		resourceTypes.resize(filenames.size());

        for(int i = 0; i < (int)filenames.size(); ++i){
            str= currentPath + "resources/" + filenames[i];
            resourceTypes[i].load(str, checksum, &checksumValue, loadedFileList,
            					treePath);
            Window::handleEvent();
			SDL_PumpEvents();
        }

        // Cleanup pixmap memory
        for(int i = 0; i < (int)filenames.size(); ++i) {
        	resourceTypes[i].deletePixels();
        }
    }
    catch(megaglest_runtime_error& ex) {
		SystemFlags::OutputDebug(SystemFlags::debugError,"In [%s::%s Line: %d] Error [%s]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__,ex.what());
		throw megaglest_runtime_error("Error loading Resource Types in: "+ currentPath + "\nMessage: " + ex.what(),!ex.wantStackTrace() || isValidationModeEnabled);
    }
	catch(const exception &e){
		SystemFlags::OutputDebug(SystemFlags::debugError,"In [%s::%s Line: %d] Error [%s]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__,e.what());
		throw megaglest_runtime_error("Error loading Resource Types in: "+ currentPath + "\nMessage: " + e.what(),isValidationModeEnabled);
    }

    // give CPU time to update other things to avoid apperance of hanging
    sleep(0);
    Window::handleEvent();
	SDL_PumpEvents();

	//load tech tree xml info
	try {
		XmlTree	xmlTree;
    	string currentPath = dir;
    	endPathWithSlash(currentPath);
		string path = currentPath + lastDir(dir) + ".xml";

		checksum->addFile(path);
		checksumValue.addFile(path);

		std::map<string,string> mapExtraTagReplacementValues;
		mapExtraTagReplacementValues["$COMMONDATAPATH"] = currentPath + "/commondata/";
		xmlTree.load(path, Properties::getTagReplacementValues(&mapExtraTagReplacementValues));
		loadedFileList[path].push_back(make_pair(currentPath,currentPath));

		Properties::setTechtreePath(currentPath);
		if(SystemFlags::VERBOSE_MODE_ENABLED) printf("==> Set techtree path to [%s]\n",currentPath.c_str());

		const XmlNode *techTreeNode= xmlTree.getRootNode();

		//attack types
		const XmlNode *attackTypesNode= techTreeNode->getChild("attack-types");
		attackTypes.resize(attackTypesNode->getChildCount());
		for(int i = 0; i < (int)attackTypes.size(); ++i) {
			const XmlNode *attackTypeNode= attackTypesNode->getChild("attack-type", i);
			attackTypes[i].setName(attackTypeNode->getAttribute("name")->getRestrictedValue());
			attackTypes[i].setId(i);

			Window::handleEvent();
			SDL_PumpEvents();
		}

	    // give CPU time to update other things to avoid apperance of hanging
	    sleep(0);
		//SDL_PumpEvents();

		//armor types
		const XmlNode *armorTypesNode= techTreeNode->getChild("armor-types");
		armorTypes.resize(armorTypesNode->getChildCount());
		for(int i = 0; i < (int)armorTypes.size(); ++i) {
			const XmlNode *armorTypeNode= armorTypesNode->getChild("armor-type", i);
			armorTypes[i].setName(armorTypeNode->getAttribute("name")->getRestrictedValue());
			armorTypes[i].setId(i);

			Window::handleEvent();
			SDL_PumpEvents();
		}

		//damage multipliers
		damageMultiplierTable.init((int)attackTypes.size(), (int)armorTypes.size());
		const XmlNode *damageMultipliersNode= techTreeNode->getChild("damage-multipliers");
		for(int i = 0; i < (int)damageMultipliersNode->getChildCount(); ++i) {
			const XmlNode *damageMultiplierNode= damageMultipliersNode->getChild("damage-multiplier", i);
			const AttackType *attackType= getAttackType(damageMultiplierNode->getAttribute("attack")->getRestrictedValue());
			const ArmorType *armorType= getArmorType(damageMultiplierNode->getAttribute("armor")->getRestrictedValue());
			double multiplier= damageMultiplierNode->getAttribute("value")->getFloatValue();
			damageMultiplierTable.setDamageMultiplier(attackType, armorType, multiplier);

			Window::handleEvent();
			SDL_PumpEvents();
		}
    }
    catch(megaglest_runtime_error& ex) {
		SystemFlags::OutputDebug(SystemFlags::debugError,"In [%s::%s Line: %d] Error [%s]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__,ex.what());
		throw megaglest_runtime_error("Error loading Tech Tree: "+ currentPath + "\nMessage: " + ex.what(),!ex.wantStackTrace() || isValidationModeEnabled);
    }
	catch(const exception &e){
		SystemFlags::OutputDebug(SystemFlags::debugError,"In [%s::%s Line: %d] Error [%s]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__,e.what());
		throw megaglest_runtime_error("Error loading Tech Tree: "+ currentPath + "\nMessage: " + e.what(),isValidationModeEnabled);
    }

    // give CPU time to update other things to avoid apperance of hanging
    sleep(0);
	//SDL_PumpEvents();

	//load factions
    try{
		factionTypes.resize(factions.size());

		int i=0;
		for ( set<string>::iterator it = factions.begin(); it != factions.end(); ++it ) {
			string factionName = *it;

		    char szBuf[8096]="";
		    snprintf(szBuf,8096,"%s %s [%d / %d] - %s",Lang::getInstance().getString("Loading").c_str(),
		    		Lang::getInstance().getString("Faction").c_str(),
		    		i+1,
		    		(int)factions.size(),
		    		formatString(this->getTranslatedFactionName(name,factionName)).c_str());
		    Logger &logger= Logger::getInstance();
		    logger.setState(szBuf);
		    logger.setProgress((int)((((double)i) / (double)factions.size()) * 100.0));

			factionTypes[i++].load(factionName, this, checksum,&checksumValue,
					loadedFileList,validationMode);

		    // give CPU time to update other things to avoid apperance of hanging
		    sleep(0);
		    Window::handleEvent();
			SDL_PumpEvents();
        }
    }
    catch(megaglest_runtime_error& ex) {
		SystemFlags::OutputDebug(SystemFlags::debugError,"In [%s::%s Line: %d] Error [%s]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__,ex.what());
		throw megaglest_runtime_error("Error loading Faction Types: "+ currentPath + "\nMessage: " + ex.what(),!ex.wantStackTrace() || isValidationModeEnabled);
    }
	catch(const exception &e){
		SystemFlags::OutputDebug(SystemFlags::debugError,"In [%s::%s Line: %d] Error [%s]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__,e.what());
		throw megaglest_runtime_error("Error loading Faction Types: "+ currentPath + "\nMessage: " + e.what(),isValidationModeEnabled);
    }

    if(techtreeChecksum != NULL) {
        *techtreeChecksum = checksumValue;
    }

    if(SystemFlags::getSystemSettingType(SystemFlags::debugSystem).enabled) SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__);
}

TechTree::~TechTree() {
	if(SystemFlags::getSystemSettingType(SystemFlags::debugSystem).enabled) SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__);
	Logger::getInstance().add(Lang::getInstance().getString("LogScreenGameUnLoadingTechtree","",true), true);
	resourceTypes.clear();
	factionTypes.clear();
	armorTypes.clear();
	attackTypes.clear();
}

std::vector<std::string> TechTree::validateFactionTypes() {
	std::vector<std::string> results;
	for (int i = 0; i < (int)factionTypes.size(); ++i) {
		std::vector<std::string> factionResults = factionTypes[i].validateFactionType();
		if(factionResults.empty() == false) {
			results.insert(results.end(), factionResults.begin(), factionResults.end());
		}

		factionResults = factionTypes[i].validateFactionTypeUpgradeTypes();
		if(factionResults.empty() == false) {
			results.insert(results.end(), factionResults.begin(), factionResults.end());
		}
	}

	return results;
}

std::vector<std::string> TechTree::validateResourceTypes() {
	std::vector<std::string> results;
	ResourceTypes resourceTypesNotUsed = resourceTypes;
	for (unsigned int i = 0; i < resourceTypesNotUsed.size(); ++i) {
		ResourceType &rt = resourceTypesNotUsed[i];
		rt.setCleanupMemory(false);
	}
	for (unsigned int i = 0; i < factionTypes.size(); ++i) {
		//printf("Validating [%d / %d] faction [%s]\n",i,(int)factionTypes.size(),factionTypes[i].getName().c_str());

		std::vector<std::string> factionResults = factionTypes[i].validateFactionTypeResourceTypes(resourceTypes);
		if(factionResults.empty() == false) {
			results.insert(results.end(), factionResults.begin(), factionResults.end());
		}

		// Check if the faction uses the resources in this techtree
		// Now lets find a matching faction resource type for the unit
		for(int j = (int)resourceTypesNotUsed.size() -1; j >= 0; --j) {
			const ResourceType &rt = resourceTypesNotUsed[j];
			//printf("Validating [%d / %d] resourcetype [%s]\n",j,(int)resourceTypesNotUsed.size(),rt.getName().c_str());

			if(factionTypes[i].factionUsesResourceType(&rt) == true) {
				//printf("FOUND FACTION CONSUMER FOR RESOURCE - [%d / %d] faction [%s] resource [%d / %d] resourcetype [%s]\n",i,(int)factionTypes.size(),factionTypes[i].getName().c_str(),j,(int)resourceTypesNotUsed.size(),rt.getName().c_str());
				resourceTypesNotUsed.erase(resourceTypesNotUsed.begin() + j);
			}
		}
	}

	if(resourceTypesNotUsed.empty() == false) {
		//printf("FOUND unused resource Types [%d]\n",(int)resourceTypesNotUsed.size());

		for (unsigned int i = 0; i < resourceTypesNotUsed.size(); ++i) {
			const ResourceType &rt = resourceTypesNotUsed[i];
			char szBuf[8096]="";
			snprintf(szBuf,8096,"The Resource type [%s] is not used by any units in this techtree!",rt.getName().c_str());
			results.push_back(szBuf);
		}
	}
    return results;
}

// ==================== get ====================

FactionType *TechTree::getTypeByName(const string &name) {
    for(int i=0; i < (int)factionTypes.size(); ++i) {
          if(factionTypes[i].getName(false) == name) {
               return &factionTypes[i];
          }
    }
    if(SystemFlags::getSystemSettingType(SystemFlags::debugSystem).enabled) SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__);
    throw megaglest_runtime_error("Faction not found: " + name,true);
}

const FactionType *TechTree::getType(const string &name) const {
    for(int i=0; i < (int)factionTypes.size(); ++i) {
          if(factionTypes[i].getName(false) == name) {
               return &factionTypes[i];
          }
    }
    if(SystemFlags::getSystemSettingType(SystemFlags::debugSystem).enabled) SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__);
    throw megaglest_runtime_error("Faction not found: " + name,true);
}

const ResourceType *TechTree::getTechResourceType(int i) const{
     for(int j=0; j < getResourceTypeCount(); ++j){
          const ResourceType *rt= getResourceType(j);
          assert(rt != NULL);
          if(rt == NULL) {
        	  throw megaglest_runtime_error("rt == NULL");
          }
          if(rt->getResourceNumber() == i && rt->getClass() == rcTech)
               return getResourceType(j);
     }

     return getFirstTechResourceType();
}

const ResourceType *TechTree::getFirstTechResourceType() const{
     for(int i=0; i<getResourceTypeCount(); ++i){
          const ResourceType *rt= getResourceType(i);
          assert(rt != NULL);
          if(rt->getResourceNumber()==1 && rt->getClass()==rcTech)
               return getResourceType(i);
     }

     char szBuf[8096]="";
     snprintf(szBuf,8096,"The referenced tech tree [%s] is either missing or has no resources defined but at least one resource is required.",this->name.c_str());
     throw megaglest_runtime_error(szBuf,true);
}

const ResourceType *TechTree::getResourceType(const string &name) const{

	for(int i=0; i < (int)resourceTypes.size(); ++i){
		if(resourceTypes[i].getName()==name){
			return &resourceTypes[i];
		}
	}

	throw megaglest_runtime_error("Resource Type not found: " + name,true);
}

const ArmorType *TechTree::getArmorType(const string &name) const{
	for(int i=0; i < (int)armorTypes.size(); ++i){
		if(armorTypes[i].getName(false)==name){
			return &armorTypes[i];
		}
	}

	throw megaglest_runtime_error("Armor Type not found: " + name,true);
}

const AttackType *TechTree::getAttackType(const string &name) const{
	for(int i=0; i < (int)attackTypes.size(); ++i){
		if(attackTypes[i].getName(false)==name){
			return &attackTypes[i];
		}
	}

	throw megaglest_runtime_error("Attack Type not found: " + name,true);
}

double TechTree::getDamageMultiplier(const AttackType *att, const ArmorType *art) const {
	return damageMultiplierTable.getDamageMultiplier(att, art);
}

void TechTree::saveGame(XmlNode *rootNode) {
	std::map<string,string> mapTagReplacements;
	XmlNode *techTreeNode = rootNode->addChild("TechTree");

//	string name;
	techTreeNode->addAttribute("name",name, mapTagReplacements);
//    //string desc;
//	string treePath;
	//techTreeNode->addAttribute("treePath",treePath, mapTagReplacements);
//	vector<string> pathList;
//	for(unsigned int i = 0; i < pathList.size(); ++i) {
//		XmlNode *pathListNode = techTreeNode->addChild("pathList");
//		pathListNode->addAttribute("value",pathList[i], mapTagReplacements);
//	}
//    ResourceTypes resourceTypes;
//	for(unsigned int i = 0; i < resourceTypes.size(); ++i) {
//		ResourceType &rt = resourceTypes[i];
//		rt.saveGame(techTreeNode);
//	}
//    FactionTypes factionTypes;
//	for(unsigned int i = 0; i < factionTypes.size(); ++i) {
//		FactionType &ft = factionTypes[i];
//		ft.saveGame(techTreeNode);
//	}

//	ArmorTypes armorTypes;
//	for(unsigned int i = 0; i < armorTypes.size(); ++i) {
//		ArmorType &at = armorTypes[i];
//		at.saveGame(techTreeNode);
//	}

//	AttackTypes attackTypes;
//	for(unsigned int i = 0; i < attackTypes.size(); ++i) {
//		AttackType &at = attackTypes[i];
//		at.saveGame(techTreeNode);
//	}

//	DamageMultiplierTable damageMultiplierTable;
//	damageMultiplierTable.saveGame(techTreeNode);

//	Checksum checksumValue;
	techTreeNode->addAttribute("checksumValue",intToStr(checksumValue.getSum()), mapTagReplacements);
}

}}//end namespace
