// ==============================================================
//	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 "skill_type.h"

#include <cassert>

#include "sound.h"
#include "util.h"
#include "lang.h"
#include "renderer.h"
#include "particle_type.h"
#include "unit_particle_type.h"
#include "tech_tree.h"
#include "faction_type.h"
#include "leak_dumper.h"

using namespace Shared::Util;
using namespace Shared::Graphics;

namespace Glest{ namespace Game{

int SkillType::nextAttackBoostId = 0;

AttackBoost::AttackBoost() : boostUpgrade() {
	enabled = false;
	allowMultipleBoosts = false;
	radius = 0;
	targetType = abtFaction;
	unitParticleSystemTypeForSourceUnit = NULL;
	unitParticleSystemTypeForAffectedUnit = NULL;
	includeSelf 	= false;
}

AttackBoost::~AttackBoost() {
	delete unitParticleSystemTypeForSourceUnit;
	unitParticleSystemTypeForSourceUnit = NULL;

	delete unitParticleSystemTypeForAffectedUnit;
	unitParticleSystemTypeForAffectedUnit = NULL;
}

bool AttackBoost::isAffected(const Unit *source, const Unit *dest) const {
	bool result = false;
	if(enabled == true &&
			source != NULL && dest != NULL &&
			(includeSelf == true || source != dest)) {
		bool destUnitMightApply = false;
		if(source == dest && includeSelf == true) {
			destUnitMightApply = true;
		}
		else {
			// All units are affected (including enemies)
			if(targetType == abtAll) {
				destUnitMightApply = (boostUnitList.empty() == true);

				// Specify which units are affected
				for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
					const UnitType *ut = boostUnitList[i];
					if(dest->getType()->getId() == ut->getId()) {
						destUnitMightApply = true;
						break;
					}
				}

			}
			// Only same faction units are affected
			else if(targetType == abtFaction) {
				//if(boostUnitList.empty() == true) {
				if(source->getFactionIndex() == dest->getFactionIndex()) {
					//destUnitMightApply = true;
					destUnitMightApply = (boostUnitList.empty() == true);

					// Specify which units are affected
					for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
						const UnitType *ut = boostUnitList[i];
						if(dest->getType()->getId() == ut->getId()) {
							destUnitMightApply = true;
							break;
						}
					}

				}
				//}
			}
			// Only ally units are affected
			else if(targetType == abtAlly) {
				//if(boostUnitList.empty() == true) {
				if(source->isAlly(dest) == true) {
					//destUnitMightApply = true;
					destUnitMightApply = (boostUnitList.empty() == true);

					// Specify which units are affected
					for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
						const UnitType *ut = boostUnitList[i];
						if(dest->getType()->getId() == ut->getId()) {
							destUnitMightApply = true;
							break;
						}
					}
				}
				//}
			}
			// Only foe units are affected
			else if(targetType == abtFoe) {
				//if(boostUnitList.empty() == true) {
				if(source->isAlly(dest) == false) {
					//destUnitMightApply = true;
					destUnitMightApply = (boostUnitList.empty() == true);

					// Specify which units are affected
					for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
						const UnitType *ut = boostUnitList[i];
						if(dest->getType()->getId() == ut->getId()) {
							destUnitMightApply = true;
							break;
						}
					}
				}
				//}
			}
			else if(targetType == abtUnitTypes) {
				// Specify which units are affected
				for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
					const UnitType *ut = boostUnitList[i];
					if(dest->getType()->getId() == ut->getId()) {
						destUnitMightApply = true;
						break;
					}
				}
			}
		}

		if(destUnitMightApply == true) {
			float distance = source->getCenteredPos().dist(dest->getCenteredPos());
			if(distance <= radius) {
				result = true;
			}
		}
	}

	return result;
}

string AttackBoost::getDesc(bool translatedValue) const{
	Lang &lang= Lang::getInstance();
    string str= "";
    string indent="  ";
    if(enabled) {
    	if(boostUnitList.empty() == false) {
    		str+= "\n"+ lang.getString("Effects")+":\n";
    	}

    	str += indent+lang.getString("effectRadius") + ": " + intToStr(radius) +"\n";
    	if(allowMultipleBoosts==false) {
    		string allowIt=lang.getString("No");
    		if(allowMultipleBoosts==true)
    			allowIt=lang.getString("False");
    		str += indent+lang.getString("allowMultiBoost") + ": " + allowIt +"\n";
    	}
    	str+=boostUpgrade.getDesc(translatedValue);

    	if(targetType==abtAlly)
    	{
    		str+= lang.getString("AffectedUnitsFromTeam") +":\n";
    	}
    	else if(targetType==abtFoe)
    	{
    		str+= lang.getString("AffectedUnitsFromFoe") +":\n";
    	}
    	else if(targetType==abtFaction)
    	{
    		str+= lang.getString("AffectedUnitsFromYourFaction") +":\n";
    	}
    	else if(targetType==abtUnitTypes)
    	{
    		str+= lang.getString("AffectedUnitsFromAll") +":\n";
    	}
    	else if(targetType==abtAll)
    	{
    		str+= lang.getString("AffectedUnitsFromAll") +":\n";
    	}

    	if(boostUnitList.empty() == false) {
			for(int i=0; i < (int)boostUnitList.size(); ++i){
				str+= "  "+boostUnitList[i]->getName(translatedValue)+"\n";
			}
    	}
    	else
    	{
    		str+= lang.getString("All")+"\n";
    	}

    	return str;
    }
    else
    	return "";
}

void AttackBoost::loadGame(const XmlNode *rootNode, Faction *faction, const SkillType *skillType) {
	const XmlNode *attackBoostNode = rootNode->getChild("AttackBoost");

	enabled = (attackBoostNode->getAttribute("enabled")->getIntValue() != 0);
	allowMultipleBoosts = (attackBoostNode->getAttribute("allowMultipleBoosts")->getIntValue() != 0);
	radius = attackBoostNode->getAttribute("radius")->getIntValue();
	targetType = static_cast<AttackBoostTargetType>(attackBoostNode->getAttribute("targetType")->getIntValue());

	if(attackBoostNode->hasChild("UnitType") == true) {
		vector<XmlNode *> attackBoostNodeList = attackBoostNode->getChildList("UnitType");
		for(unsigned int i = 0; i < attackBoostNodeList.size(); ++i) {
			XmlNode *node = attackBoostNodeList[i];

			string unitTypeName = node->getAttribute("name")->getValue();
			const UnitType *unitType = faction->getType()->getUnitType(unitTypeName);
			if(unitType != NULL) {
				boostUnitList.push_back(unitType);
			}
		}
	}
	//boostUpgrade.loadGame(attackBoostNode,faction);
	boostUpgrade = skillType->getAttackBoost()->boostUpgrade;

	unitParticleSystemTypeForSourceUnit = new UnitParticleSystemType();
	unitParticleSystemTypeForSourceUnit->loadGame(attackBoostNode);

	unitParticleSystemTypeForAffectedUnit = new UnitParticleSystemType();
	unitParticleSystemTypeForAffectedUnit->loadGame(attackBoostNode);

	includeSelf = (attackBoostNode->getAttribute("includeSelf")->getIntValue() != 0);
	name = attackBoostNode->getAttribute("name")->getValue();
}

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

//	bool enabled;
	attackBoostNode->addAttribute("enabled",intToStr(enabled), mapTagReplacements);
//	bool allowMultipleBoosts;
	attackBoostNode->addAttribute("allowMultipleBoosts",intToStr(allowMultipleBoosts), mapTagReplacements);
//	int radius;
	attackBoostNode->addAttribute("radius",intToStr(radius), mapTagReplacements);
//	AttackBoostTargetType targetType;
	attackBoostNode->addAttribute("targetType",intToStr(targetType), mapTagReplacements);
//	vector<const UnitType *> boostUnitList;
	for(unsigned int i = 0; i < boostUnitList.size(); ++i) {
		const UnitType *ut = boostUnitList[i];
		XmlNode *unitTypeNode = attackBoostNode->addChild("UnitType");
		unitTypeNode->addAttribute("name",ut->getName(false), mapTagReplacements);
	}
//	UpgradeTypeBase boostUpgrade;
	boostUpgrade.saveGame(attackBoostNode);
//	UnitParticleSystemType *unitParticleSystemTypeForSourceUnit;
	if(unitParticleSystemTypeForSourceUnit != NULL) {
		unitParticleSystemTypeForSourceUnit->saveGame(attackBoostNode);
	}
//	UnitParticleSystemType *unitParticleSystemTypeForAffectedUnit;
	if(unitParticleSystemTypeForAffectedUnit != NULL) {
		unitParticleSystemTypeForAffectedUnit->saveGame(attackBoostNode);
	}

//	bool includeSelf;
	attackBoostNode->addAttribute("includeSelf",intToStr(includeSelf), mapTagReplacements);
//	string name;
	attackBoostNode->addAttribute("name",name, mapTagReplacements);
}

// =====================================================
// 	class SkillType
// =====================================================

SkillType::~SkillType() {
	deleteValues(sounds.getSounds().begin(), sounds.getSounds().end());
	//remove unitParticleSystemTypes
	while(!unitParticleSystemTypes.empty()) {
		delete unitParticleSystemTypes.back();
		unitParticleSystemTypes.pop_back();
	}
}

const XmlNode * SkillType::findAttackBoostDetails(string attackBoostName,
		const XmlNode *attackBoostsNode,const XmlNode *attackBoostNode) {
	const XmlNode *result = attackBoostNode;

	if(attackBoostsNode != NULL && attackBoostName != "") {
		for(int i = 0; i < (int)attackBoostsNode->getChildCount(); ++i) {
			const XmlNode *abn= attackBoostsNode->getChild("attack-boost", i);

			string sharedName = abn->getAttribute("name")->getRestrictedValue();
			if(sharedName == attackBoostName) {
				result = abn;
				break;
			}
		}

	}

	return result;
}

void SkillType::loadAttackBoost(const XmlNode *attackBoostsNode, const XmlNode *attackBoostNode,
		const FactionType *ft, string parentLoader, const string & dir,
		string currentPath, std::map<string,vector<pair<string,string> > > & loadedFileList,
		const TechTree *tt) {

	attackBoost.enabled = true;
    if(attackBoostNode->hasAttribute("name") == true) {
        attackBoost.name = attackBoostNode->getAttribute("name")->getRestrictedValue();

        attackBoostNode = findAttackBoostDetails(attackBoost.name,attackBoostsNode,attackBoostNode);
    }
    else {
        attackBoost.name = "attack-boost-autoname-" + intToStr(getNextAttackBoostId());
    }
    string targetType = attackBoostNode->getChild("target")->getAttribute("value")->getValue();

    attackBoost.allowMultipleBoosts = false;
    if(attackBoostNode->hasChild("allow-multiple-boosts") == true) {
    	attackBoost.allowMultipleBoosts = attackBoostNode->getChild("allow-multiple-boosts")->getAttribute("value")->getBoolValue();
    }

    attackBoost.radius = attackBoostNode->getChild("radius")->getAttribute("value")->getIntValue();

    attackBoost.includeSelf = false;
    if(attackBoostNode->getChild("target")->hasAttribute("include-self") == true) {
        attackBoost.includeSelf = attackBoostNode->getChild("target")->getAttribute("include-self")->getBoolValue();
    }

    if(targetType == "ally") {
        attackBoost.targetType = abtAlly;
        for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
            const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
            attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
        }
    }
    else if(targetType == "foe") {
		attackBoost.targetType = abtFoe;
		for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
			const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
			attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
		}
	}
	else if(targetType == "faction") {
		attackBoost.targetType = abtFaction;
		for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
			const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
			attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
		}
	}
	else if(targetType == "unit-types") {
		attackBoost.targetType = abtUnitTypes;
		for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
			const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
			attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
		}
	}
	else if(targetType == "all") {
		attackBoost.targetType = abtAll;
		for(int i = 0;i < (int)attackBoostNode->getChild("target")->getChildCount();++i) {
			const XmlNode *boostUnitNode = attackBoostNode->getChild("target")->getChild("unit-type", i);
			attackBoost.boostUnitList.push_back(ft->getUnitType(boostUnitNode->getAttribute("name")->getRestrictedValue()));
		}
	}
	else {
		char szBuf[8096] = "";
		snprintf(szBuf, 8096,"Unsupported target [%s] specified for attack boost for skill [%s] in [%s]", targetType.c_str(), name.c_str(), parentLoader.c_str());
		throw megaglest_runtime_error(szBuf);
	}

    attackBoost.boostUpgrade.load(attackBoostNode,attackBoost.name);
    if(attackBoostNode->hasChild("particles") == true) {
        const XmlNode *particleNode = attackBoostNode->getChild("particles");
        bool particleEnabled = particleNode->getAttribute("value")->getBoolValue();
        if(particleEnabled == true) {
            if(particleNode->hasChild("originator-particle-file") == true){
                const XmlNode *particleFileNode = particleNode->getChild("originator-particle-file");
                string path = particleFileNode->getAttribute("path")->getRestrictedValue();
                attackBoost.unitParticleSystemTypeForSourceUnit = new UnitParticleSystemType();
                attackBoost.unitParticleSystemTypeForSourceUnit->load(particleFileNode, dir, currentPath + path, &Renderer::getInstance(), loadedFileList, parentLoader, tt->getPath());
                loadedFileList[currentPath + path].push_back(make_pair(parentLoader, particleFileNode->getAttribute("path")->getRestrictedValue()));
            }
            if(particleNode->hasChild("affected-particle-file") == true) {
                const XmlNode *particleFileNode = particleNode->getChild("affected-particle-file");
                string path = particleFileNode->getAttribute("path")->getRestrictedValue();
                attackBoost.unitParticleSystemTypeForAffectedUnit = new UnitParticleSystemType();
                attackBoost.unitParticleSystemTypeForAffectedUnit->load(particleFileNode, dir, currentPath + path, &Renderer::getInstance(), loadedFileList, parentLoader, tt->getPath());
                loadedFileList[currentPath + path].push_back(make_pair(parentLoader, particleFileNode->getAttribute("path")->getRestrictedValue()));
            }
        }
    }
}

void SkillType::load(const XmlNode *sn, const XmlNode *attackBoostsNode,
		const string &dir, const TechTree *tt, const FactionType *ft,
		std::map<string,vector<pair<string, string> > > &loadedFileList,
		string parentLoader) {
	//name
	name= sn->getChild("name")->getAttribute("value")->getRestrictedValue();

	//ep cost
	if(sn->hasChild("ep-cost") == true) {
		mpCost = sn->getChild("ep-cost")->getAttribute("value")->getIntValue();
	}
	else {
		mpCost = 0;
	}

	if (sn->hasChild("hp-cost") == true) {
		hpCost = sn->getChild("hp-cost")->getAttribute("value")->getIntValue();
	}
	else {
		hpCost = 0;
	}

	//speed
	if(sn->hasChild("speed") == true) {
		speed = sn->getChild("speed")->getAttribute("value")->getIntValue();
	}
	else {
		speed = 0;
	}

	//anim speed
	if(sn->hasChild("anim-speed") == true) {
		animSpeed = sn->getChild("anim-speed")->getAttribute("value")->getIntValue();
	}
	else {
		animSpeed = 0;
	}

	//model
	string currentPath = dir;
	endPathWithSlash(currentPath);

	animationRandomCycleMaxcount = -1;
	if(sn->hasChild("animation-random-cycle-maxcount") == true) {
		const XmlNode *randomCycleCountNode = sn->getChild("animation-random-cycle-maxcount");
		animationRandomCycleMaxcount = randomCycleCountNode->getAttribute("value")->getIntValue();
	}

	if(sn->hasChild("animation") == true) {
		//string path= sn->getChild("animation")->getAttribute("path")->getRestrictedValue(currentPath);
		vector<XmlNode *> animationList = sn->getChildList("animation");
		for(unsigned int i = 0; i < animationList.size(); ++i) {
			string path= animationList[i]->getAttribute("path")->getRestrictedValue(currentPath);
			if(fileExists(path) == true) {
				Model *animation= Renderer::getInstance().newModel(rsGame, path, false, &loadedFileList, &parentLoader);
				loadedFileList[path].push_back(make_pair(parentLoader,animationList[i]->getAttribute("path")->getRestrictedValue()));

				animations.push_back(animation);
				//printf("**FOUND ANIMATION [%s]\n",path.c_str());

				AnimationAttributes animationAttributeList;
				if(animationList[i]->getAttribute("minHp",false) != NULL && animationList[i]->getAttribute("maxHp",false) != NULL) {
					animationAttributeList.fromHp = animationList[i]->getAttribute("minHp")->getIntValue();
					animationAttributeList.toHp = animationList[i]->getAttribute("maxHp")->getIntValue();
				}
				animationAttributes.push_back(animationAttributeList);
			}
			else {
				SystemFlags::OutputDebug(SystemFlags::debugError,"In [%s::%s Line %d] WARNING CANNOT LOAD MODEL [%s] for parentLoader [%s]\n",__FILE__,__FUNCTION__,__LINE__,path.c_str(),parentLoader.c_str());
			}
		}
		if(animations.empty() == true) {
			char szBuf[8096]="";
			snprintf(szBuf,8096,"Error no animations found for skill [%s] for parentLoader [%s]",name.c_str(),parentLoader.c_str());
			throw megaglest_runtime_error(szBuf);
		}
	}

	//particles
	if(sn->hasChild("particles")) {
		const XmlNode *particleNode= sn->getChild("particles");
		bool particleEnabled= particleNode->getAttribute("value")->getBoolValue();
		if(particleEnabled) {
			for(int i = 0; i < (int)particleNode->getChildCount(); ++i) {
				const XmlNode *particleFileNode= particleNode->getChild("particle-file", i);
				string path= particleFileNode->getAttribute("path")->getRestrictedValue();
				UnitParticleSystemType *unitParticleSystemType= new UnitParticleSystemType();
				unitParticleSystemType->load(particleFileNode, dir, currentPath + path, &Renderer::getInstance(),
						loadedFileList,parentLoader,tt->getPath());

				if(particleNode->getAttribute("start-time",false) != NULL) {
					//printf("*NOTE particle system type has start-time [%f]\n",particleNode->getAttribute("start-time")->getFloatValue());
					unitParticleSystemType->setStartTime(particleNode->getAttribute("start-time")->getFloatValue());
				}
				if(particleNode->getAttribute("end-time",false) != NULL) {
					//printf("*NOTE particle system type has end-time [%f]\n",particleNode->getAttribute("end-time")->getFloatValue());
					unitParticleSystemType->setEndTime(particleNode->getAttribute("end-time")->getFloatValue());
				}

				loadedFileList[currentPath + path].push_back(make_pair(parentLoader,particleFileNode->getAttribute("path")->getRestrictedValue()));
				unitParticleSystemTypes.push_back(unitParticleSystemType);
			}
		}
	}

	//sound
	if(sn->hasChild("sound")) {
		const XmlNode *soundNode= sn->getChild("sound");
		if(soundNode->getAttribute("enabled")->getBoolValue()) {
			soundStartTime= soundNode->getAttribute("start-time")->getFloatValue();
			sounds.resize((int)soundNode->getChildCount());
			for(int i = 0; i < (int)soundNode->getChildCount(); ++i) {
				const XmlNode *soundFileNode= soundNode->getChild("sound-file", i);
				string path= soundFileNode->getAttribute("path")->getRestrictedValue(currentPath, true);

				StaticSound *sound= new StaticSound();
				sound->load(path);
				loadedFileList[path].push_back(make_pair(parentLoader,soundFileNode->getAttribute("path")->getRestrictedValue()));
				sounds[i]= sound;
			}
		}
	}


	// attack-boost
	if(sn->hasChild("attack-boost") == true) {
		//printf("$$FOUND ATTACK BOOST FOR [%s]\n",parentLoader.c_str());
		const XmlNode *attackBoostNode = sn->getChild("attack-boost");
		loadAttackBoost(attackBoostsNode, attackBoostNode, ft, parentLoader, dir, currentPath, loadedFileList, tt);
	}
}

bool SkillType::CanCycleNextRandomAnimation(const int *animationRandomCycleCount) const {
	bool result = true;
	if(animations.size() > 1) {
		if( animationRandomCycleMaxcount >= 0 &&
				animationRandomCycleCount != NULL &&
			*animationRandomCycleCount >= animationRandomCycleMaxcount) {
			result = false;
		}
	}
	return result;
}

const AnimationAttributes SkillType::getAnimationAttribute(int index) const {
	return animationAttributes[index];
}

Model *SkillType::getAnimation(float animProgress, const Unit *unit,
		int *lastAnimationIndex, int *animationRandomCycleCount) const {
	int modelIndex = 0;
	//printf("Count [%d] animProgress = [%f] for skill [%s] animationRandomCycleCount = %d\n",animations.size(),animProgress,name.c_str(),*animationRandomCycleCount);
	if(animations.size() > 1) {
		//printf("animProgress = [%f] for skill [%s] animationRandomCycleCount = %d\n",animProgress,name.c_str(),*animationRandomCycleCount);

		if(lastAnimationIndex) {
			modelIndex = *lastAnimationIndex;
		}
		if(modelIndex < 0 || animProgress > 1.0f) {
			bool canCycle = CanCycleNextRandomAnimation(animationRandomCycleCount);
			if(canCycle == true) {
				vector<int> filteredAnimations;
				bool foundSpecificAnimation = false;
				if(unit != NULL) {
					for(unsigned int i = 0; i < animationAttributes.size(); ++i) {
						const AnimationAttributes &attributes = animationAttributes[i];
						if(attributes.fromHp != 0 || attributes.toHp != 0) {
							if(unit->getHp() >= attributes.fromHp && unit->getHp() <= attributes.toHp) {
								modelIndex = i;
								foundSpecificAnimation = true;
								filteredAnimations.push_back(i);
								//printf("SELECTING Model index = %d [%s] model attributes [%d to %d] for unit [%s - %d] with HP = %d\n",i,animations[modelIndex]->getFileName().c_str(),attributes.fromHp,attributes.toHp,unit->getType()->getName().c_str(),unit->getId(),unit->getHp());
								//break;
							}
						}
					}
					// Need to make sure the filtered list does NOT include any
					// models with min/max hp
					if(foundSpecificAnimation == false) {
						for(unsigned int i = 0; i < animationAttributes.size(); ++i) {
							const AnimationAttributes &attributes = animationAttributes[i];
							if(attributes.fromHp == 0 && attributes.toHp == 0) {
								modelIndex = i;
								foundSpecificAnimation = true;
								filteredAnimations.push_back(i);
									//printf("SELECTING Model index = %d [%s] model attributes [%d to %d] for unit [%s - %d] with HP = %d\n",i,animations[modelIndex]->getFileName().c_str(),attributes.fromHp,attributes.toHp,unit->getType()->getName().c_str(),unit->getId(),unit->getHp());
									//break;
							}
						}
					}
				}

				if(foundSpecificAnimation == false) {
					//int modelIndex = random.randRange(0,animations.size()-1);
					Chrono seed(true);
					srand((unsigned int)seed.getCurTicks() + (unit != NULL ? unit->getId() : 0));

					modelIndex = rand() % animations.size();

					//const AnimationAttributes &attributes = animationAttributes[modelIndex];
					//printf("SELECTING RANDOM Model index = %d [%s] model attributes [%d to %d] for unit [%s - %d] with HP = %d\n",modelIndex,animations[modelIndex]->getFileName().c_str(),attributes.fromHp,attributes.toHp,unit->getType()->getName().c_str(),unit->getId(),unit->getHp());
				}
				else {
					Chrono seed(true);
					srand((unsigned int)seed.getCurTicks() + unit->getId());

					int filteredModelIndex = rand() % filteredAnimations.size();
					modelIndex = filteredAnimations[filteredModelIndex];
				}
			}
		}
	}
	if(lastAnimationIndex) {
		if(*lastAnimationIndex != modelIndex) {
			//printf("Switching model from [%s] to [%s]\n",(*lastAnimationIndex >= 0 ? animations[*lastAnimationIndex]->getFileName().c_str() : "none"),animations[modelIndex]->getFileName().c_str());
		}
		*lastAnimationIndex = modelIndex;
	}

	//printf("!!RETURN ANIMATION [%d / %d]\n",modelIndex,animations.size()-1);
	return animations[modelIndex];
}

string SkillType::skillClassToStr(SkillClass skillClass) {
	switch(skillClass){
	case scStop: return "Stop";
	case scMove: return "Move";
	case scAttack: return "Attack";
	case scHarvest: return "Harvest";
	case scRepair: return "Repair";
	case scBuild: return "Build";
	case scDie: return "Die";
	case scBeBuilt: return "Be Built";
	case scProduce: return "Produce";
	case scUpgrade: return "Upgrade";
	case scFogOfWar: return "Fog Of War";
	default:
		assert(false);
		break;
	}
	return "";
}

string SkillType::fieldToStr(Field field) {
	Lang &lang= Lang::getInstance();
	string fieldName = "";
	switch(field) {
		case fLand:
			if(lang.hasString("FieldLand") == true) {
				fieldName = lang.getString("FieldLand");
			}
			else {
				fieldName = "Land";
			}
			//return "Land";
			return lang.getTilesetString("FieldLandName",fieldName.c_str());

		case fAir:
			if(lang.hasString("FieldAir") == true) {
				fieldName = lang.getString("FieldAir");
			}
			else {
				fieldName = "Air";
			}

			//return "Air";
			return lang.getTilesetString("FieldAirName",fieldName.c_str());
		default:
			assert(false);
			break;
	}
	return "";
}

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

//    SkillClass skillClass;
	skillTypeNode->addAttribute("skillClass",intToStr(skillClass), mapTagReplacements);
//	string name;
	skillTypeNode->addAttribute("name",name, mapTagReplacements);
//	int mpCost;
	skillTypeNode->addAttribute("mpCost",intToStr(mpCost), mapTagReplacements);
//	int hpCost;
	skillTypeNode->addAttribute("hpCost",intToStr(hpCost), mapTagReplacements);
//    int speed;
	skillTypeNode->addAttribute("speed",intToStr(speed), mapTagReplacements);
//    int animSpeed;
	skillTypeNode->addAttribute("animSpeed",intToStr(animSpeed), mapTagReplacements);
//    int animationRandomCycleMaxcount;
	skillTypeNode->addAttribute("animationRandomCycleMaxcount",intToStr(animationRandomCycleMaxcount), mapTagReplacements);
//    vector<Model *> animations;
//    vector<AnimationAttributes> animationAttributes;
//
//    SoundContainer sounds;
//	float soundStartTime;
	skillTypeNode->addAttribute("soundStartTime",floatToStr(soundStartTime,6), mapTagReplacements);
//	RandomGen random;
	skillTypeNode->addAttribute("random",intToStr(random.getLastNumber()), mapTagReplacements);
//	AttackBoost attackBoost;
	attackBoost.saveGame(skillTypeNode);
//	static int nextAttackBoostId;
	skillTypeNode->addAttribute("nextAttackBoostId",intToStr(nextAttackBoostId), mapTagReplacements);
//	UnitParticleSystemTypes unitParticleSystemTypes;
	for(UnitParticleSystemTypes::iterator it = unitParticleSystemTypes.begin(); it != unitParticleSystemTypes.end(); ++it) {
		(*it)->saveGame(skillTypeNode);
	}
}

// =====================================================
// 	class StopSkillType
// =====================================================

StopSkillType::StopSkillType(){
    skillClass= scStop;
}

string StopSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Stop";
	}
	return Lang::getInstance().getString("Stop");
}

// =====================================================
// 	class MoveSkillType
// =====================================================

MoveSkillType::MoveSkillType(){
	skillClass= scMove;
}

string MoveSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Move";
	}

	return Lang::getInstance().getString("Move");
}

int MoveSkillType::getTotalSpeed(const TotalUpgrade *totalUpgrade) const{
	int result = speed + totalUpgrade->getMoveSpeed(this);
	result = max(0,result);
	return result;
}

// =====================================================
// 	class AttackSkillType
// =====================================================

//varios
AttackSkillType::AttackSkillType() {
    skillClass= scAttack;
	attackType= NULL;
    projectile= false;
    splash= false;
    splashRadius= 0;
    spawnUnit="";
    spawnUnitcount=0;
	projectileParticleSystemType= NULL;
	splashParticleSystemType= NULL;

	for(int i = 0; i < fieldCount; ++i) {
		attackFields[i]= false;
	}

	attackStrength=0;
	attackVar=0;
	attackRange=0;
	attackStartTime=0;
	splashDamageAll=false;
}

AttackSkillType::~AttackSkillType() {
	delete projectileParticleSystemType;
	projectileParticleSystemType = NULL;

	delete splashParticleSystemType;
	splashParticleSystemType = NULL;
	deleteValues(projSounds.getSounds().begin(), projSounds.getSounds().end());
	projSounds.clearSounds();
}

void AttackSkillType::load(const XmlNode *sn, const XmlNode *attackBoostsNode,
		const string &dir, const TechTree *tt,
		const FactionType *ft, std::map<string,vector<pair<string, string> > > &loadedFileList,
		string parentLoader) {
    SkillType::load(sn, attackBoostsNode,dir, tt, ft, loadedFileList, parentLoader);

	string currentPath = dir;
	endPathWithSlash(currentPath);

	//misc
	std::vector<string> attackStrengthXMLTags;
	attackStrengthXMLTags.push_back("attack-strenght");
	attackStrengthXMLTags.push_back("attack-strength");
	attackStrength= sn->getChildWithAliases(attackStrengthXMLTags)->getAttribute("value")->getIntValue();
    attackVar= sn->getChild("attack-var")->getAttribute("value")->getIntValue();

    if(attackVar < 0) {
        char szBuf[8096]="";
        snprintf(szBuf,8096,"The attack skill has an INVALID attack var value which is < 0 [%d] in file [%s]!",attackVar,dir.c_str());
        throw megaglest_runtime_error(szBuf);
    }

    attackRange= sn->getChild("attack-range")->getAttribute("value")->getIntValue();
	string attackTypeName= sn->getChild("attack-type")->getAttribute("value")->getRestrictedValue();
	attackType= tt->getAttackType(attackTypeName);
	attackStartTime= sn->getChild("attack-start-time")->getAttribute("value")->getFloatValue();

	if (sn->hasChild("unit")) {
		spawnUnit = sn->getChild("unit")->getAttribute("value")->getValue();
		spawnUnitcount
				= sn->getChild("unit")->getAttribute("amount")->getIntValue();
	} else {
		spawnUnit = "";
		spawnUnitcount = 0;
	}
	//attack fields
	const XmlNode *attackFieldsNode= sn->getChild("attack-fields");
	for(int i=0; i < (int)attackFieldsNode->getChildCount(); ++i){
		const XmlNode *fieldNode= attackFieldsNode->getChild("field", i);
		string fieldName= fieldNode->getAttribute("value")->getRestrictedValue();
		if(fieldName=="land"){
			attackFields[fLand]= true;
		}
		else if(fieldName=="air"){
			attackFields[fAir]= true;
		}
		else{
			throw megaglest_runtime_error("Not a valid field: "+fieldName+": "+ dir);
		}
	}

	//projectile
	const XmlNode *projectileNode= sn->getChild("projectile");
	projectile= projectileNode->getAttribute("value")->getBoolValue();
	if(projectile){

		//proj particle
		const XmlNode *particleNode= projectileNode->getChild("particle");
		bool particleEnabled= particleNode->getAttribute("value")->getBoolValue();
		if(particleEnabled){
			string path= particleNode->getAttribute("path")->getRestrictedValue();
			projectileParticleSystemType= new ParticleSystemTypeProjectile();
			projectileParticleSystemType->load(particleNode, dir, currentPath + path,
					&Renderer::getInstance(), loadedFileList, parentLoader,
					tt->getPath());
		}

		//proj sounds
		const XmlNode *soundNode= projectileNode->getChild("sound");
		if(soundNode->getAttribute("enabled")->getBoolValue()){

			projSounds.resize((int)soundNode->getChildCount());
			for(int i=0; i < (int)soundNode->getChildCount(); ++i){
				const XmlNode *soundFileNode= soundNode->getChild("sound-file", i);
				string path= soundFileNode->getAttribute("path")->getRestrictedValue(currentPath, true);
				//printf("\n\n\n\n!@#$ ---> parentLoader [%s] path [%s] nodeValue [%s] i = %d",parentLoader.c_str(),path.c_str(),soundFileNode->getAttribute("path")->getRestrictedValue().c_str(),i);

				StaticSound *sound= new StaticSound();
				sound->load(path);
				loadedFileList[path].push_back(make_pair(parentLoader,soundFileNode->getAttribute("path")->getRestrictedValue()));
				projSounds[i]= sound;
			}
		}
	}

	//splash
	const XmlNode *splashNode= sn->getChild("splash");
	splash= splashNode->getAttribute("value")->getBoolValue();
	if(splash){
		splashRadius= splashNode->getChild("radius")->getAttribute("value")->getIntValue();
		splashDamageAll= splashNode->getChild("damage-all")->getAttribute("value")->getBoolValue();

		//splash particle
		const XmlNode *particleNode= splashNode->getChild("particle");
		bool particleEnabled= particleNode->getAttribute("value")->getBoolValue();
		if(particleEnabled){
			string path= particleNode->getAttribute("path")->getRestrictedValue();
			splashParticleSystemType= new ParticleSystemTypeSplash();
			splashParticleSystemType->load(particleNode, dir, currentPath + path,
					&Renderer::getInstance(),loadedFileList, parentLoader,
					tt->getPath());
		}
	}
}

string AttackSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Attack";
	}

	return Lang::getInstance().getString("Attack");
}

//get totals
int AttackSkillType::getTotalAttackStrength(const TotalUpgrade *totalUpgrade) const{
	int result = attackStrength + totalUpgrade->getAttackStrength(this);
	result = max(0,result);
	return result;
}

int AttackSkillType::getTotalAttackRange(const TotalUpgrade *totalUpgrade) const{
	int result = attackRange + totalUpgrade->getAttackRange(this);
	result = max(0,result);
	return result;
}

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

//    int attackStrength;
	attackSkillTypeNode->addAttribute("attackStrength",intToStr(attackStrength), mapTagReplacements);
//    int attackVar;
	attackSkillTypeNode->addAttribute("attackVar",intToStr(attackVar), mapTagReplacements);
//    int attackRange;
	attackSkillTypeNode->addAttribute("attackRange",intToStr(attackRange), mapTagReplacements);
//	const AttackType *attackType;
	if(attackType != NULL) {
		attackSkillTypeNode->addAttribute("attackType",attackType->getName(false), mapTagReplacements);
	}
//	bool attackFields[fieldCount];
	for(unsigned int i = 0; i < fieldCount; ++i) {
		XmlNode *attackFieldsNode = attackSkillTypeNode->addChild("attackFields");
		attackFieldsNode->addAttribute("key",intToStr(i), mapTagReplacements);
		attackFieldsNode->addAttribute("value",intToStr(attackFields[i]), mapTagReplacements);
	}
//	float attackStartTime;
	attackSkillTypeNode->addAttribute("attackStartTime",floatToStr(attackStartTime,6), mapTagReplacements);
//	string spawnUnit;
	attackSkillTypeNode->addAttribute("spawnUnit",spawnUnit, mapTagReplacements);
//	int spawnUnitcount;
	attackSkillTypeNode->addAttribute("spawnUnitcount",intToStr(spawnUnitcount), mapTagReplacements);
//    bool projectile;
	attackSkillTypeNode->addAttribute("projectile",intToStr(projectile), mapTagReplacements);
//    ParticleSystemTypeProjectile* projectileParticleSystemType;
	if(projectileParticleSystemType != NULL) {
		projectileParticleSystemType->saveGame(attackSkillTypeNode);
	}
//	SoundContainer projSounds;
//
//    bool splash;
	attackSkillTypeNode->addAttribute("splash",intToStr(splash), mapTagReplacements);
//    int splashRadius;
	attackSkillTypeNode->addAttribute("splashRadius",intToStr(splashRadius), mapTagReplacements);
//    bool splashDamageAll;
	attackSkillTypeNode->addAttribute("splashDamageAll",intToStr(splashDamageAll), mapTagReplacements);
//    ParticleSystemTypeSplash* splashParticleSystemType;
	if(splashParticleSystemType != NULL) {
		splashParticleSystemType->saveGame(attackSkillTypeNode);
	}
}
// =====================================================
// 	class BuildSkillType
// =====================================================

BuildSkillType::BuildSkillType(){
    skillClass= scBuild;
}

string BuildSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Build";
	}

	return Lang::getInstance().getString("Build");
}

// =====================================================
// 	class HarvestSkillType
// =====================================================

HarvestSkillType::HarvestSkillType(){
    skillClass= scHarvest;
}

string HarvestSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Harvest";
	}

	return Lang::getInstance().getString("Harvest");
}

// =====================================================
// 	class RepairSkillType
// =====================================================

RepairSkillType::RepairSkillType(){
    skillClass= scRepair;
}

string RepairSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Repair";
	}

	return Lang::getInstance().getString("Repair");
}

// =====================================================
// 	class ProduceSkillType
// =====================================================

ProduceSkillType::ProduceSkillType(){
    skillClass= scProduce;
    animProgressBound=false;
}

void ProduceSkillType::load(const XmlNode *sn, const XmlNode *attackBoostsNode,
		const string &dir, const TechTree *tt,
		const FactionType *ft, std::map<string,vector<pair<string, string> > > &loadedFileList,
		string parentLoader) {
	SkillType::load(sn, attackBoostsNode,dir, tt, ft, loadedFileList, parentLoader);

	if(sn->hasChild("anim-progress-bound")){
		animProgressBound= sn->getChild("anim-progress-bound")->getAttribute("value")->getBoolValue();
	}
	else {
		animProgressBound=false;
	}
}


string ProduceSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Produce";
	}

	return Lang::getInstance().getString("Produce");
}

int ProduceSkillType::getTotalSpeed(const TotalUpgrade *totalUpgrade) const{
	int result = speed + totalUpgrade->getProdSpeed(this);
	result = max(0,result);
	return result;
}

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

	produceSkillTypeNode->addAttribute("animProgressBound",intToStr(animProgressBound), mapTagReplacements);
}
// =====================================================
// 	class UpgradeSkillType
// =====================================================

UpgradeSkillType::UpgradeSkillType(){
    skillClass= scUpgrade;
    animProgressBound=false;
}

void UpgradeSkillType::load(const XmlNode *sn, const XmlNode *attackBoostsNode,
		const string &dir, const TechTree *tt,
		const FactionType *ft, std::map<string,vector<pair<string, string> > > &loadedFileList,
		string parentLoader) {
	SkillType::load(sn, attackBoostsNode,dir, tt, ft, loadedFileList, parentLoader);

	if(sn->hasChild("anim-progress-bound")){
		animProgressBound= sn->getChild("anim-progress-bound")->getAttribute("value")->getBoolValue();
	}
	else {
		animProgressBound=false;
	}
}

string UpgradeSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Upgrade";
	}

	return Lang::getInstance().getString("Upgrade");
}

int UpgradeSkillType::getTotalSpeed(const TotalUpgrade *totalUpgrade) const{
	int result = speed + totalUpgrade->getProdSpeed(this);
	result = max(0,result);
	return result;
}

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

	upgradeSkillTypeNode->addAttribute("animProgressBound",intToStr(animProgressBound), mapTagReplacements);
}

// =====================================================
// 	class BeBuiltSkillType
// =====================================================

BeBuiltSkillType::BeBuiltSkillType(){
    skillClass= scBeBuilt;
    animProgressBound=false;
}

void BeBuiltSkillType::load(const XmlNode *sn, const XmlNode *attackBoostsNode,
		const string &dir, const TechTree *tt,
		const FactionType *ft, std::map<string,vector<pair<string, string> > > &loadedFileList,
		string parentLoader) {
	SkillType::load(sn, attackBoostsNode,dir, tt, ft, loadedFileList, parentLoader);

	if(sn->hasChild("anim-progress-bound")){
		animProgressBound= sn->getChild("anim-progress-bound")->getAttribute("value")->getBoolValue();
	}
	else if(sn->hasChild("anim-hp-bound")){ // deprecated!!!! remove it when you see it after 15th July 2011
		animProgressBound= sn->getChild("anim-hp-bound")->getAttribute("value")->getBoolValue();
	}
	else {
		animProgressBound=false;
	}
}


string BeBuiltSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Be built";
	}

	return "Be built";
}

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

	beBuiltSkillTypeNode->addAttribute("animProgressBound",intToStr(animProgressBound), mapTagReplacements);
}

// =====================================================
// 	class MorphSkillType
// =====================================================

MorphSkillType::MorphSkillType(){
    skillClass= scMorph;
    animProgressBound=false;
}

void MorphSkillType::load(const XmlNode *sn, const XmlNode *attackBoostsNode,
		const string &dir, const TechTree *tt,
		const FactionType *ft, std::map<string,vector<pair<string, string> > > &loadedFileList,
		string parentLoader) {
	SkillType::load(sn, attackBoostsNode,dir, tt, ft, loadedFileList, parentLoader);

	if(sn->hasChild("anim-progress-bound")){
		animProgressBound= sn->getChild("anim-progress-bound")->getAttribute("value")->getBoolValue();
	}
	else {
		animProgressBound=false;
	}
}

string MorphSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Morph";
	}

	return "Morph";
}

int MorphSkillType::getTotalSpeed(const TotalUpgrade *totalUpgrade) const{
	int result = speed + totalUpgrade->getProdSpeed(this);
	result = max(0,result);
	return result;
}

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

	morphSkillTypeNode->addAttribute("animProgressBound",intToStr(animProgressBound), mapTagReplacements);
}

// =====================================================
// 	class DieSkillType
// =====================================================

DieSkillType::DieSkillType(){
    skillClass= scDie;
    fade=false;
}

void DieSkillType::load(const XmlNode *sn, const XmlNode *attackBoostsNode,
		const string &dir, const TechTree *tt,
		const FactionType *ft, std::map<string,vector<pair<string, string> > > &loadedFileList,
		string parentLoader) {
	SkillType::load(sn, attackBoostsNode,dir, tt, ft, loadedFileList, parentLoader);

	fade= sn->getChild("fade")->getAttribute("value")->getBoolValue();
}

string DieSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "Die";
	}

	return "Die";
}

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

	dieSkillTypeNode->addAttribute("fade",intToStr(fade), mapTagReplacements);
}


// =====================================================
// 	class FogOfWarSkillType
// =====================================================

FogOfWarSkillType::FogOfWarSkillType(){
    skillClass= scFogOfWar;

	fowEnable = false;
	applyToTeam = false;
	durationTime = 0;
}

void FogOfWarSkillType::load(const XmlNode *sn, const XmlNode *attackBoostsNode,
		const string &dir, const TechTree *tt,
		const FactionType *ft, std::map<string,vector<pair<string, string> > > &loadedFileList,
		string parentLoader) {
	SkillType::load(sn, attackBoostsNode,dir, tt, ft, loadedFileList, parentLoader);

	fowEnable = sn->getChild("enable-fog")->getAttribute("value")->getBoolValue();
	applyToTeam = sn->getChild("apply-team")->getAttribute("value")->getBoolValue();
	durationTime = sn->getChild("duration")->getAttribute("value")->getFloatValue();
}

string FogOfWarSkillType::toString(bool translatedValue) const{
	if(translatedValue == false) {
		return "FogOfWar";
	}

	return "FogOfWar";
}

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

	fogSkillTypeNode->addAttribute("enable-fog",intToStr(fowEnable), mapTagReplacements);
	fogSkillTypeNode->addAttribute("apply-team",intToStr(applyToTeam), mapTagReplacements);
	fogSkillTypeNode->addAttribute("duration",floatToStr(durationTime,6), mapTagReplacements);
}

// =====================================================
// 	class SkillTypeFactory
// =====================================================

SkillTypeFactory::SkillTypeFactory(){
	registerClass<StopSkillType>("stop");
	registerClass<MoveSkillType>("move");
	registerClass<AttackSkillType>("attack");
	registerClass<BuildSkillType>("build");
	registerClass<BeBuiltSkillType>("be_built");
	registerClass<HarvestSkillType>("harvest");
	registerClass<RepairSkillType>("repair");
	registerClass<ProduceSkillType>("produce");
	registerClass<UpgradeSkillType>("upgrade");
	registerClass<MorphSkillType>("morph");
	registerClass<DieSkillType>("die");
	registerClass<FogOfWarSkillType>("fog_of_war");
}

SkillTypeFactory &SkillTypeFactory::getInstance(){
	static SkillTypeFactory ctf;
	return ctf;
}

}} //end namespace
