morpheus.Ranking = function(values) {
	var ranks = [];
	for (var i = 0, length = values.length; i < length; i++) {
		ranks.push({
			value : values[i],
			position : i
		});
	}
	if (ranks.length === 0) {
		return [];
	}
	ranks.sort(function(a, b) {
		return (a.value < b.value ? -1 : (a.value === b.value ? 0 : 1));
	});

	var out = [];
	var pos = 1; // position in sorted array
	out[ranks[0].position] = pos;
	var tiesTrace = [];
	tiesTrace.push(ranks[0].position);
	for (var i = 1; i < ranks.length; i++) {
		if (ranks[i].value > ranks[i - 1].value) {
			// tie sequence has ended (or had length 1)
			pos = i + 1;
			if (tiesTrace.length > 1) { // if seq is nontrivial, resolve
				morpheus.Ranking.fillAverage(out, tiesTrace);
			}
			tiesTrace = [];
			tiesTrace.push(ranks[i].position);
		} else {
			// tie sequence continues
			tiesTrace.push(ranks[i].position);
		}
		out[ranks[i].position] = pos;
	}
	if (tiesTrace.length > 1) { // handle tie sequence at end
		morpheus.Ranking.fillAverage(out, tiesTrace);
	}
	return out;
};
morpheus.Ranking.fill = function(data, tiesTrace, value) {
	for (var i = 0, length = tiesTrace.length; i < length; i++) {
		data[tiesTrace[i]] = value;
	}
};
morpheus.Ranking.fillAverage = function(ranks, tiesTrace) {
	var c = ranks[tiesTrace[0]];
	// length of sequence of tied ranks
	var length = tiesTrace.length;
	morpheus.Ranking.fill(ranks, tiesTrace, (2 * c + length - 1) / 2);
};