
var stylePath = '../../../style/gray/';

var unitCount = 0; // kind of an unique id for units.

var allUnits = []; // flat representation of all units.


// -- first some generic functions used later

/**
 * Assert implementation from http://aymanh.com/9-javascript-tips-you-may-not-know
 */
function AssertException(message) { this.message = message; }

AssertException.prototype.toString = function () {
	return 'AssertException: ' + this.message;
}

function assert(exp, message) {
	if (!exp) {
		throw new AssertException(message);
	}
}

/** Returns translated texts for unit.js
 *  Actually this is only a placeholder for the real implementation in common.js
 */
function i18n(text) {
  return text;
}

/** 
 *  isArray(): function from prototype
 *  We can't use prototype on the command line because it needs
 *  a window reference
 */
function isArray(object) {
	return (object != null && typeof object == "object" && 'splice' in object && 'join' in object);
}

// Validates a number as being properly numeric for our purposes (positive integer value)
function isNumeric(n) {
	return !isNaN(parseFloat(n)) && isFinite(n) && (n > 0) && (n.indexOf('.') < 0);
}

/**
 * Escapes quotes in the string with the html equivalent
 * Currently quotes are the only really anoying thing in strings
 */
String.prototype.escapeSpecial = function() {
	return this.replace(/"/g, '&#x22;').replace(/'/g, '&#x27;');
};

/**
 * Returns one random element from the array
 */
Array.prototype.random = function() {
	return this[ Math.floor( Math.random()*this.length ) ];
};

/**
 * Does a deep copy of the array
 */
Array.prototype.deepCopy = function() {
	var newArray = this.clone();
	
	for( var i = 0; i<newArray.length; i++ ) {
		if (newArray != null && newArray[i] instanceof Array) {
			newArray[i] = newArray[i].clone();
		}
	}
	
	return newArray;
};



/**
 * Get the textual description of all units
 */
function getUnitText() {
	return $('all_units').value;
}

/**
 * Set the textual description of all units
 */
function setUnitText( value ) {
	$('all_units').value = value;
}

/** Returns the unit with the given number 
 *  This function is needed to get a unit object from html.
 */
function getUnit(num) {
	for (var i = 0; i < allUnits.length; i++) {
		if (allUnits[i].number == num) {
			return allUnits[i];
		}
	}
	
	return null;
}



// --- the unit class

/** Creates a new unit
 *  The new unit is pushed to the main unit.
 *  @param mainUnit The unit this unit belongs to. If mainUnit is null then the newly created unit is the base unit.
 *  @param type The type of the new unit. If null then the first valid subUnit will be picked.
 */
function Unit(mainUnit, type) {
	this.number = unitCount;
	this.mainUnit = mainUnit; // The unit I belong to
	
	// If we have a main unit then it needs to have some kind of definition
	assert(!this.mainUnit || this.mainUnit.definition);
	
	// If we have a main unit then we also need to have subDefinitions
	if (this.mainUnit && !this.mainUnit.definition.subDefinitions) {
		return;
	}
	
	// Set the type (and find one if not explicitely defined)
	// NB: the base unit does not have a type
	this.type = type;
	if (mainUnit && (!type || !mainUnit.definition.subDefinitions[type])) {
		for (var unitType in mainUnit.definition.subDefinitions) {
			if (mainUnit.definition.subDefinitions[unitType].name) {
				this.type = unitType;
				break;
			}
		}
	}
	
	// a new base unit
	if (mainUnit == null) {
		this.definition = window.baseDefinition;
	} else {
		this.definition = mainUnit.definition.subDefinitions[this.type];
	}
	
	this.count		= this.definition.minsize ? this.definition.minsize : 1;
	this.width		= 5;
	this.name		= "";
	this.options	= this.definition.defaultOptions ? eval(uneval(this.definition.defaultOptions)) : {};
	this.slot		= ""; // Lord, Hero, Core, Special, Rare
	this.extraText	= "";
	this.icon		= ""; // in case we have a single model you can change the icon
	this.subUnits	= []; // unit array for sub-units
	this.extraPoints = 0;
	this.commentsText = "";
	
	if (mainUnit != null) {
		mainUnit.subUnits.push(this); // add me to my main unit
	}
	
	allUnits.push(this);
	unitCount++;
}
  
/** Copy constructor */
Unit.prototype.clone = function() {
	var unit = new Unit(this.mainUnit, this.type);
	unit.count = this.count;
	unit.width = this.width;
	unit.name = this.name;
	unit.unitType = this.unitType;
	
	// -- copy the options
	unit.options = {};
	
	this.forAllSelectedOptions(
		function(option, count) { 
			unit.options[option[0]] = count;
		}
	);
	
	unit.extraText = this.extraText;
	unit.extraPoints = this.extraPoints;
	unit.commentsText = this.commentsText;
	unit.icon = this.icon;
	
	// -- now the subunits
	for (var i = 0; i < this.subUnits.length; i++) {
		unit.subUnits.push(this.subUnits[i].clone());
		this.subUnits.pop().mainUnit = unit; // and re-link the sub unit
	}
	
	return unit;
};
  
/** remove this unit also from the main unit*/
Unit.prototype.remove = function() {
	if (this.mainUnit == null) {
		return; // don't remove the base unit
	}
	
	while (this.subUnits.length > 0) {
		this.subUnits[0].remove();
	}
	
	this.mainUnit.subUnits = this.mainUnit.subUnits.without(this);
	allUnits = allUnits.without(this);
};
  
/** Return the keys of sub-units for this unit*/
Unit.prototype.getUnits = function() {
	res = [];
	
	for (var unitType in this.definition.subDefinitions) {
		var unitStruct = this.definition.subDefinitions[unitType];
		if (unitStruct.name) {
			res.push(unitType);
		}
	}
	
	return res;
}

/** calls the given action-function for all options 
 * The function gets the option as one parameter.
 */
Unit.prototype.forAllOptions = function(action, unitOptions) {
	if (unitOptions == null) {
		unitOptions = this.definition.options;
	}
	
	if (unitOptions == null)
		return;
	
	for (var i = 0; i < unitOptions.length; i++) {
		if (unitOptions[i]) {
			// if the first element does contain an array then it's not an option
			if (isArray(unitOptions[i][0])) {
				this.forAllOptions(action, unitOptions[i]);
			} else {
				action(unitOptions[i], this.options[unitOptions[i][0]]);
			}
		}
	}
};

/** calls the given action-function for all selected options 
 * The function gets the option as one parameter.
 */
Unit.prototype.forAllSelectedOptions = function(action, unitOptions) {
	if (unitOptions == null) {
		unitOptions = this.definition.options;
	}

	if (unitOptions == null)
		return;

  this.forAllOptions( function(option, count) {
                      if (count > 0)
                        action(option, count);
                      }, unitOptions );
};
  
/** Returns the options struct with the given name.
 *  Recursive searches through the list of unitOptions for the name
 *  Idea: construct a lookup hash if too slow.
 */
Unit.prototype.getOptionsStruct = function(name, unitOptions) {
	if (unitOptions == null && !(unitOptions = this.definition.options))
		return null;
	
	for (var i = 0; i < unitOptions.length; i++) {
		if (unitOptions[i]) {
			// if the first element does contain an array then it's not an option
			if (isArray(unitOptions[i][0])) {
				var res = this.getOptionsStruct(name, unitOptions[i]);
				if (res) {
					return res;
				}
			} else {
				if (unitOptions[i][0] == name) {
					return unitOptions[i];
				}
			}
		}
	}
	
	return null;
};

Unit.prototype.toString = function() {
	var res = "";
	if (this.mainUnit != null) {
		res += '[unit]' + this.number + ',' + this.type + ',' +
		this.count + ',' + this.width + ',' + this.name + ',';
		
		// --- output the options
		this.forAllSelectedOptions(
			function(option, count) {
				res += option[0] + '=' + count + '.';
			}
		);
		
		// --- output the extras
		res += ',' + this.extraText + ',' + this.extraPoints + ',' + this.commentsText + ',' + this.icon + ',' + this.mainUnit.number;
		res += "[/unit]";
	}
	
	// --- output the sub-units
	for (var i = 0; i < this.subUnits.length; i++) {
		res += "\n" + this.subUnits[i];
	}
	
	return res;
};
  
/** Adds units contained in the given text */
Unit.prototype.addFromText = function(text) {
	var unitExp = new RegExp('\\[unit\\](.*?)\\[/unit\\]', "g");
	var match;
	
	while ((match = unitExp.exec(text))) {
		var unitText = match[1];
		var values = unitText.split(",");
		
		if (values.length > 7) {
			var mainUnit = getUnit(parseInt(values[10]));
			
			if (!mainUnit) {
				mainUnit = this;
			}
			
			var unit = new Unit( mainUnit, values[1]);
			unit.number = parseInt(values[0]);
			
			if (unitCount <= unit.number) { // ensure that we don't reuse a number
				unitCount = unit.number + 1;
			}
			
			unit.count = parseInt(values[2]);
			unit.width = parseInt(values[3]);
			unit.name = values[4];
			unit.options = {};
			
			var options = values[5].split('.');
			
			for (var i = 0; i < options.length; i++) {
				var options2 = options[i].split('=');
				
				if (options2.length == 2) {
					unit.options[options2[0]] = parseInt( options2[1]);
				}
			}
			
			unit.extraText = values[6];
			unit.extraPoints = parseInt(values[7]);
			unit.commentsText = values[8];
			unit.icon = values[9];
			// number 10 is the parent object;
		}
	}
};
  
/** Updates the textual representation in the give text string.
 *  Tries to replace an existing unit text representation. 
 *  If not found then it just adds it at the end.
 */
function updateUnitText(unit) {
	 // --- remove the old subunit texts
	for (var i = 0; i < unit.subUnits.length; i++) {
		removeUnitText(unit.subUnits[i]);
	}
	
	var text = getUnitText();
	var unitExp = new RegExp('\\[unit\\]' + unit.number + ',.*?\\[/unit\\]');
	
	if (unitExp.test(text)) {
		setUnitText(text.replace(unitExp, unit.toString()));
	} else {
		setUnitText(text + unit.toString());
	}
}

function removeUnitText(unit) {
	// --- remove the old subunit texts
	for (var i = 0; i < unit.subUnits.length; i++) {
		removeUnitText(unit.subUnits[i]);
	}
	
	setUnitText(getUnitText().replace(new RegExp('\\[unit\\]' + unit.number + ',.*?\\[/unit\\]\\s*'), ''));
}

function addUnitText(unit, beforeUnit) {
	var text = getUnitText();
	
	if (beforeUnit == null) {
		text = text + '\n' + unit.toString();
	} else {
		var oldUnitReg = new RegExp('(\\[unit\\]' + beforeUnit.number + ',.*?\\[/unit\\])');
			
		if (!oldUnitReg.test(text)) {
			text = unit.toString() + '\n' + text;
		} else {
			text = text.replace(oldUnitReg, unit.toString() + '\n$1');
		}
	}
	
	setUnitText(text);
}

Unit.prototype.getId = function() {
	return "unit" + this.number;
};
  
/** Returns the number of points used by magic items.
 *  Magic items are options that start with a "m"
 */
Unit.prototype.getMagicPoints = function() {
	// --- calc the option points
	var optionPoints = 0;
	this.forAllSelectedOptions(
		function(option, count) {
			if (option[0][0] == 'm') {
				optionPoints += count * option[2];
			}
		}
	);
	 
	return optionPoints;
};
  
/**
 *  Calculate the total unit point cost and returns it.
 *  Items starting with "i" are multiplied by the number of models.
 */
Unit.prototype.getTotalPoints = function() {
	// --- calc the option points
	var count = this.count;
	var optionPoints = 0;
	
	this.forAllSelectedOptions(
		function(option, itemCount) {
			var points = itemCount * option[2];
			if (option[0].charAt(0) == 'i') {
				points *= count;
			}
		
			optionPoints += points;
		}
	);
	
	// -- now the subunits
	var subUnitPoints = 0;
	
	for (var i = 0; i < this.subUnits.length; i++) {
		subUnitPoints += this.subUnits[i].getTotalPoints();
	}
	
	return ((this.definition.points ? this.definition.points : 0) + subUnitPoints) * this.count + optionPoints + this.extraPoints;
};

Unit.prototype.getText = function() {
	
	var text = '';
	
	if (this.definition.basicText) {
		text = this.definition.basicText;
	}
	
	this.forAllSelectedOptions(
		function(option, count) { 
			if (text != '') {
				text += ', ';
			}
		
			if (count == 1) {
				text += option[1];
			} else {
				text += count + 'x ' + option[1];
			}
		}
	);
	
	if (this.extraText != '') {
		if (text != '') {
			text += '<br>';
		}
		
		text += this.extraText;
	}
		
	 return text;
};

Unit.prototype.removeAction = function() {
	Effect.BlindUp(this.getId());
	
	$(this.getId()).innerHTML = "";
	removeUnitText(this, getUnitText());
	this.remove();
	this.updatePoints();
	
	return false;
};

Unit.prototype.cloneAction = function() {
	var newUnit = this.clone();
	
	$(this.getId()).insert({ 'before': newUnit.toHTML() });
	addUnitText(newUnit, this);
	this.updatePoints();
	
	return false;
};
  
/** adds a unit at the end of this unit */
Unit.prototype.newUnitAction = function() {
	var newUnit = new Unit(this);
	var marker = $(this.getId() + '_end');
	
	marker.insert({ 'before': newUnit.toHTML(2) });
	addUnitText(newUnit, null);
	this.updatePoints();
	
	// -- create a new sub-unit if needed
	if (newUnit.definition.subDefinitions) {
		newUnit.newUnitAction();
	}
	
	return false;
};

Unit.prototype.editAction = function() {
	$(this.getId()).replace(this.toHTML(2));
	
	return false;
}

Unit.prototype.uneditAction = function() {
	$(this.getId()).replace(this.toHTML());
	
	return false;
}

Unit.prototype.upAction = function() {
	var node = $(this.getId());
	var parent = node.parentNode;
	
	// -- get the previous unit node
	var previous = node;
	do {
		previous = previous.previousSibling;
		if (previous == null)
			return false;
	} while (previous.className != 'unit');
	
	parent.removeChild(node);
	parent.insertBefore(node, previous);
	
	 // -- update text
	// just switch the texts around
	setUnitText(getUnitText().replace(
		new RegExp(
			'(\\[unit\\].*?\\[/unit\\])' +
			'([^\[]*)' +
			'(\\[unit\\]' + this.number + ',.*?\\[/unit\\])', 'm'
		),
		'$3$2$1'));
	 return false;
}

Unit.prototype.downAction = function() {
	var node = $(this.getId());
	var parent = node.parentNode;
	
	// -- get the next unit node
	var next = node;
	do {
		next = next.nextSibling;
		if (next == null)
			return false;
	} while (next.className != 'unit');
	
	parent.removeChild(next);
	parent.insertBefore(next, node);
	
	// -- update text
	setUnitText(getUnitText().replace(
		new RegExp(
			'(\\[unit\\]' + this.number + ',.*?\\[/unit\\])' +
			'([^\[]*)' +
			'(\\[unit\\].*?\\[/unit\\])', 'm'
		), 
		'$3$2$1'));
	return false;
}

Unit.prototype.increaseWidthAction = function() {
	this.setWidth(this.width + 1);
	return false;
};

Unit.prototype.decreaseWidthAction = function() {
	this.setWidth(this.width - 1);
	return false;
};

Unit.prototype.addOptionIcons = function(array, optionName) {
	// -- find the options struct and icons
	var optionsStruct = this.getOptionsStruct(optionName);
	if (optionsStruct == null)
		return;
	
	var optionOptions = optionsStruct[3];
	if (optionOptions != null && optionOptions.icons != null) {
		array.push(optionOptions.icons);
	}
};

Unit.prototype.makeIconsDiv = function(edit) {
	var unitStruct = this.definition;
	var unitIcons = unitStruct.icons;
	var positions = {
		front: [],
		middle: [],
		back: [],
		bottom: [],
		top: [],
		left: [],
		right: []
	};
	
	 // lay out the images in different regions
	if (unitStruct.deployment != null) {
		positions = unitStruct.deployment(this, positions);
	} else {
		positions = normalDeployment(this, positions);
	}
	
	var mainIcons = positions.front.concat(positions.middle, positions.back);
	
	// -- ok, no icons. We just skip the table
	if (mainIcons.length == 0)
		return "<span class='icons' id='" + this.getId() + "_icons'></span>\n";
	
	var result = "<table class='icons' id='" + this.getId() + "_icons'>\n";
	
	if (mainIcons.length == 1) {
		result += '<tr><td><img src="' + iconFolder + mainIcons[0].random() + '"></td></tr>\n';
		if (edit) {
			// -- edit button
			// result += '<br><input type="image" src="'+stylePath+'chooseButton.svg.png" alt="choose" onclick="return getUnit('+this.number+').chooseIconAction();" title="Choose icon">\n';
		}
	} else {
		var maxRow = Math.ceil(mainIcons.length / this.width);
		// -- icons in the middle
		// this special counter counts from front to back 
		// starting in the middle front rank with 0
		var count;
    var column;
		
		for (var row = 0; row < maxRow; row++) {
			result += '<tr>\n';
			// -- icons to the left
			if (row == 0) {
				for (column = 0; column < positions.left.length; column++) {
					result += '<td rowspan="' + maxRow + '">\n';
					result += '<img src="' + iconFolder + positions.left[column].random() + '">';
					result += '</td>\n';
				}
			}
			 // -- adjust the width value
			if (this.width > mainIcons.length) {
				this.width = mainIcons.length;
			}
			
			// -- finally output the column
		 	for (column = 0; column < this.width; column++) {
				 // -- calc the count number (Black magic)
				var halfWidth = Math.floor(this.width / 2);
				
				if (column < halfWidth) {
					count = (halfWidth-column) * 2 - 1;
				} else {
					count = (column - halfWidth) * 2;
				}
			 	
				count += (maxRow - row - 1) * this.width;
				
				// -- finally add the icon
				result += '<td>';
				if (count >= 0 && count < mainIcons.length && mainIcons[count]) {
					result += '<img src="' + iconFolder + mainIcons[count].random() + '">';
				}
				
				result += '</td>\n';
			}
			 // -- icons to the right
			if (row == 0) {
				for (column = 0; column < positions.right.length; column++) {
					result += '	<tr rowspan="' + maxRow + '">\n';
					result += '<img src="' + iconFolder + positions.right[column].random() + '">';
					result += '</tr>\n';
				}
			}
			result += '</tr>\n';
		}
	
		if (edit) {
			// -- smaller/wider buttons
			if (mainIcons.length > 1) {
				result += '<tr><td style="text-align: center;" colspan="' + (this.width + positions.left.length + positions.right.length) + '">\n' +
				(this.width > 1 ? '<input type="image" src="' + stylePath + 'narrowButton.svg.png" alt="smaller" onclick="return getUnit(' + this.number + ').decreaseWidthAction();" title="Decrease unit width">\n' : '') +
				(this.width < mainIcons.length ? '<input type="image" src="' + stylePath + 'wideButton.svg.png" alt="wider" onclick="return getUnit(' + this.number + ').increaseWidthAction();" title="Increase unit width">\n' : '') +
				'</td></tr>\n';
			}
		}
	}
	
	result += '</table>\n';
	
	return result;
};
  
/** Creates the unit select tag with all the different options available for the current army 
 */
Unit.prototype.makeUnitSelect = function() {
	if (this.mainUnit == null)
		return '';
	
	var result = '<select id="' + this.getId() + '_type" name="' + this.getId() + '_type" ' +
		'onchange="return getUnit(' + this.number + ').setType(this.value);" />';
	
	var allDefs = this.mainUnit.definition.subDefinitions;
	
	for (var unitType in allDefs) {
		var unitStruct = allDefs[unitType];
	
		if (unitStruct.name) {
			result += '<option value="' + unitType + '" ' +
				((this.type == unitType) ? 'selected="true" ' : '') +
				'>' + unitStruct.name.escapeSpecial() + '</option>\n';
		}
	}
	
	result += '</select>\n';
	return result;
};

Unit.prototype.makeOptionsTextDiv = function() {
	var text = this.getText();
	
	if (text.length == 0) {
		return "";
	} else {
		return "<div id='" + this.getId() + "_options_text' class='options'>" + text + "</div>\n";
	}
};
  
/** Returns the options box for the given unitType 
 *  Uses the unit_types array to output the html code for a unit box. 
 */
Unit.prototype.makeOptionsBoxDiv = function() {
	var unitStruct = this.definition;
	var unitOptions = unitStruct.options;
	var defaultOptions = {};
	var id = this.getId();
	
	if (!unitOptions || unitOptions.length == 0) {
		return "<div id='" + id + "_options'></div>\n";
	}
		var result = "<div id='" + id + "_options' class='options'>\n";
	if (unitOptions) {
		// Cut off trailing empty columns
		if (unitOptions.length>0 && unitOptions[unitOptions.length-1] == null) {
			unitOptions.pop();
		}
	
		result += '<table><tr>\n';
	
		// maxTextLen --> 130
		var maxTextLen = 175 / unitOptions.length - 11;
	
    // --- output all the options for the unit.
    // that might be number input fields, radio or check buttons
		for (var column = 0; column < unitOptions.length; column++) {
			result += '<td>\n';
			for (var row = 0; row < unitOptions[column].length; row++) {
				var option = unitOptions[column][row];
				if (option) {
					// -- check if we have some option options
					var optionOptions = option[3];
					if (!optionOptions) {
						optionOptions = defaultOptions;
					}

          // - get the disabled value
          // disabled may be a function in wich case we call it with 
          // the current unit and use the return value
          var disabled = false;
          if (optionOptions.disabled) {
            if (Object.isFunction(optionOptions.disabled))
              disabled = optionOptions.disabled(this);
            else
              disabled = optionOptions.disabled;
          }
				
          // - generic tags
					// truncate the text if too long
					var optText = option[1];
					if (optText && optText.length > maxTextLen) {
						optText = optText.substring(0, maxTextLen - 1) + '&hellip;';
					}
          var optId = 'id="' + id + '_' + option[0] + '" ';
          var optName = 'name="' + option[0] + '" ';
          var optTitle = 'title="' + option[1].escapeSpecial() + ' (' + option[2] + ')" ';
          var optChecked = (this.options[option[0]] ? 'checked="checked" ' : '');
          var optDisabled = (disabled ? 'disabled="'+disabled+'" ' : '');
				
					// -- number input field
					if (optionOptions.maxCount) {
						result += '<input type="text" ' +
              optId + optName + optTitle + 
							'value="' + (this.options[option[0]] ? this.options[option[0]] : 0) + '" ' +
							'size=1 ' +
							'onchange="return getUnit(' + this.number + ').setOption(\'' + option[0] + '\', this.value);" />';

          // -- radio buttons
					} else if (optionOptions.oneOf) {
						result += '<input type="radio" ' +
              optId + '" name="' + optionOptions.oneOf + id + '" ' +
              optTitle + ' value="'+option[0]+'" ' + optChecked + optDisabled +
							'onclick="return getUnit(' + this.number + ').setOption(\'' + option[0] + '\', this.checked?1:0);" />';

          // -- check buttons
					} else {
						result += '<input type="checkbox" ' +
              optId + optName +
              optTitle + optChecked + optDisabled +
							'onclick="return getUnit(' + this.number + ').setOption(\'' + option[0] + '\', this.checked?1:0);" />';
					}

					// -- print the label of the option
          optId = 'id="label' + id + '_' + option[0] + '" ';
          optDisabled = 'class="options'+(disabled ? ' disabled':'')+'" ';

					result += '<label ' + optId + optDisabled +
            'for="' + id + '_' + option[0] + '" ' +
            'title="' + option[1].escapeSpecial() + ' ('+option[2]+')" >' + optText +
            '</label>' + "<br>\n";
				} else { // option == null
					result += '<br/>';
				}
			}
		
			result += "</td>\n";
		}
		
		result += "</tr></table>\n";
	}
	
	result += '</div>\n';
	
	return result;
};


function compPointsDiv(points, totalPoints, slot, red)
{
   var style = "";

   if (red) {
      style = 'style="color: #ff0000;"';
   }

   return '<span '+style+'>'+slot+
     ': <span style="font-weight: normal">'+
     Math.ceil(points/totalPoints*100)+'%('+points+')</span></span> ';
}

/** Return a div containing a unit composition summary */
Unit.prototype.makeCompositionDiv = function() {

  var pointsLord = 0;
  var pointsHero = 0;
  var pointsCore = 0;
  var pointsSpecial = 0;
  var pointsRare = 0;

  var pointsSlotsTotal = 0;

  // -- sum up the points for each unit and slot
  var i;
  var unit;
  var unitStruct;
  var total;
	for (i = 0; i < allUnits.length; i++) {
    unit = allUnits[i];
    unitStruct = unit.definition;
    total = unit.getTotalPoints();
    if (unitStruct.slot) {
      if (unitStruct.slot == "Lord")
        pointsLord += total;
      else if (unitStruct.slot == "Hero")
        pointsHero += total;
      else if (unitStruct.slot == "Core")
        pointsCore += total;
      else if (unitStruct.slot == "Special")
        pointsSpecial += total;
      else if (unitStruct.slot == "Rare")
        pointsRare += total;

      pointsSlotsTotal += total;
    }
  }

  // -- we don't have any slots
  if (pointsSlotsTotal == 0)
    return '<strong id="' + this.getId() + '_points" class="composition">' +
      this.getTotalPoints()+' '+i18n("points")+'</strong>\n';

	return '<strong id="' + this.getId() + '_points" class="composition">' +
    compPointsDiv( pointsLord, pointsSlotsTotal, i18n("lords"), (pointsLord/pointsSlotsTotal > 0.25) ) +
    compPointsDiv( pointsHero, pointsSlotsTotal, i18n("heroes"), (pointsHero/pointsSlotsTotal > 0.25) ) +
    compPointsDiv( pointsCore, pointsSlotsTotal, i18n("core"), (pointsCore/pointsSlotsTotal < 0.25) ) +
    compPointsDiv( pointsSpecial, pointsSlotsTotal, i18n("special"), (pointsSpecial/pointsSlotsTotal > 0.5) ) +
    compPointsDiv( pointsRare, pointsSlotsTotal, i18n("rare"), (pointsRare/pointsSlotsTotal > 0.25) ) +
    'total: ' + this.getTotalPoints()+' '+i18n("points")+
  '</strong>\n';
}

Unit.prototype.makePointsDiv = function() {
	if (this.mainUnit == null) {
    return this.makeCompositionDiv();

  } else {
    var unitStruct = this.definition;
    var style = "";

    if (unitStruct.itemPoints && this.getMagicPoints() > unitStruct.itemPoints) {
      style = 'style="color: #ff0000;"';
    }

    return '<strong id="' + this.getId() + '_points" class="points" ' + style + '>' + this.getTotalPoints()+' '+i18n("points")+'</strong>\n';
  }
};


/** Returns html tags that represent this unit
 * @param level The level of verbosity.
 *   0 means not even an edit button. 
 *   1 means edit button
 *   2 is full edit functionality
 */
Unit.prototype.toHTML = function(level) {
	if (level == null) {
		level = 1
	}
	
	var unitStruct = this.definition;
	var id = this.getId();
	
	var character = unitStruct.maxsize && unitStruct.maxsize <= 1;
	var hideT = 'style="display: none;" ';
	var unit = 'getUnit(' + this.number + ')';
	var result = "<div class='unit' id='" + id + "'>\n";
	
	if (this.mainUnit != null) {
		result += this.makeIconsDiv(level > 1);
		result += this.makePointsDiv();
			
		if (level == 1) {
			result += "<div class='buttons'>\n" +
			"<input type='image' src='" + stylePath + "editButton.svg.png' alt='Edit' onclick='" + unit + ".editAction();' title='Toggle options'>\n" +
			"</div>\n";
		} else if (level == 2) {
			result += '<div class="buttons">\n' +
			'<input type="image" src="' + stylePath + 'cloneButton.svg.png" alt="Clone" onclick="' + unit + '.cloneAction();" title="Clone this unit">\n' +
			'<input type="image" src="' + stylePath + 'removeButton.svg.png" alt="Delete" onclick="' + unit + '.removeAction();" title="Delete this unit">\n' +
			'<input type="image" src="' + stylePath + 'downButton.svg.png" alt="Down" onclick="' + unit + '.downAction();" title="Move this unit downwards">\n' +
			'<input type="image" src="' + stylePath + 'upButton.svg.png" alt="Up" onclick="' + unit + '.upAction();" title="Move this unit upwards">\n' +
			'<input type="image" src="' + stylePath + 'uneditButton.svg.png" alt="Edit" onclick="' + unit + '.uneditAction();" title="Toggle options">\n' +
			'</div>\n';
		}
	} else {

    // the topmost unit is just a container and only displays
    // the total points plus the composition
		result += this.makePointsDiv();
	}
	
	if (level < 2) {
		// The name is either the unit's full name, or the character name if applicable, or the unit with a count (eg 12x Saurus)
		result += 
			'<h4 class="name">' +
			(unitStruct.fullName ? unitStruct.fullName : (character ? (this.name ? this.name + ', ' : '') : this.count + 'x') + ' ' + unitStruct.name).escapeSpecial() +
			'</h4>\n' +
			this.makeOptionsTextDiv();
	} else {
		result += '<h4 class="name">' +
			'<input type="text" id="' + id + '_count" name="' + id + '_count" value="' + this.count + '" size="2" ' +
			(character ? hideT : '') +
			' onchange="this.value = ' + unit + '.setCount(this.value);" />\n' +
			'<input type="text" id="' + id + '_name" name="' + id + '_name" value="' + this.name.escapeSpecial() + '" ' +
			(!character || unitStruct.fullName || unitStruct.noSpecialName ? hideT : '') +
			' onchange="' + unit + '.setName(this.value);" />\n' +
			this.makeUnitSelect() +
			'</h4>\n';
		
		result += this.makeOptionsBoxDiv();
	}
		// Output sub-units
	for (var i = 0; i < this.subUnits.length; i++) {
		result += this.subUnits[i].toHTML(level);
	}
	
	// The main unit always needs the add button
	if ((level > 0 && this.mainUnit == null || level > 1) && this.definition.subDefinitions) {
		result += "<br clear='both' id='" + id + "_end'>\n" +
      "<input type='button' onclick='" + unit + ".newUnitAction();' value='"+
      ((this.mainUnit==null)?i18n("Add new unit"):i18n("Add sub unit"))+"' />\n";
	}
	
	result += '</div>\n'; // unit
	
	return result;
};

// Change the unit type
Unit.prototype.setType = function(type) {
	var id = this.getId();
	
	if (this.mainUnit == null || type == null || this.mainUnit.definition.subDefinitions[type] == null)
		return false;
		
	this.type = type;
	this.definition = this.mainUnit.definition.subDefinitions[type];

	// Update the count if needed.
	var definition = this.definition;
	
	if (definition.minsize && this.count < definition.minsize) {
		this.count = definition.minsize;
		$(id + '_count').value = this.count;
	}
	
	if (definition.maxsize && this.count > definition.maxsize) {
		this.count = definition.maxsize;
		$(id + '_count').value = this.count;
	}
	
	// We adapt the width afterwards after we know the actual amount of models we need to display
	// -- Hide/unhide the input depending on the maxsize/and/or special char
	if (definition.maxsize == null || definition.maxsize > 1) {
		$(id + '_count').show();
		$(id + '_name').hide();
	} else if (definition.fullName) {
		$(id + '_count').hide();
		$(id + '_name').hide();
	} else {
		$(id + '_count').hide();
		$(id + '_name').show();
	}
	
	this.width = 5; // set a sane width
	
	// Clone the default definions so that we can later on modify them
	this.options = this.definition.defaultOptions ? eval(uneval(this.definition.defaultOptions)) : {};
	
	// Sub-units
	// - remove the old ones
	while (this.subUnits.length > 0) {
		this.subUnits[0].removeAction();
	}
	
	// If we have subDefinition then create at least one unit with it
	if (this.definition.subDefinitions) {
		new Unit(this, null);
	}
	
	$(id).replace(this.toHTML(2));
	this.updatePoints();
	updateUnitText( this);
	
	return true;
};

Unit.prototype.setName = function(value) {
	// Only change the name if it's *different*
	if (value == this.name)
		return true;
	
	this.name = value;
	updateUnitText(this);

	return true;
};


/**
 * Sets the number of models in this unit.
 * @return a corrected number of models that should be set back to the input field.
 */
Unit.prototype.setCount = function(value) {
	value = parseInt(value);
	
	if (value <= 0 || value > 200) {
		value = this.count;
		return value;
	}
	
	if (value == this.count)
		return value;
	
	if (this.definition.minsize && value < this.definition.minsize) {
		value = this.definition.minsize;
	}
	
	if (this.definition.maxsize && value > this.definition.maxsize) {
		value = this.definition.maxsize;
	}
	
	this.count = value;
	
	// many units start with one. having such a small width is stupid
	if (this.width == 1) {
		this.width = 5;
	}
	
	$(this.getId() + '_icons').replace(this.makeIconsDiv(true));
	this.updatePoints();
	updateUnitText(this);
	
	return value;
};

Unit.prototype.setWidth = function(value) {
	value = parseInt(value);
	
	if (value <= 0 || value > 200)
		return false;
	
	if (value == this.width)
		return true;
	
	this.width = value;
	$(this.getId() + '_icons').replace(this.makeIconsDiv(true));
	updateUnitText(this);
	
	return true;
};

Unit.prototype.setOption = function(name, count) {
	// -- return if nothing to do
	if (this.options[name] == count) {
		return false;
	}

	this.options[name] = count;
	var option = this.getOptionsStruct(name);
	var optionOptions = option[3];
	var unit = this; // for the sake of the anonymous function below

	// -- unselect other options if this is a oneOf
	if (optionOptions && optionOptions.oneOf && count > 0) {
		this.forAllSelectedOptions(
			function(option2, count2) { 
				var optionOptions2 = option2[3];
				if (optionOptions2 && optionOptions2.oneOf == optionOptions.oneOf && option2[0] != name && count2 > 0) {
					unit.setOption(option2[0], 0);
				}
			}
		);
	}

  // -- call a change function if the option has one
	if (optionOptions && optionOptions.changeFunction) {
		optionOptions.changeFunction(this, name, count);
	}
  //-- selecting an option might change the icon and the points
	$(this.getId() + '_icons').replace(this.makeIconsDiv(true));
	this.updatePoints();
	updateUnitText(this);
	
	return true;
};

Unit.prototype.setExtraText = function(value) {
	this.extraText = value;
	$(this.getId() + '_options_text').replace(this.makeOptionsTextDiv());
	return true;
};

Unit.prototype.setExtraPoints = function(value) {
	value = parseInt(value);
	this.extraPoints = value;
	
	this.updatePoints();
	return true;
};

Unit.prototype.setCommentsText = function(value) {
	this.extraText = value;
	return true;
};
  
/**
 *  This function updates recursively the points div
 */
Unit.prototype.updatePoints = function() {
	var unit = this;
	while (unit) {
		var pointsElement = $(unit.getId() + '_points');
	
		if (pointsElement != null) {
			pointsElement.replace(unit.makePointsDiv());
		}
	
		unit = unit.mainUnit;
	}

	return true;
};

