var character = null;
var debug = false;

function isInteger(s) {
	return (s.toString().search(/^-?[0-9]+$/) == 0);
}

function isHex(s) {
	return (s.toString().search(/^-?[0-9a-f]+$/) == 0);
}

function getCharacter() {
	return character;
}

function setCharacter( ch ) {
	character = ch;
}

String.prototype.format = function() {
	if ( ! arguments || arguments.length < 1 ) {
		return this;
	}
	if ( arguments[ 0 ] instanceof Array ) {
		arguments = arguments[ 0 ];
	}
	var output = "";
	var argIndex = 0;
	var pos = 0;
	var oldPos = 0;
	while ( 0 <= ( pos = this.indexOf( "%", pos ) ) ) {
		if ( pos != oldPos ) {
			output += this.substring( oldPos, pos );
		}
		pos++;
		if ( '%' == this.charAt( pos ) ) {
			output += '%';
			pos++;
		} else if ( 's' == this.charAt( pos ) ) {
			output += arguments[ argIndex++ ];
			pos++;
		} else if ( 'i' == this.charAt( pos ) ) {
			output += signedNumber( arguments[ argIndex++ ] );
			pos++;
		} else if ( 'u' == this.charAt( pos ) ) {
			output += arguments[ argIndex++ ];
			pos++;
		} else if ( 't' == this.charAt( pos ) ) {
			output += formatTime( arguments[ argIndex++ ] );
			pos++;
		}
		oldPos = pos;
	}
	output += this.substr( oldPos );
	return output;
}

function signedNumber( n ) {
	return n>0?"+"+n:n;
}

function formatTime( time ) {
	var units = null;
	var isToggle = false;
	var isInstant = false;
	var rawTime = 0;
	var output = "";
	if (isInteger(time)) {
		rawTime = time;
		isToggle = time < 0;
		isInstant = time == 0;
		units = 's';
	} else {
		units = time.charAt(time.length - 1);
		rawTime = parseInt(time);
	}
	if (isToggle || isInstant ) {
		output = ((time < 0)?"Toggle":"Instant");
	} else {
		if( rawTime >= 60 ) {
			higherTime = Math.floor(rawTime / 60);
			higherUnits = units=='s'?'m':'h';
			output += formatTime( higherTime + higherUnits ) + " ";
			rawTime = rawTime % 60;
		}
		if( rawTime > 0) {
			output += rawTime + " ";
			switch (units) {
			case 's':
				output += "second";
				break;
			case 'm':
				output += "minute";
				break;
			case 'h':
				output += "hour";
				break;
			}
			output += rawTime>1?"s":"";
		}
	}
	return output;
}

var groupSkills = { "pirate_RepelGrapples2" : true, "navy_BattleDoctrine" : true };
function Character( characterClass, level, odd ) {
	this.odd = odd;
	this.level = Math.min( level, maxLevel );

	this.characterClass = characterClass;
	this.name = classes[characterClass];
	this.skillPoints = Math.floor((this.level + odd)/2);
	this.skills = new Object();
	this.skillGroups = new Array();
	this.activeSkills = new Object();
	this.activeGroupSkills = new Object();
	this.minLevelReq = 0;
	this.morale = 100;

	this.addSkill = function( skill ) {
		this.skills[ skill.id ] = skill;
	}

	this.getSkill = function( id ) {
		return this.skills[ id ];
	}

	this.resetActiveSkills = function () {
		this.activeSkills = new Object();
		this.activeGroupSkills = new Object();
		this.morale = 100;
		for( id in this.skills )
			if ( this.skills[ id ].passive && this.skills[ id ].selected() )
				this.toggleActiveSkill( id );
	}

	this.isActive = function( id ) {
		return (this.activeSkills[ id ] != null);
	}

	this.isGroupActive = function( id ) {
		return (this.activeGroupSkills[ id ] != null);
	}

	this.calculateEffects = function() {
		var effects = calculateEffects( this.activeSkills );
		addGroupEffects( effects, this.activeGroupSkills )
		return effects;
	}
	
	this.deactivateCooldown = function( cooldownname ) {
		for ( id in this.activeSkills ) {
			var skill = this.activeSkills[id];
			if ( skill != null ) {
				var cooldown = skill.getProperty( "tt_cooldownCategory" );
				if( cooldown != null && cooldown.modifier == cooldownname)
					this.toggleActiveSkill( id );
			}
		}
	}

	this.toggleActiveSkill = function( id ) {
		var output = "";
		var skill = this.skills[ id ];
		if( skill.selected() && this.activeSkills[ id ] == null && this.activeGroupSkills[ id ] == null) {
			if(skill.activationcandidate ) {
				var cooldown = skill.getProperty( "tt_cooldownCategory" );
				if( cooldown != null )
					this.deactivateCooldown( cooldown.modifier );
				var moraleCost = 0;
				for( var i = 0; i < skill.properties.length; i++ )
					if( skill.properties[ i ].property == "max_energy" )
						moraleCost += parseInt( skill.properties[ i ].modifier )
				if( (-1 * moraleCost) <= this.morale ) {
					this.morale += moraleCost;
					this.activeSkills[ id ] = skill;
				} else {
					output += "Insufficient morale. ";
				}
			} else {
				output += "Not a valid toggle skill. ";
			}
		} else if ( skill.selected() && this.activeSkills[ id ] != null) {
			if( groupSkills[id] ) {
				this.activeSkills[ id ] = null;
				this.activeGroupSkills[ id ] = skill;
				output += "Stacked skill activated. ";
			} else {
				var moraleCost = 0;
				if( skill.passive ) {
					output += "You cannot disable a passive skill. ";
				} else {
					for( var i = 0; i < skill.properties.length; i++ )
						if( skill.properties[ i ].property == "max_energy" )
							moraleCost += parseInt( skill.properties[ i ].modifier )
					if( moraleCost < 0 || this.morale >= moraleCost ) {
						this.morale -= moraleCost;
						this.activeSkills[ id ] = null;
					} else {
						output += "Your morale would drop below 0. ";
					}
				}
			}
		} else if ( skill.selected() && this.activeGroupSkills[ id ] != null) {
			var moraleCost = 0;
			for( var i = 0; i < skill.properties.length; i++ )
				if( skill.properties[ i ].property == "max_energy" )
					moraleCost += parseInt( skill.properties[ i ].modifier )
			if( moraleCost < 0 || this.morale >= moraleCost ) {
				this.morale -= moraleCost;
				this.activeGroupSkills[ id ] = null;
			} else {
				output += "Your morale would drop below 0. ";
			}
		}
		return output;
	}

	this.addSkillGroup = function( skillgroup ) {
		skillgroup.character = this;
		this.skillGroups.push(skillgroup);
		for(var i = 0; i < skillgroup.skills.length; i++) {
			addskill(skillgroup.skill[i]);
		}
	}

	this.setLevel = function( level ) {
		level = Math.min( level, maxLevel );
		var delta = level - this.level;
		if ( delta < 0 ) {
			delta = Math.max( delta, -(this.skillPoints * 2) );
			var maxLevelReq = 0;
			for ( id in this.skills ) {
				if ( this.skills[ id ].rank > this.skills[ id ].defaultRank ) {
					for ( var i = 0; i < this.skills[ id ].requirements.length; i++ ) {
						if ( this.skills[ id ].requirements[ i ] instanceof LevelRequirement ) {
							var levelReq = this.skills[ id ].requirements[ i ].requiredLevelForThisRank();
							if ( levelReq > maxLevelReq ) {
								maxLevelReq = levelReq;
							}
						}
					}
				}
			}
			delta = Math.max( delta, maxLevelReq - this.level );
		}
		var spDelta = 0;
		spDelta -= Math.floor((this.level + this.odd)/2);
		this.level += delta;
		spDelta += Math.floor((this.level + this.odd)/2);
		this.skillPoints += spDelta;
	}
	
	this.calcToChar = function() {
		var pos = 1;
		var state = 0;
		for(var i = 0; i < this.skillGroups.length; i++)
			for(var j = 0; j < this.skillGroups[i].skills.length; j++) {
				var skill = this.skillGroups[i].skills[j];
				if( skill.selected() && skill.activationcandidate && !skill.passive ) {
					if( groupSkills[this.skillGroups[i].skills[j].id] ) {
						if ( this.activeGroupSkills[ this.skillGroups[i].skills[j].id ] != null )
							state |= pos;
						pos <<= 1;
					}
					if ( this.activeSkills[ this.skillGroups[i].skills[j].id ] != null )
						state |= pos;
					pos <<= 1;
				}
			}
		return state.toString(16);
	}

	this.calcFromChar = function( statechars ) {
		var pos = 1;
		var state = parseInt(statechars, 16);
		for(var i = 0; i < this.skillGroups.length; i++)
			for(var j = 0; j < this.skillGroups[i].skills.length; j++) {
				var skill = this.skillGroups[i].skills[j];
				if( skill.selected() && skill.activationcandidate && !skill.passive ) {
					if( groupSkills[this.skillGroups[i].skills[j].id] ) {
						if ( state & pos ) {
							this.toggleActiveSkill( this.skillGroups[i].skills[j].id );
							this.toggleActiveSkill( this.skillGroups[i].skills[j].id );
						}
						pos <<= 1;
					}
					if ( state & pos )
						this.toggleActiveSkill( this.skillGroups[i].skills[j].id );
					pos <<= 1;
				}
			}
	}
}

var ignoreFx = { "tt_morale" : true, "tt_cooldownCategory" : true, "max_energy" : true, 'tt_target' : true };
function addRawEffects( effects, propertylist, skill ) {
		for ( var i = 0; i <  propertylist.length; i++ ) {
			var property = propertylist[i];
			var effect = effects[ property.property ];
			if( property.property.indexOf( "tt_DURATION" ) >= 0 ) {
				//do we want to calculate build duration?
			}
			else if ( property.property =="tt_target" && property.modifier == "Group" )
				addRawEffects( effects, skill.getTargetPropertyGroup(), skill );
			else if ( ignoreFx[ property.property ] ) 
				;//ignore
			else if ( effect == null )
				effects[ property.property ] = new Property(null, property.property, property.modifier);
			else {
				if( isInteger(effect.modifier) ) {
					effect.modifier += property.modifier;
					effect.resetOutput();
				} else throw "NOT AN INTEGER: " + effect.modifier + " in " + effect.property;  
			}
		}
	return effects;
}

function addRawGroupEffects( effects, propertylist ) {
		for ( var i = 0; i <  propertylist.length; i++ ) {
			var property = propertylist[i];
			var effect = effects[ property.property ];
			if ( ignoreFx[ property.property ] ) 
				;//ignore
			else if ( effect == null ) {
				if( isInteger(property.modifier) )
					effects[ property.property ] = new Property(null, property.property, 6 * property.modifier )				
				else throw "NOT AN INTEGER: " + effect.modifier + " in " + effect.property;  
			}
			else {
				if( isInteger(effect.modifier) ) {
					effect.modifier += 6 * property.modifier;
					effect.resetOutput();
				} else throw "NOT AN INTEGER: " + effect.modifier + " in " + effect.property;  
			}
		}
	return effects;
}

function addGroupEffects( effects, skilllist ) {
	for ( skillId in skilllist )
		if ( skilllist[skillId] != null)
			addRawGroupEffects( effects, skilllist[skillId].getTargetPropertyGroup() );
	return effects;
}

function addEffects( effects, skilllist ) {
	for ( skillId in skilllist )
		if ( skilllist[skillId] != null)
			addRawEffects( effects, skilllist[skillId].properties, skilllist[skillId] );
	return effects;
}

function calculateEffects( skilllist ) {
	var effects = new Object();
	return addEffects(effects, skilllist);
}

function SkillGroup( id, row, name, sequential ) {
	this.id = id;
	this.row = row;
	this.name = name;
	this.skills = new Array();
	this.character = null;
	this.sequential = sequential;
	this.sequence = "";
	
	this.asChar = function() {
		if ( sequential ) {
			var c = 0;
			for( var i = 0; i < this.skills.length; i++ ) {
				if( this.skills[i].selected() )
					c++;
				else
					break;
			}
			return c;
		} else {
			var c = 0;
			for( var i = 0; i < this.sequence.length; i++ )
				if( this.skills[parseInt(this.sequence.charAt(i))].selected() )
					c += 1 << i;
			return c.toString(16); 
		}
	}
	
	this.fromChar = function( c ) {
		if ( this.sequential ) {
			var topSkill = parseInt(c);
			if( topSkill > this.skills.length )
				topSkill = this.skills.length;
			for(var i = 0; i < topSkill; i++)
				toggleSkill(this.skills[i].id);
		} else {
			var bits = parseInt(c, 16); 
			for( var i = 0; i < this.sequence.length; i++ )
				if( ((1 << i) & bits) > 0 )
					toggleSkill(this.skills[ parseInt(this.sequence.charAt(i)) ].id);
		}
	}
	
	this.addSkill = function( skill ) {
		if( this.sequence.length >= 4)
			throw "Only up to 4 skills supported in out-of-order groups.";
		if( !sequential && skill.canSelect() )
			this.sequence += this.skills.length;
		this.skills.push( skill );
		if( null != character)
			character.addSkill(skill);
		skill.group = this;
	}
}

var invalidTargets = { "Enemy" : true, "Derelict" : true, "Ally" : true };
function Skill( id, character, type, name, description ) {
    this.id = id;
    this.character = character;
	this.name = name;
	this.type = type;
	this.description = description;
	this.properties = new Array();
	this.propertyGroups = new Array();
	this.requirements = new Array();
	this.dependentSkills = new Array();
	this._selected = false;
	this.group = null;
	this.passive = false;
	this.activationcandidate = true;

	
	this.addProperty = function( property ) {
		this.properties.push( property );
		if( property.property == "tt_target" && invalidTargets[property.modifier] )
			this.activationcandidate = false;
	}

	this.addPropertyGroup = function( group ) {
		this.propertyGroups.push( group );
	}

	this.addRequirement = function( requirement ) {
		this.requirements.push( requirement );
		if( requirement.type == "passiverequirement" ) {
			this.passive = true;
		}
	}

	this.addDependentSkill = function( skill ) {
		this.dependentSkills.push( skill );
	}
	
	this.getProperty = function( propname ) {
		for ( var i = 0; i < this.properties.length; i++ )
			if ( this.properties[ i ].property == propname )
				return this.properties[ i ];
		return null;
	}

	this.getTargetPropertyGroup = function() {
		for ( var i = 0; i < this.propertyGroups.length; i++ )
			if ( this.propertyGroups[ i ] instanceof Fx_targetPropertyGroup )
				return this.propertyGroups[ i ].properties;
		alert("returning null");
		return null;
	}

	this.selected = function() {
		if( type == "BASE" )
			return true;
		else
			return this._selected;
	}
	
	this.select = function() {
		this._selected = true; 
	}
	
	this.deselect = function() {
		this._selected = false; 
	}
	
	this.canSelect = function() {
		if ( type == "BASE" )
			return false;
		if ( this._selected )
			return false;
		if ( 0 >= this.character.skillPoints )
			return false;
		for ( var i = 0; i < this.requirements.length; i++ ) {
			if ( ! this.requirements[ i ].complied() ) {
				return false;
			}
		}
		return true;
	}

	this.canDeselect = function() {
		if ( type == "BASE" )
			return false;
		if ( ! this._selected )
			return false;
		for ( var i = 0; i < this.dependentSkills.length; i++ ) {
			if ( this.dependentSkills[ i ]._selected ) {
				return false;
			}
		}
		return true;
	}

}

function Property( skill, property, modifier ) {
	this.property = property;
	this.modifier = modifier;
	this.output;
	
	this.resetOutput = function() {
		this.output = propertyMap[this.property].format(this.modifier);
	}
	this.resetOutput();
}

function PropertyGroup( skill, description ) {
	this.skill = skill;
	this.type = "group";
	this.description = description;
	this.properties = new Array();
	this.output = description + ":";
	this.addProperty = function( property ) {
		this.properties.push( property );
	}
}

function Fx_selfPropertyGroup( skill ) {
	PropertyGroup.call( this, skill, 'Self Effects' );
}

function Fx_targetPropertyGroup( skill ) {
	PropertyGroup.call( this, skill, 'Target Effects' );
}

function Requirement( skill ) {
	this.skill = skill;
}

function FlsReqLevel_GERequirement( skill, level ) {
	Requirement.call( this, skill );
	this.type = "levelrequirement";
	this.output = "You must be level " + level;
	this.character = skill.character;
	this.level = level;
	this.complied = function() {
		return character.level >= this.level;
	}
}

function SkillRequirement( skill, prevSkill ) {
	Requirement.call( this, skill );
	this.type = "skillrequirement";
	prevSkill.addDependentSkill( skill );
	this.format = "Requires <em>%s</em> Level %d";
	this.prevSkill = prevSkill;
	this.output = "Skills: " + prevSkill.name;

	this.complied = function() {
		return this.prevSkill.selected();
	}
}

function TextRequirement( skill, text ) {
	Requirement.call( this, skill );
	this.type = "textrequirement";
	this.text = text;
	this.output = text;
	this.complied = function() {
		return true;
	}
}

function Passive_skillRequirement( skill, dummy ) {
	Requirement.call( this, skill );
	this.type = "passiverequirement";
	this.output = "Passive Skill";
	this.complied = function() {
		return true;
	}
}

function Reset_timerRequirement( skill, reset ) {
	Requirement.call( this, skill );
	this.output = "Reset timer: %t".format(reset);
	this.type = "reset";
	this.complied = function() {
		return true;
	}
}



