/***
|''Name:''|DiceRollParser |
|''Description:''|Dice roll parser. |
|''Version:''|0.1 |
|''Date:''|2008.08.19 |
|''Source:''|http://www.draugrheim.net/rpgwiki/rpgwiki.html |
|''Status''|stable |
|''Author:''|[[Martin Andersen|mailto:draugen@draugrheim.net]] |
|''Contributors''|[[Devon Jones|http://www.legolas.org/gmwiki/dev/gmwikidev.html]] |
|''Type:''|plugin |
|''License:''|GPL |
|''CoreVersion:''|2.4 |
!Description
Dice roll parser library. Used by DiceRollMacro.
!Notes
!Usage
{{{new DiceRollMacro(expression)}}}
!Revision history
!!0.1 - 2208-08-19
Initial release. Complete rewrite/refactor of original code.
!Code
***/

function DiceRollParser(exp, prefix) {
	this.exp = exp;
	this.prefix = prefix ? prefix : "+";
	this.subexps = [];
	this.result = 0;
	this.rolls = [];
	this.dice = [];
	this.parse(this.exp);
	this.roll();
}
DiceRollParser.prototype.parse = function expParse(exp) {
	var tests = {
		// TODO: Add success/botch/exploding parsing
		//       (for Shadowrun/ White Wolf)
		//       and high/low collation
		// 			 idea: http://www.asmor.com/programs/dicechucker/
		// regexes contain extra parenthesises for pattern matching later
		parens: /\((.*)\)/, // (<anything>)
		parensDice: /\((.*)\)[dD]([\dFf+])/,
		checkLast: /^.+([\+\-])(\d+)$/, //<any string>+|-<number>
		check: /^([\+\-])(\d+)$/, // +|-<number>
		dc: /^[Dd][Cc](\d+)$/, // DC|Dc|dc|dC<number>
		num: /^\d+$/, // <number>
		complex: /(\d*)([dD])([\dfF]+)([\+\-]?)(.*)/, //<number>d|D[<number|f|F][+|-]<anything>
		dice: /(\d*)([dD])([\dfF]+)/ //<number>d|D[<number>|f|F]
	};
	if (tests.checkLast.test(exp)) {
		var checkLastExp = tests.checkLast.exec(exp);
		this.mod = checkLastExp[2];
		if (checkLastExp[1] === "-") {
			this.mod = this.mod * -1;
		}
		this.parse(exp.replace(/[\+\-]\d+$/, ""));
	}
	else if (tests.check.test(exp)) {// roll +|-x
		var checkExp = tests.check.exec(exp);
		this.mod = checkExp[2];
		this.dice.push([new Dice()]);
		if (checkExp[1] === "-") {
			this.mod = this.mod * -1;
		}
	}
	else 	if (tests.dc.test(exp)) {
		var dcExp = tests.dc.exec(exp);
		this.dc = dcExp[1];
		this.dice.push([new Dice()]);
	}
	else if (tests.parens.test(exp)) {
			// extract expression from parenthesies and evaluate
			var parensExp = tests.parens.exec(exp);
			this.subexp = new DiceRollParser(parensExp[1]);
			// re-parse original expression with the evaluated result of the parenthesied expression
			this.parse(exp.replace(tests.parens, this.subexp.result));
	}
	else if (tests.complex.test(exp)) {
		var complexExp = tests.complex.exec(exp);
		var count = complexExp[1];
		var sides = complexExp[3];
		var prefix = complexExp[4];
		var subexp = complexExp[5];
		this.dice.push([new Dice(count, new Die(sides))]);
		if (tests.num.test(subexp)) {
			this.mod = subexp;
			if (prefix === "-") {
				this.mod = this.mod * -1;
			}
		}
		else if (tests.dice.test(subexp)) {
			diceExp = tests.dice.exec(subexp);
			count = diceExp[1];
			sides = diceExp[3];
			this.dice.push([new Dice(count, new Die(sides)), prefix]);
		}
		else {
			this.parse(subexp);
		}
	}
};

DiceRollParser.prototype.roll = function () {
	if (this.dice.length) {
		for (var i in this.dice) {
			if (this.dice.hasOwnProperty(i)) {
				var dice = this.dice[i][0];
				var prefix = this.dice[i][1];
				if (prefix === "-") {
					this.result -= dice.rolls.total;
					for (var j = 0; j < dice.rolls.length; j++) {
						var roll = dice.rolls[j];
						this.rolls.push("(" + roll * -1 + ")");
					}
				}
				else {
					this.result += dice.rolls.total;
					for (var k = 0; k < dice.rolls.length; k++) {
						roll = dice.rolls[k];
						this.rolls.push(roll);
					}
				}
			}
		}
	}
	if (this.subexp) {
		//var subexpDice = new RegExp("\\(" + this.subexp.exp + "\\)[dD]\\dFf+");
		var subexpDice = /\(.*\)[dD][\dFf]+/;
		if (!subexpDice.test(this.exp)) {
			this.result += this.subexp.result;
		}
	}
	//add modifier
	this.result += this.mod ? parseInt(this.mod, 10) : 0;
	//subtract DC, if applicable
	this.result -= this.dc ? parseInt(this.dc, 10) : 0;
	return this.result;
};

DiceRollParser.prototype.toString = function () {
	var isNegNum = /^\-\d+/;
	var s = this.rolls.length ? this.rolls.join(' + ') + "" : "";
	if (isNegNum.test(this.mod)) {
		s += this.mod ? (s ? " + (" + this.mod + ")": "(" + this.mod + ")" ) : "";
	}
	else {	
		s += this.mod ? (s ? " + " + this.mod : this.mod ) : "";
	}
	s += this.dc ? " - " + this.dc : "";
	if (this.subexp) {
		var subexpDice = /\(.*\)[dD][\dFf]+/;
		if (!subexpDice.test(this.exp)) {
			s += " " + this.subexp.prefix + " (" + this.subexp.toString() + ")";
		}
	}
	s += " = " + this.result;
	return s;
};

DiceRollParser.prototype.toHtmlString = function () {
	var s = "<dl>";
	s += "<dt>Result</dd>";
	s += "<dd c>" + this.toString() + "</dd>";
	if (this.dice.length) {
		s += "<dt>Dice</dd>";
		for (i in this.dice) {
			if(this.dice.hasOwnProperty(i)) {
				s += "<dd>" + this.dice[i][0].toString() +"</dd>";
			}
		}
	}
	if (this.dc) {
		s += "<dt>DC</dt>";
		s += "<dd>" + this.dc + "</dd>";
	}
	if (this.mod) {
		s += "<dt>Modifier</dt>";
		s += "<dd>" + this.mod + "</dd>";
	}
	if (this.subexp) {
		s += "<dt>Sub-expression</dt>";
		s += "<dd>" + this.subexp.toHtmlString() + "</dd>";
	}
	s += "<dt>Expression</dt>";
	s += "<dd>" + this.exp + "</dd>";
	s += "</dl>";
	return s;
}