/* No reproduction of contents without permission */

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 ( 'd' == this.charAt( pos ) ) {
			var value = arguments[ argIndex++ ] + 0.00001;
			//output += arguments[ argIndex++ ].toFixed( 0 );
			output += Math.round( value );
			pos++;
		} else if ( 'f' == this.charAt( pos ) ) {
			var value = arguments[ argIndex++ ] + 0.00001;
			//output += Math.floor( value ) + ".";
			//output += ( Math.pow( 10, 1 ) + Math.round( ( value - Math.floor( value ) ) * Math.pow( 10, 1 ) ) + "" ).substr( 1, 1 );
			output += value.toFixed( 1 );
			pos++;
		}
		oldPos = pos;
	}
	output += this.substr( oldPos );
	return output;
}


var character = null;

function getCharacter() {
	return character;
}

function setCharacter( ch ) {
	character = ch;
}

function Character( name, characterClass, level, healthPerLevel, adjustmentFormula ) {
	this.name = name;
	this.level = Math.min( level, maxLevel );
	this.characterClass = characterClass;
	this.adjustmentFormula = adjustmentFormula;
	this.healthPerLevel = healthPerLevel;
	this.attributePoints = (level - 1) * 5;
	this.additionalAttributePoints = 0;
	this.skillPoints = level - 1;
	this.attributes = new Object();
	this.skills = new Object();
	this.minLevelReq = 0;

	this.addAttribute = function( id, attribute ) {
		this.attributes[ id ] = attribute;
	}

	this.getAttribute = function( id ) {
		return this.attributes[ id ];
	}

	this.addSkill = function( id, skill ) {
		this.skills[ id ] = skill;
	}

	this.getSkill = function( id ) {
		return this.skills[ id ];
	}

	this.setLevel = function( level ) {
		level = Math.min( level, maxLevel );
		var delta = level - this.level;
		if ( 0 > delta ) {
			delta = Math.max( delta, -this.skillPoints );
			delta = Math.max( delta, -Math.floor( this.attributePoints / 5 ) );
			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 );
		}
		this.level += delta;
		this.attributePoints += delta * 5;
		this.skillPoints += delta;
	}

	this.setAdditionalAttributePoints = function( value ) {
		value = Math.min( value, maxAdditionalAttributePoints );
		var delta = value - this.additionalAttributePoints;
		if ( 0 > delta ) {
			delta = Math.max( delta, -this.attributePoints );
		}
		this.attributePoints += delta;
		this.additionalAttributePoints += delta;
	}

	this.getHealth = function() {
		var health = this.getAttribute( 'stamina' ).value * 5;
		health += ( this.level - 1 ) * this.healthPerLevel;
		return health;
	}

	this.getHealthRegenRate = function() {
		return 0;
	}

	this.getPower = function() {
		return this.getAttribute( 'willpower' ).value * 2 + 40;
	}

	this.getPowerRechargeRate = function() {
		return this.getAttribute( 'willpower' ).value * 3 + 120;
	}

	this.getCriticalDamage = function() {
		return this.getAttribute( 'accuracy' ).value * 2;
	}

	this.adjustment = function() {
		var cl = this.level;
		return eval( this.adjustmentFormula );
	}
}

function Attribute( id, character, name, defaultValue, description ) {
	this.id = id;
	this.character = character;
	this.name = name;
	this.defaultValue = defaultValue;
	this.value = defaultValue;
	this.description = description;

	this.isIncreasable = function() {
		return this.character.attributePoints > 0;
	}

	this.isDecreasable = function() {
		return this.value > this.defaultValue;
	}

	this.increase = function() {
		if ( this.isIncreasable() ) {
			this.value++;
			this.character.attributePoints--;
		}
	}

	this.decrease = function() {
		if ( this.isDecreasable() ) {
			this.value--;
			this.character.attributePoints++;
		}
	}

	this.setValue = function( newValue ) {
		newValue = Math.max( newValue, this.defaultValue );
		var delta = newValue - this.value;
		if ( delta > 0 ) {
			delta = Math.min( delta, this.character.attributePoints );
		}
		this.value += delta;
		this.character.attributePoints -= delta;
	}
}

function Skill( id, character, row, column, name, group, maxRank, defaultRank, rank ) {
	this.id = id;
	this.character = character;
	this.row = row;
	this.column = column;
	this.name = name;
	this.group = group;
	this.maxRank = maxRank;
	this.defaultRank = defaultRank;
	this.rank = rank;
	this.constantProperties = new Array();
	this.variableProperties = new Array();
	this.requirements = new Array();
	this.dependentSkills = new Array();

	this.addConstantProperty = function( property ) {
		this.constantProperties.push( property );
	}

	this.addVariableProperty = function( property ) {
		this.variableProperties.push( property );
	}

	this.addRequirement = function( requirement ) {
		this.requirements.push( requirement );
	}

	this.addDependentSkill = function( skill ) {
		this.dependentSkills.push( skill );
	}

	this.isIncreasable = function() {
		if ( this.rank >= this.maxRank ) {
			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.isDecreasable = function() {
		if ( this.rank <= this.defaultRank ) {
			return false;
		}
		for ( var i = 0; i < this.dependentSkills.length; i++ ) {
			if ( this.dependentSkills[ i ].rank > 0 ) {
				for ( var j = 0; j < this.dependentSkills[ i ].requirements.length; j++ ) {
					if ( this.dependentSkills[ i ].requirements[ j ] instanceof SkillRequirement ) {
						if ( this.dependentSkills[ i ].requirements[ j ].rank >= this.rank ) {
							return false;
						}
					}
				}
			}
		}
		return true;
	}

	this.increase = function() {
		if ( this.isIncreasable() ) {
			this.rank++;
			this.character.skillPoints--;
			return true;
		}
		return false;
	}

	this.decrease = function() {
		if ( this.isDecreasable() ) {
			this.rank--;
			this.character.skillPoints++;
			return true;
		}
		return false;
	}
}

function Property( skill ) {
	this.skill = skill;
	this.type = "";
	this.output = "";
	this.parseFormula = function( formula ) {
		var parsedFormula = "";
		var oldPos = -1;
		var pos = -1;
		while ( 0 <= ( pos = formula.indexOf( '[', oldPos + 1 ) ) ) {
			parsedFormula = parsedFormula.concat( formula.substring( oldPos + 1, pos ) );
			oldPos = pos;
			pos = formula.indexOf( ']', oldPos + 1 );
			if ( 0 <= pos ) {
				var skillId = formula.substring( oldPos + 1, pos );
				var skill = getCharacter().getSkill( skillId );
				if ( null != skill ) {
					parsedFormula = parsedFormula.concat( skill.rank );
				} else {
					parsedFormula = parsedFormula.concat( '0' );
				}
				oldPos = pos;
			}
		}
		parsedFormula = parsedFormula.concat( formula.substr( oldPos + 1 ) );
		return parsedFormula;
	}
}

function PowerProperty( skill, formula ) {
	Property.call( this, skill );
	this.format = "Power Cost: %f";
	this.formula = formula;
	this.type = "powercost";
	this.output = function( rank ) {
		var sl = rank;
		var cl = this.skill.character.level;
		var a = this.skill.character.adjustment();
		var value = eval( this.parseFormula( formula ) );
		return this.format.format( value );
	}
}

function RangeProperty( skill, formula ) {
	Property.call( this, skill );
	this.format = "This skill's range is %f meters";
	this.formula = formula;
	this.type = "range";
	this.output = function( rank ) {
		var sl = rank;
		var cl = this.skill.character.level;
		var range = eval( this.parseFormula( formula ) );
		return this.format.format( range );
	}
}

function RateProperty( skill, formula ) {
	Property.call( this, skill );
	this.format = "Rate of Use: every %f seconds";
	this.formula = formula;
	this.type = "rateofuse";
	this.output = function( rank ) {
		var sl = rank;
		var cl = this.skill.character.level;
		var rate = eval( this.parseFormula( formula ) );
		return this.format.format( rate );
	}
}

function EffectProperty( skill, text, formula ) {
	Property.call( this, skill );
	this.format = "<em>Effect:</em> %s";
	this.text = text;
	this.formula = formula;
	this.type = "effect";
	this.output = function( rank ) {
		var output = '';
		if ( "undefined" == typeof this.formula ) {
			output = this.text;
		} else {
			var sl = rank;
			var cl = this.skill.character.level;
			var a = this.skill.character.adjustment();
			var hp = this.skill.character.getHealth();
			var ep = this.skill.character.getPower();
			var value;
			var display = false;
			if ( "string" == typeof this.formula ) {
				value = eval( this.parseFormula( this.formula ) );
				if ( 0 != value ) {
					display = true;
				}
			} else {
				value = Array();
				for ( var i = 0; i < this.formula.length; i++ ) {
					value[ i ] = eval( this.parseFormula( this.formula[ i ] ) );
					if ( 0 != value[ i ] ) {
						display = true;
					}
				}
			}
			if ( display ) {
				output = this.text.format( value );
			}
		}
		if ( '' != output ) {
			return this.format.format( output );
		}
		return '';
	}
}

function DescriptionProperty( skill, text, formula ) {
	Property.call( this, skill );
	this.format = "<em>Description:</em> %s";
	this.text = text;
	this.formula = formula;
	this.type = "description";
	this.output = function( rank ) {
		var output = '';
		if ( "undefined" == typeof this.formula ) {
			output = this.text.replace( /%%/g, "%" );
		} else {
			var sl = rank;
			var cl = this.skill.character.level;
			var a = this.skill.character.adjustment();
			var hp = this.skill.character.getHealth();
			var ep = this.skill.character.getPower();
			var value;
			if ( "string" == typeof this.formula ) {
				value = eval( this.parseFormula( this.formula ) );
			} else {
				value = new Array();
				for ( var i = 0; i < this.formula.length; i++ ) {
					value[ i ] = eval( this.parseFormula( this.formula[ i ] ) );
				}
			}
			output = this.text.format( value );
		}
		return this.format.format( output );
	}
}

function Requirement( skill ) {
	this.skill = skill;
	this.complied = true;
}

function LevelRequirement( skill, levels ) {
	Requirement.call( this, skill );
	this.type = "levelrequirement";
	this.format = "Character level too low to upgrade this skill";
	this.rankFormat = "Requires Level: %d";
	this.character = skill.character;
	this.levels = levels;
	this.complied = function() {
		if ( this.skill.rank >= this.skill.maxRank ) {
			return true;
		}
		return character.level >= this.requiredLevelForNextRank();
	}
	this.requiredLevelForThisRank = function() {
		return this.levels[ this.skill.rank - 1 ];
	}
	this.requiredLevelForNextRank = function() {
		return this.levels[ this.skill.rank ];
	}
	this.output = function() {
		if ( this.complied() ) {
			return "";
		} else {
			return this.format;
		}
	}
	this.rankOutput = function() {
		if ( this.complied() ) {
			return "";
		} else {
			return this.rankFormat.format( this.requiredLevelForNextRank() );
		}
	}
}

function SkillRequirement( skill, prevSkill, rank ) {
	Requirement.call( this, skill );
	this.type = "skillrequirement";
	prevSkill.addDependentSkill( skill );
	this.format = "Requires <em>%s</em> Level %d";
	this.prevSkill = prevSkill;
	this.rank = rank;
	this.complied = function() {
		return this.prevSkill.rank >= this.rank;
	}
	this.output = function() {
		if ( this.complied() ) {
			return "";
		} else {
			return this.format.format( this.prevSkill.name, this.rank );
		}
	}
	this.rankOutput = function() {
		return "";
	}
}

function TextRequirement( skill, text ) {
	Requirement.call( this, skill );
	this.type = "textrequirement";
	this.text = text;
	this.complied = function() {
		return true;
	}
	this.output = function() {
		return this.text;
	}
	this.rankOutput = function() {
		return "";
	}
}
