/*
 * Flot plugin to order bars side by side.
 *
 * Released under the MIT license by Benjamin BUFFET, 20-Sep-2010.
 * Modifications made by Steven Hall <github.com/emmerich>, 01-May-2013.
 *
 * This plugin is an alpha version.
 *
 * To activate the plugin you must specify the parameter "order" for the specific serie :
 *
 *  $.plot($("#placeholder"), [{ data: [ ... ], bars :{ order = null or integer }])
 *
 * If 2 series have the same order param, they are ordered by the position in the array;
 *
 * The plugin adjust the point by adding a value depanding of the barwidth
 * Exemple for 3 series (barwidth : 0.1) :
 *
 *          first bar décalage : -0.15
 *          second bar décalage : -0.05
 *          third bar décalage : 0.05
 *
 */

// INFO: decalage/decallage is French for gap. It's used to denote the spacing applied to each
// bar.
(function ($) {
	function init(plot) {
		var orderedBarSeries;
		var nbOfBarsToOrder;
		var borderWidth;
		var borderWidthInXabsWidth;
		var pixelInXWidthEquivalent = 1;
		var isHorizontal = false;

		// A mapping of order integers to decallage.
		var decallageByOrder = {};

		/*
		 * This method add shift to x values
		 */
		function reOrderBars(plot, serie, datapoints) {
			var shiftedPoints = null;

			if (serieNeedToBeReordered(serie)) {
				checkIfGraphIsHorizontal(serie);
				calculPixel2XWidthConvert(plot);
				retrieveBarSeries(plot);
				calculBorderAndBarWidth(serie);

				if (nbOfBarsToOrder >= 2) {
					var position = findPosition(serie);
					var decallage = 0;

					var centerBarShift = calculCenterBarShift();

					// If we haven't already calculated the decallage for this order value, do it.
					if (typeof decallageByOrder[serie.bars.order] === 'undefined') {
						if (isBarAtLeftOfCenter(position)) {
							decallageByOrder[serie.bars.order] = -1 * (sumWidth(orderedBarSeries, position - 1, Math.floor(nbOfBarsToOrder / 2) - 1)) - centerBarShift;
						} else {
							decallageByOrder[serie.bars.order] = sumWidth(orderedBarSeries, Math.ceil(nbOfBarsToOrder / 2), position - 2) + centerBarShift + borderWidthInXabsWidth * 2;
						}
					}

					// Lookup the decallage based on the series' order value.
					decallage = decallageByOrder[serie.bars.order];

					shiftedPoints = shiftPoints(datapoints, serie, decallage);
					datapoints.points = shiftedPoints;
				}
			}
			return shiftedPoints;
		}

		function serieNeedToBeReordered(serie) {
			return serie.bars != null
				&& serie.bars.show
				&& serie.bars.order != null;
		}

		function calculPixel2XWidthConvert(plot) {
			var gridDimSize = isHorizontal ? plot.getPlaceholder().innerHeight() : plot.getPlaceholder().innerWidth();
			var minMaxValues = isHorizontal ? getAxeMinMaxValues(plot.getData(), 1) : getAxeMinMaxValues(plot.getData(), 0);
			var AxeSize = minMaxValues[1] - minMaxValues[0];
			pixelInXWidthEquivalent = AxeSize / gridDimSize;
		}

		function getAxeMinMaxValues(series, AxeIdx) {
			var minMaxValues = new Array();
			for (var i = 0; i < series.length; i++) {
				minMaxValues[0] = series[i].data[0][AxeIdx];
				minMaxValues[1] = series[i].data[series[i].data.length - 1][AxeIdx];
			}
			return minMaxValues;
		}

		function retrieveBarSeries(plot) {
			orderedBarSeries = findOthersBarsToReOrders(plot.getData());
			nbOfBarsToOrder = orderedBarSeries.length;
		}

		function findOthersBarsToReOrders(series) {
			var retSeries = new Array();
			var orderValuesSeen = [];

			for (var i = 0; i < series.length; i++) {
				if (series[i].bars.order != null && series[i].bars.show &&
					orderValuesSeen.indexOf(series[i].bars.order) < 0) {

					orderValuesSeen.push(series[i].bars.order);
					retSeries.push(series[i]);
				}
			}
			return retSeries.sort(sortByOrder);
		}

		function sortByOrder(serie1, serie2) {
			var x = serie1.bars.order;
			var y = serie2.bars.order;
			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		}

		function calculBorderAndBarWidth(serie) {
			borderWidth = typeof serie.bars.lineWidth !== 'undefined' ? serie.bars.lineWidth : 2;
			borderWidthInXabsWidth = borderWidth * pixelInXWidthEquivalent;
		}

		function checkIfGraphIsHorizontal(serie) {
			if (serie.bars.horizontal) {
				isHorizontal = true;
			}
		}

		function findPosition(serie) {
			var pos = 0
			for (var i = 0; i < orderedBarSeries.length; ++i) {
				if (serie == orderedBarSeries[i]) {
					pos = i;
					break;
				}
			}

			return pos + 1;
		}

		function calculCenterBarShift() {
			var width = 0;

			if (nbOfBarsToOrder % 2 != 0)
				width = (orderedBarSeries[Math.ceil(nbOfBarsToOrder / 2)].bars.barWidth) / 2;

			return width;
		}

		function isBarAtLeftOfCenter(position) {
			return position <= Math.ceil(nbOfBarsToOrder / 2);
		}

		function sumWidth(series, start, end) {
			var totalWidth = 0;

			for (var i = start; i <= end; i++) {
				totalWidth += series[i].bars.barWidth + borderWidthInXabsWidth * 2;
			}

			return totalWidth;
		}

		function shiftPoints(datapoints, serie, dx) {
			var ps = datapoints.pointsize;
			var points = datapoints.points;
			var j = 0;
			for (var i = isHorizontal ? 1 : 0; i < points.length; i += ps) {
				points[i] += dx;
				//Adding the new x value in the serie to be abble to display the right tooltip value,
				//using the index 3 to not overide the third index.
				serie.data[j][3] = points[i];
				j++;
			}

			return points;
		}

		plot.hooks.processDatapoints.push(reOrderBars);

	}

	var options = {
		series: {
			bars: {order: null} // or number/string
		}
	};

	$.plot.plugins.push({
		init: init,
		options: options,
		name: "orderBars",
		version: "0.2"
	});

})(jQuery);