/**
 * Author: Alexander Ljungberg, 2008
 * http://www.norwinter.com
 *
 * Creative Commons Attribution 3.0 License
 */

function setVisible(element, visible) {
  if (visible)
    element.show();
  else
    element.hide();
};

function parseNumber(number, defval, lower, higher) {
	if (isNaN(number))
		number = defval;
	number = Math.max(lower, number);
	number = Math.min(higher, number);
	
	return number;
};

var Konkret = Class.create({
  initialize: function() {
    this.UPDATE_INTERVAL = 3
    this.MAX_HISTORY = 10;
    this.rollHistory = new Array(this.MAX_HISTORY);
    this.rollHistorySize = 0;
    this.rollHistoryLastVisible = 0;
    this.MAX_MEMORY = 10;
    this.rollMemory = new Array(this.MAX_MEMORY);
    this.rollMemorySize = 0;
    this.whatses = new Array();
  
    this.uncountedRolls = 0;
    this.controller = null;
    Event.observe(window, 'load', this.prime.bind(this));
  },

  /**
   Setup the page.
   */
  prime: function() {
    this.controller = new KonkretController();
    new PeriodicalExecuter(this.periodicUpdate.bind(this), this.UPDATE_INTERVAL);
  },
  
  periodicUpdate: function() {
    if (this.uncountedRolls > 0) {
      // Send a roll count increment request
    	new Ajax.Updater({ success: 'rollCount'}, 'increaseAndGetRollCount/', {
        parameters: { 'count': this.uncountedRolls }
      });
      this.uncountedRolls = 0;
    }
  },
  
  doRoll: function() {
    this.uncountedRolls++;

  	oRoll = this.controller.getRoll();
  	$("_label").value = ""; // Prevent accidental overwriting

  	var newContent = "";

  	var i;
  	for (i=0; i<oRoll.repeat; i++) {
  		rollEntry = copyRoll(oRoll);		

  		var rollResult = rollDice(rollEntry);
  		var textResult = this.rollToText(rollResult);
  		newContent += textResult;

  		var ha = new Array(3);
  		ha[0] = textResult;
  		ha[1] = rollEntry.label;
  		Da = new Date();
  		ha[2] = Da.toLocaleTimeString();
  		this.putHistory(ha);
  	}

    $("resultSet").update("<span>"+newContent+"</span>");

  	if (oRoll.label.length>0) {
  		// Create a 'memory' entry.
  		var sa = new MemoryEntry();
  		sa.label = oRoll.label;
  		sa.sides = oRoll.sides;
  		sa.pool = oRoll.pool;
  		sa.modifier = oRoll.modifier;
  		sa.repeat = oRoll.repeat;
  		sa.lowdrop = oRoll.dropLow;
  		sa.summation = oRoll.doSum;
  		sa.lowcap = oRoll.capDown;
  		sa.diff = oRoll.wodDiff;
  		sa.system = oRoll.system;
  		sa.wegIgnore = oRoll.wegIgnore;
  		sa.sumMod = oRoll.sumMod;
  		sa.wodAgain = oRoll.wodAgain;

  		this.putSaved(sa);
  	}
  },
  
  rollToText: function(rollResult) {
    var r = "";

  	// Floats gotta go first.
  	if (rollResult.doSum) {
  	  r += "<span class='sumResult'>";
  		r += "(Sum: "+rollResult.sum+")";
  		r += "</span>";
  	}

  	if (rollResult.wodDiff > 0) {
  	  r += "<span class='systemResult'>";
  		r += "(WOD Result: "+rollResult.wodSuccesses+")";	
  		r += "</span>";
  	}

    r += "<span class='rollDesc'>";

    var dX = "D"+rollResult.sides;

    if (rollResult.modifier != 0)
      dX = "(" + dX + signify(rollResult.modifier) + ")";

  	r += rollResult.initialPool + "x"+dX;

    if (rollResult.sumMod != 0)
      r += signify(rollResult.sumMod);
      
  	r += ": </span>";

  	var i;
  	for (i=0; i<rollResult.pool; i++) {
  		if ((rollResult.system==1 || rollResult.system == 3) && i>=rollResult.initialPool) {
  		  r += "+"
  		}
  		var s = rollResult.diceValues[i];
  		var dieTxt = s + " ";
  		var className = '';
  		if(rollResult.weg && i >= rollResult.wegWildDie) {
  		  className = 'wegWild';
  			// If the Wild Die was dropped, put it in ().
  			if (rollResult.diceDropped[i])
  				dieTxt = "("+s+") ";
  		} else if (rollResult.diceDropped[i])  { // Dropped value
  		  className = 'lowestDropped';
  			dieTxt = "("+s+") ";
  		}  else if(rollResult.diceWodType[i] == 1) {	// Botch	
  		  className = 'wodBotch';
  		}  else if(rollResult.diceWodType[i] == 2) { // Success
  			if (rollResult.wodAgainDice[i])
  		    className = 'wodSuccessAgain';	  
  			else
  		    className = 'wodSuccess';	  
  		} else if (rollResult.wodAgainDice[i]) {
  	      className = 'wodAgain';	  
  		}

  		r += "<span class="+className+">"+dieTxt+"</span>";
  	}
  	return r;
  },
  
  putHistory: function(rollResult) {
  	if (this.rollHistorySize<this.MAX_HISTORY) {
  		this.rollHistory[this.rollHistorySize++] = rollResult;
  	} else {
  		var i;
  		for (i=0; i<this.MAX_HISTORY-1; i++) {
  			this.rollHistory[i] = this.rollHistory[i+1];
  		}
  		this.rollHistory[this.MAX_HISTORY-1] = rollResult;
  	}
  	this.updateHistoryScreen();
  },
  
  updateHistoryScreen: function() {
  	r = "<div>";
  	var i;
  	for (i=0; i<this.rollHistorySize; i++) {
  	  r += "<div class='aResult' id='history"+i+"'>";
  	  r += "<span class='historyTime'>";
      r += "("+this.rollHistory[i][2]+") </span>";
  		if (this.rollHistory[i][1].length>0) {
  		  r += "<span class='historyLabel'>";
        r += '"'+this.rollHistory[i][1]+'"';
        r += "</span>";
  		}
  		r += this.rollHistory[i][0];
  		r += "</div>"
  	}
    r += "</div>";

    $("historySet").update(r);
    
    if (this.rollHistorySize == this.MAX_HISTORY) {
      // Remove the topmost item.
      //Effect.SlideUp('history0', {duration: 0.5 });
      //Effect.SlideDown('history'+(this.MAX_HISTORY-1), {duration: 0.5 });
      Effect.SlideUp('history0', {duration: 0.5 });
      Effect.Appear('history'+(this.MAX_HISTORY-1), {duration: 0.5 });
    } else if (this.rollHistoryLastVisible < this.rollHistorySize) {
      var newElement = $('history'+(this.rollHistorySize-1));
      newElement.hide();
      Effect.Appear(newElement, { duration: 0.5 });
    }
    this.rollHistoryLastVisible = this.rollHistorySize;
  },
  
  putSaved: function(roll) {
  	// Remove it if its in the list already.
  	var i;
  	for (i=0; i<this.rollMemorySize; i++) {
  		if (this.rollMemory[i].label == roll.label) {
  			var j;
  			for (j=i; j<this.rollMemorySize-1; j++) {
  				this.rollMemory[j] = this.rollMemory[j+1];
  			}
  			this.rollMemorySize--;
  			break;
  		}
  	}

  	// Put it at the top.
  	this.rollMemorySize = Math.min(this.rollMemorySize+1, this.MAX_MEMORY);
  	for (i=Math.min(this.rollMemorySize, this.MAX_MEMORY); i>=1; i--) {
  		this.rollMemory[i] = this.rollMemory[i-1];
  	}
  	this.rollMemory[0] = roll;

  	this.updateSavedScreen();
  },
  
  updateSavedScreen: function() {
    r = '<ul>'
  	for(i=0; i<this.rollMemorySize; i++) {
      r += '<li><a href="#" onClick="konkret.doRestoreAndRoll('+i+')">' + this.rollMemory[i].label + '</a></li>';      
  	}	    
    r += '</ul>'
    $$('#savedRolls ul')[0].replace(r);
    //Effect.BlindDown('savedRolls', { duration: 0.7, scaleX: true, scaleY: true });    
  },

  doRestoreAndRoll: function(rollIndex) {
    var r = this.rollMemory[rollIndex];

    this.controller.setRoll(r)

  	this.doRoll();

    this.controller.setLabel(''); // Prevent accidental overwriting
  }
});

var Roll = Class.create({
  initialize: function(nSides, nPool) { 
  	this.sides = nSides;
  	// Remember this in case the system modifies it
  	this.initialPool = nPool;
  	this.pool = nPool;
  	this.diceValues = new Array(nPool);
  	this.diceDropped = new Array(nPool);

  	this.sum = 0;

  	this.diceWodType = new Array(nPool);
  	this.wodSuccesses = 0;
  	this.newWod = false;
  	this.wodAgain = new Array();
  	this.wodAgainDice = new Array(nPool);
  	this.wegWildDie = this.pool-1;
  	this.wegDropHigh = false;
  	this.wegIgnore = false;  
  }
});

var KonkretController = Class.create({
  initialize: function() {
    this.updateSystemOptions(false);
    
    $('_systemType').observe('change', this.updateSystemOptions.bind(this));   
    $('_doRoll').observe('click', konkret.doRoll.bind(konkret)); 
    Event.observe(document, 'keypress', this.handleKeypress.bind(this));
  },
  
  setRoll: function(roll) {
  	$("_dieType").value = roll.sides;
  	$("_pool").value = roll.pool;
  	$("_repeat").value = roll.repeat;
  	$("_lowdrop").checked = roll.lowdrop;
  	$("_lowcap").checked = roll.lowcap;
  	$("_summation").checked = roll.summation;
  	$("_wodDiff").value = (roll.diff>0?roll.diff:6);
  	$("_systemType").value = roll.system;
  	$("_wegIgnore").checked = roll.wegIgnore;
  	$("_modType").value = (roll.modifier != 0?"1":"0");
  	$("_modifier").value = (roll.modifier != 0?roll.modifier:roll.sumMod);
  	
  	for(i=0; i<roll.wodAgain.length; i++) {
  		rep = roll.wodAgain[i];
  		if (roll.system == 3) {
  			base = "_nwod";
  		} else {
  			base = "_owod";
  		}
  		$(base+rep).checked = true;
  	}

    this.updateSystemOptions(false);
  },
  
  /**
   * Read the current UI settings and create a Roll based on it.
   */
  getRoll: function () {
  	var sides = parseNumber($("_dieType").value, 10, 1, 100);
  	var pool = parseNumber($("_pool").value, 10, 1, 99);
  	var modType = parseNumber($("_modType").value, 0, 0, 1);
  	var modifier = 0;
  	var sumMod = 0;
  	if (modType == 1)
  		modifier = parseNumber($("_modifier").value, 0, -10000, +10000);
  	else
  		sumMod = parseNumber($("_modifier").value, 0, -10000, +10000);
  	var lowdrop = $("_lowdrop").checked;
  	var summation = $("_summation").checked;
  	var lowcap = $("_lowcap").checked;
  	var system = parseNumber($("_systemType").value, 0, 0, 3);
  	var repeat = parseNumber($("_repeat").value, 1, 1, 10);
  	var label = $("_label").value;

  	var wod = false; var newWod = false;
  	var diff = 0;
  	var wodAgain = new Array();
  	if (system == 1) {
  		wod = true;
  		var wi = 0;
  		if ($("_owod10").checked)
  			wodAgain[wi++] = 10;
  		if ($("_owod9").checked)
  			wodAgain[wi++] = 9;
  		if ($("_owod8").checked)
  			wodAgain[wi++] = 8;		
  	}
  	if (system == 3) {
  		wod = true; newWod = true;
  		diff = 8; // Always 8 in the new system.

  		var wi = 0;
  		if ($("_nwod10").checked)
  			wodAgain[wi++] = 10;
  		if ($("_nwod9").checked)
  			wodAgain[wi++] = 9;
  		if ($("_nwod8").checked)
  			wodAgain[wi++] = 8;
  	}
  	var weg = false;
  	if (system == 2)
  		weg = true;

  	if (wod && !newWod)
  		diff = parseNumber($("_wodDiff").value, 6, 1, 10);

  	var wegIgnore = false;
  	if (weg)
  		wegIgnore = $("_wegIgnore").checked;

  	var rollEntry = new Roll(sides, pool);
  	rollEntry.modifier = modifier;
  	rollEntry.sumMod = sumMod;
  	rollEntry.dropLow = lowdrop;
  	rollEntry.doSum = summation;
  	rollEntry.wodDiff = diff;
  	rollEntry.newWod = newWod;
  	rollEntry.wodAgain = wodAgain;
  	rollEntry.weg = weg;
  	rollEntry.wegIgnore = wegIgnore;
  	rollEntry.capDown = lowcap;
  	rollEntry.repeat = repeat;
  	rollEntry.label = label;
  	rollEntry.system = system;

  	return rollEntry;
  },
  
  setLabel: function(label) {
    $("_label").value = label;
  },
  
  getActiveSystem: function() {
    return parseNumber($("_systemType").value, 0, 0, 3);
  },
    
  updateSystemOptions: function(nonAutomatic) {
  	sel = this.getActiveSystem();
    
    var sysContainer = $('systemParamContainer');
    if (sel == 0) {
      // None system.
      if (nonAutomatic)
        Effect.Fade(sysContainer, { duration: 0.5 });
      else {
        sysContainer.hide()
      }
    } else {
    	setVisible($("systemParamNone"), sel==0);
    	setVisible($("systemParamWod"), sel==1);
    	setVisible($("systemParamWeg"), sel==2);
    	setVisible($("systemParamNWod"), sel==3);
    	if (!sysContainer.visible())
        Effect.Appear(sysContainer, { duration: 0.5 });
    }

  	if (nonAutomatic) {
  		// Default to a D10 for WOD.
  		if (sel == 1 || sel == 3) {
  			$("_dieType").value = 10; 
  			$("_summation").checked = false; 
  			$("_lowdrop").checked = false; 
  		}

  		// If we're playing WEG, default to a D6, and turn on sums.
  		if (sel == 2) {
  			$("_dieType").value = 6; 
  			$("_summation").checked = true; 
  		}
  	}
  },
  
  handleKeypress: function(e) { 
  	var code;
  	if (!e) var e = window.event;
  	if (e.keyCode) code = e.keyCode;
  	else if (e.which) code = e.which;
  	var character = String.fromCharCode(code);
  	
  	switch(code) {
  	  case 13: // Return
  	  $('_doRoll').click();
  	  break;
  	  default:
  	  //console.log('Ignored keypress ' + character + ' (code: '+code+')')
  	  return false;
  	}
  	
  	e.doit = false;
  }
});

function signify(number) {
  if (number > 0)
    return "+"+number;
  return number;
}

function nWodSel(which) {
	if (which & 1) {
		d.getElementById("_nwod10").checked = true;
	}
	if (which & 2) {
		d.getElementById("_nwod9").checked = true;
	}
}

function oWodSel(which) {
	if (which & 1) {
		d.getElementById("_owod10").checked = true;
	}
	if (which & 2) {
		d.getElementById("_owod9").checked = true;
	}
}

function toggleWhats(which) {
	whatses[which] = !whatses[which];
	if (whatses[which])
		showWhats(which);
	else
		hideWhats(which);
}

function showWhats(which) {
	b = d.getElementById("whats"+which);
	b.style.display="block";
}

function hideWhats(which) {
	b = d.getElementById("whats"+which);
	b.style.display="none";
}

function randInt(lower, higher) {
	var r = Math.random()*(higher-lower+1);
	return Math.floor(r+lower);
}


function rollDice(result) {
	var i;
	for(i=0; i<result.pool; i++) {
		result.diceValues[i] = randInt(1, result.sides)+result.modifier;
		if (result.capDown && result.diceValues[i]<1)
			result.diceValues[i] = 1;
		if (!result.wegIgnore && result.weg && i+1 == result.pool) {
			// Do the wild die
			if (i == result.wegWildDie && result.diceValues[i] == 1) {
				// Drop the wild and the highest
				result.wegDropHigh = true;
				result.diceDropped[i] = true;
			} else if (result.diceValues[i] == 6) {
				result.pool++;
				firstWild = false;
			}
		}
		
		for(j=0; j<result.wodAgain.length; j++) {
			if (result.diceValues[i] == result.wodAgain[j]) {
				result.pool++;
				result.wodAgainDice[i] = true;
			}
		}
	}
	
	if (result.dropLow) {
		var smallInd = 0;
		for(i=result.pool; i>0; i--) {
			if (result.diceValues[i]<result.diceValues[smallInd])
				smallInd = i;
		}
		result.diceDropped[smallInd] = true;
	}
	
	if (result.wegDropHigh) {
		var bigInd = 0;
		for(i=result.pool; i>0; i--) {
			if (i == result.wegWildDie)
				continue; // Don't wanna drop the Wild Die twice.
			if (result.diceValues[i]>result.diceValues[bigInd])
				bigInd = i;
		}
		result.diceDropped[bigInd] = true;
	}
	
	if (result.doSum) {
		for(i=0; i<result.pool; i++) {
			if (!((result.dropLow || result.wegDropHigh) && result.diceDropped[i]))
				result.sum += result.diceValues[i];
		}
	}
	
	result.sum += result.sumMod;
	
	if (result.wodDiff>0) {
		for(i=0; i<result.pool; i++) {
			if (result.dropLow && result.diceDropped[i])
				continue; // Kinda weird to drop lowest for WOD. But who knows.
			if (!result.newWod && result.diceValues[i] < 2) { // Botch
				result.wodSuccesses--;
				result.diceWodType[i] = 1;
			} else if (result.diceValues[i]>=result.wodDiff) { // Success
				result.wodSuccesses++;
				result.diceWodType[i] = 2;
			} else { // Miss
				result.diceWodType[i] = 0;
			}
		}
	}
	return result;
}

function MemoryEntry() {
}


// Helper method for cloning dice rolls
function cloneObject(what) {
    for (i in what) {
        this[i] = what[i];
    }
}

function copyRoll(r) {
	var rollEntry = new Roll(r.sides, r.pool);
	rollEntry.modifier = r.modifier;
	rollEntry.dropLow = r.dropLow;
	rollEntry.doSum = r.doSum;
	rollEntry.wodDiff = r.wodDiff;
	rollEntry.newWod = r.newWod;
	rollEntry.wodAgain = r.wodAgain;
	rollEntry.weg = r.weg;
	rollEntry.wegIgnore = r.wegIgnore;
	rollEntry.capDown = r.capDown;
	rollEntry.repeat = r.repeat;
	rollEntry.label = r.label;
	rollEntry.system = r.system;
	rollEntry.sumMod = r.sumMod;

	return rollEntry;
}

konkret = new Konkret();
