var var_dump = function (array, returnOutput, glue) {
	var output = new Array();
	if (typeof array != 'object') {
		array = new Array(array);
	}
	
	for (var i in array) {
		if (typeof array[i] == 'function') {//false && 
			output.push(i + ': ' + (typeof array[i]) + '("function")');
		} else {
			output.push(i + ': ' + (typeof array[i]) + '("' + array[i] + '")');
		}
	}
	
	if (returnOutput) {
		return output;
	} else {
		if (!glue) { glue = "\n"; }
		alert(output.join(glue));
	}
}


/***************************************************************************************************
* Startup function (Triggered by google load callback)
***************************************************************************************************/
var initGraphTables = function () {
	// Initialise the data tables and graph them
	var tables = $('table.graphData');
	var chart = null;
	for (var i = 0; i < tables.length; i++) {
		var table = $(tables[i]);
		
		// Plot a graph using the data in this table - the class name defines the type of chart to draw
		if (table.hasClass('graphBar')) {
			chart = graphTable._default.graphBar(table);
		} else if (table.hasClass('graphBarStacked')) {
			chart = graphTable._default.graphBarStacked(table);
		} else if (table.hasClass('graphLine')) {
			chart = graphTable._default.graphLine(table);
		} else {
			// Default chart type
			alert('Unknown chart type');
		}
	}
	
	// This will return the last chart
	return chart;
};


/***************************************************************************************************
* Graph plotting adapters
***************************************************************************************************/
/**
 * Abstract adapter - other adapters should extend this and override the methods
 */
var graphTable_Abstract = {
	graphLine: function (table) {
		alert('Method not defined (graphLine)');
	},
	
	graphBar: function (table) {
		alert('Method not defined (graphBar)');
	},
	
	graphBarStacked: function (table) {
		alert('Method not defined (graphBarStacked)');
	}
};


/**
 * Google visualizations
 */
google.load('visualization', '1', { packages: ['linechart', 'columnchart', 'barchart'] });
var graphTable_GoogleVisualization = $.extend(true, {}, graphTable_Abstract);
$.extend(true, graphTable_GoogleVisualization, {
	_chartIndex: 0,
	
	_colors: [// This is a copy of the list of colours google visualisations are using - when we define different colours to these in the options to the chart, most lines appear black in ie so we are sticking to the default
		'4684EE','DC3912','FF9900','008000','666666','4942CC','CB4AC5','D6AE00','336699','DD4477',
		'AAAA11','66AA00','888888','994499','DD5511','22AA99','999999','705770','109618','A32929'
	],
	
	_defaultOptions: {
		width: 760,
		height: 500,
		legend: 'none',
		min: 0,
		axisFontSize: 14,
		legendFontSize: 14,
		titleFontSize: 16,
		tooltipFontSize: 14,
		tooltipHeight: 80,
		tooltipWidth: 280
	},
	
	/**
	 * Charts a line chart
	 * 
	 * @param		HTMLTableElement
	 * @return		null
	 */
	graphLine: function (table) {
		var data = this._getData(table);
		var graphDiv = this._newDiv(table);
		
		// Draw the chart
		var chart = new google.visualization.LineChart(graphDiv);
		this._activateTooltipOnHover(chart);
		chart.draw(data.data, $.extend(true, {
			title: table.attr('summary'),
			titleY: data.yLabel
		}, this._defaultOptions));
		
		// Draw the legend
		this._redrawLegend(data.data, this._newDiv(table, 'graphLegend'));
		
		return chart;
	},
	
	/**
	 * Charts a bar chart
	 * 
	 * @param		HTMLTableElement
	 * @return		null
	 */
	graphBar: function (table) {
		var data = this._getData(table);
		var graphDiv = this._newDiv(table);
		
		// Draw the chart
		var chart = new google.visualization.ColumnChart(graphDiv);
		this._activateTooltipOnHover(chart);
		chart.draw(data.data, $.extend(true, {
			title: table.attr('summary'),
			titleY: data.yLabel
		}, this._defaultOptions));
		
		// Draw the legend
		this._redrawLegend(data.data, this._newDiv(table, 'graphLegend'));
		
		return chart;
	},
	
	/**
	 * Charts a stacked bar chart
	 * 
	 * @param		HTMLTableElement
	 * @return		null
	 */
	graphBarStacked: function (table) {
		var data = this._getData(table);
		var graphDiv = this._newDiv(table);
		
		// Draw the chart
		var chart = new google.visualization.ColumnChart(graphDiv);
		this._activateTooltipOnHover(chart);
		chart.draw(data.data, $.extend(true, {
			title: table.attr('summary'),
			titleY: data.yLabel,
			isStacked: true
		}, this._defaultOptions));
		
		// Draw the legend
		this._redrawLegend(data.data, this._newDiv(table, 'graphLegend'));
		
		return chart;
	},
	
	/**
	 * Creates a new div element to host the chart to be drawn
	 * 
	 * @param		HTMLTableElement
	 * @param		string
	 * @return		HTMLDivElement
	 */
	_newDiv: function (table, className) {
		var graphDiv = document.createElement('div');
		if (className) {
			graphDiv.className = className;
		} else {
			graphDiv.className = 'graph';
		}
		table.parent()[0].appendChild(graphDiv);
		return graphDiv;
	},
	
	/**
	 * Tries to automatically detect if a table is in 'rotated' form or not
	 * 
	 * @param		HTMLTableElement
	 * @return		boolean
	 */
	_isRotatedTable: function (table) {
		if ($(table).hasClass('graphDataRotated')) {
			return true
		}
		
		// Check for rowspans
		var trs = $('tbody tr', table);
		for (var i = 0; i < trs.length; i++) {
			var cells = $('th,td', trs[i]);
			for (var j = 0; j < cells.length; j++) {
				if ($(cells[j]).attr('rowspan') > 1) {
					return true;
				}
			}
		}
		
		return false;
	},
	
	/**
	 * Maps data from an html table to a DataTable format compatible with the google visualisation charts
	 * 
	 * @param		HTMLTableElement
	 * @return		google.visualization.DataTable
	 */
	_getDataRotated: function (table) {
		var data = new google.visualization.DataTable();
		
		// Combine the rows in <thead /> and <tbody />
		var thead = $('thead', table);
		var tbody = $('tbody', table);
		var trs = $.merge($('tr', thead).toArray(), $('tr', tbody).toArray());
		
		// Add columns (in this case - we assume only the first row are the column headers)
		data.addColumn('string', 'Year');
		var ths = $('th', trs[0]);
		var thOffset = $('th', trs[1]).length; // The number of th cells in the second row and will also tell us the number of cells to skip in the first row
		for (var i = thOffset; i < ths.length; i++) {
			data.addColumn('number', this._wrapText($(ths[i]).text()));
		}
		
		// Add row headers (primary key - we start at the second row as the first row has been assumed as the column header row)
		var maxThs = 0; // This will keep track of the max headers per data row to help calculate the offset for creating the composite primary key values
		{// Focus on adding the lowest level first
			for (var i = 1; i < trs.length; i++) {
				var ths = $('th', trs[i]);
				if (maxThs < ths.length) {
					maxThs = ths.length;
				}
				
				for (var j = ths.length - 1; j >= 0; j--) {
					if (j == (ths.length - 1)) {
						data.addRow();
						data.setValue(i - 1, 0, $(ths[j]).text());
					}
				}
			}
		}
		{// Now prepend additional header values to support rowspans (this is done as a separate step because we need to have all the lowest level in before we can prepend to it
			for (var i = 1; i < trs.length; i++) {
				var ths = $('th', trs[i]);
				for (var j = ths.length - 1; j >= 0; j--) {
					if (j == (ths.length - 1)) {
						// This was handled in the previous block of code
					} else {
						// TODO: there is a bug here - this will only support rowspan once as we need to take into account offsets created by missing table headers per row following colspans
						var jNormalised = j;// - (maxThs - ths.length);
						//alert(maxThs);
						if (ths[jNormalised]) {
							var rowspan = $(ths[jNormalised]).attr('rowspan')?$(ths[jNormalised]).attr('rowspan'):1;
							
							for (var m = 0, k = i - 1; m < rowspan; m++, k++) {
								var previousValue = data.getValue(k, 0);
								var newValue = $(ths[jNormalised]).text() + ', ' + previousValue;
								data.setValue(k, 0, newValue);
							}
						}
					}
				}
			}
		}
		
		// Add data
		var yLabel = ''; // It is assumed that all cells will carry the same label
		for (var i = 1; i < trs.length; i++) {
			var tds = $('td', trs[i]);
			for (var j = 0; j < tds.length; j++) {
				// Set the data value
				var value = $(tds[j]).text();
				value = value.replace(/<[^>]+>/g, ' ');
				value = value.replace(/\s/g, '');
				yLabel = this._extractLabel(value, yLabel);
				value = value.replace(/([^0-9\.\-]+|\s)/g, '');
				data.setValue(i - 1, j + 1, Number(value));
			}
		}
		
		return {
			yLabel: this._filterLabel(yLabel),
			data: data
		};
	},
	
	/**
	 * Maps data from an html table to a DataTable format compatible with the google visualisation charts
	 * 
	 * @param		HTMLTableElement
	 * @return		google.visualization.DataTable
	 */
	_getData: function (table) {
		// Check if this table is rotated
		if (this._isRotatedTable(table)) {
			return this._getDataRotated(table);
		}
		
		var data = new google.visualization.DataTable();
		
		// Check the <thead />
		var thead = $('thead', table);
		var theadTrs = $('tr', thead);

		// Check the <tbody />
		var tbody = $('tbody', table);
		var tbodyTrs = $('tr', tbody);
		
		if (theadTrs.length == 0) {
			theadTrs = tbodyTrs.slice(0, 1);
			tbodyTrs = tbodyTrs.slice(1);
		}
		
		// Add columns
		data.addColumn('string', 'Year');
		for (var i = 0; i < tbodyTrs.length; i++) {
			if ($(tbodyTrs[i]).hasClass('ignore')) {
				continue;
			}
			
			var ths = $('th', tbodyTrs[i]);
			if (ths.length == 1/* && !ths.hasClass('ignore')*/) {
				data.addColumn('number', this._wrapText(ths.text()));
			}
		}
		
		// Add row headers (primary key)
		// i starts counting from the last row
		// j starts at 1 because we are ignoring the first th cell of the header rows
		for (var i = theadTrs.length - 1; i >= 0; i--) {
			if (i == (theadTrs.length - 1)) {
				var ths = $('th', theadTrs[i]);
				data.addRows(ths.length - 1);
				for (var j = 1; j < ths.length; j++) {
					data.setValue(j - 1, 0, $(ths[j]).text());
				}
			} else {
				// Handling colspans
				// Prepend value of previous level
				var ths = $('th', theadTrs[i]);
				for (var j = 1, k = 0; j < ths.length; j++) {
					// j is the index of the current th cell, k is the primary index of the lowest level cell
					var colspan = $(ths[j]).attr('colspan')?$(ths[j]).attr('colspan'):1; // Unlikely to default to 1 unless deliberately trying to break
					for (var m = 0; m < colspan; m++, k++) {
						var previousValue = data.getValue(k, 0);
						var newValue = $(ths[j]).text() + ', ' + previousValue;
						data.setValue(k, 0, newValue);
					}
				}
			}
		}
		
		// Add data
		var yLabel = ''; // It is assumed that all cells will carry the same label
		var offset = 0;
		for (var i = 0; i < tbodyTrs.length; i++) {
			if ($(tbodyTrs[i]).hasClass('ignore')) {
				offset++;
				continue;
			}
			
			var tds = $('td', tbodyTrs[i]);
			for (var j = 0; j < tds.length; j++) {
				// Set the data value
				var value = $(tds[j]).text();
				value = value.replace(/<[^>]+>/g, ' ');
				value = value.replace(/\s/g, '');
				yLabel = this._extractLabel(value, yLabel);
				value = value.replace(/([^0-9\.\-]+|\s)/g, '');
				data.setValue(j, i + 1 - offset, Number(value)); // The first column is our primary key so we add 1 to the data column index
			}
		}
		
		return {
			yLabel: this._filterLabel(yLabel),
			data: data
		};
	},
	
	_extractLabel: function (value, prevLabel) {
		if (match = value.match(/([^0-9\.\-]+)/)) {
			if (match[0].charCodeAt(0) == 37 || match[0].charCodeAt(0) == 163) {
				return match[0];
			}
		}
		return prevLabel;
	},
	
	/**
	 * Gets the textual representation of a symbol label
	 * 
	 * @param		string
	 * @return		string
	 */
	_filterLabel: function (value) {
		// Get a nicer looking label
		if (value && value.length) {
			switch (value.charCodeAt(0)) {
				case 37:
					value = 'Percentage';
					break;
				case 163:
					value = 'GBP';
					break;
				case 160: // This is just a blank
					value = '';
					break;
				default:
					//alert('['+value+'] '+value.length+' '+value.charCodeAt(0));
					break;
			}
		} else {
			//alert('['+value+'] '+value.length+' '+value.charCodeAt(0));
		}
		return value;
	},
	
	/**
	 * Text that is too long does not automatically wrap in the tooltip box which appears when hovering over the chart bar/point
	 * - this function forces a new line character after a certain number of characters
	 * 
	 * @param		string			string
	 * @param		integer			maxlength
	 * @return		string
	 */
	_wrapText: function (string, maxlength) {
		if (!maxlength) {
			maxlength = 40;
		}
		
		var parts = string.split(/\s+/);
		var output = '';
		var lineLength = 0;
		for (var i = 0; i < parts.length; i++) {
			lineLength+= parts[i].length;
			if (lineLength > maxlength) {
				output+= "\n";
				lineLength = parts[i].length;
			} else {
				if (lineLength > parts[i].length) {
					output+= ' ';
				}
				lineLength++;
			}
			output+= parts[i];
		}
		return output;
	},
	
	/**
	 * Returns the labels required to redraw the legend
	 * 
	 * @param		google.visualization.DataTable
	 * @return		Array
	 */
	_getColumnLabels: function (data) {
		var items = new Array();
		// Start counting at 1 as the first column is manually inserted 'Year'
		for (var i = 1; i < data.getNumberOfColumns(); i++) {
			items.push(data.getColumnLabel(i));
		}
		return items;
	},
	
	/**
	 * Draws the legend that google fails to draw to a readable standard
	 * 
	 * @param		google.visualization.DataTable
	 * @param		HTMLDivElement
	 * @return		null
	 */
	_redrawLegend: function (data, legendDiv) {
		var legendItems = this._getColumnLabels(data);
		
		// No problems to hide and redraw legend - you just won't get the wobbly
		var ol = document.createElement('ol');
		var colors = this._colors;
		for (var i = 0, j = 0; i < legendItems.length; i++, j++) {
			if (!colors[j]) {
				j = 0;
			}
			var li = document.createElement('li');
			li.innerHTML = '<span style="background: #' + colors[j] + ';"></span> ' + legendItems[i] + '';
			li.id = 'legendItem_' + this._chartIndex + '_' + i;
			li.style.cursor = 'pointer';
			
			$(li).click(function () {
				var parts = this.id.match(/^legendItem_(\d+)_(\d+)$/);
				var iframe = $('iframe');
				var iframeDocument = window.frames[iframe.attr('name')];
				iframeDocument.parent._GCHART_getBuilder(parts[1])._onLegendClick(parts[2]);
			});
			ol.appendChild(li);
		}
		legendDiv.appendChild(ol);
		
		this._chartIndex++;
		
		return legendDiv;
	},
	
	/**
	 * By default, the tooltip is triggered on click, this forces the trigger to occur on hover also
	 * 
	 * @param		google.visualization.ColumnChart|google.visualization.LineChart
	 * @return		null
	 */
	_activateTooltipOnHover: function (chart) {
		google.visualization.events.addListener(chart, 'onmouseover', function (cell) {
			if (!chart.openedByHover) {
				chart.openedByHover = true;
				chart.setSelection([cell]);
			}
		});
		google.visualization.events.addListener(chart, 'onmouseout', function (cell) {
			if (chart.openedByHover) {
				chart.openedByHover = false;
				chart.setSelection([]);
			}
		});
		google.visualization.events.addListener(chart, 'select', function (cell) {
			chart.openedByHover = false;
		});
	}
});


/**
 * Flot (and also using graphTable component)
 */
var graphTable_Flot = $.extend(true, {}, graphTable_Abstract);
/*$.extend(true, graphTable_Flot, {
	graphLine: function (table) {
		var configGraphTable = {
			series: 'rows',
			dataTransform: function (s) { return s.replace('%', ''); }
		};
		
		var configFlot = {
			series: {
				lines: { show: true },
				bars: this.lines,
				points: { show: true },
				hoverable: true,
				clickable: true
			},
			xaxis: {
				tickDecimals: 0
			},
			yaxis: {
				min: 0,
				max: 100
			}
		};
		
		table.graphTable(configGraphTable, configFlot);
	},
	
	graphBar: function (table) {
		var tableData = new Array();
		var xTicks = new Array();
		var rows = table.find('tr');
		
		var startRow = 0;
		for (var i = startRow; i < rows.length; i++) {
			var tds = $(rows[i]).find('td');
			var ths = $(rows[i]).find('th');
			if (tds.length == 0) {
				// This is the header
				// Look at the next row to see how many headers are in the row (it's unlikely that there will be no rows after the header row)
				var k = $(rows[i + 1]).find('th').length;
				
				// Use the headings on the top row as headers (it is assumed the first few th cells are not used depending on the number of th cells on the next row - this is used as offset k)
				for (var j = k; j < ths.length; j++) {
					xTicks.push([j - k, $(ths[j]).text()]);
				}
			} else {
				var label = $(ths[0]).text();
				var data = new Array();
				for (var j = 0; j < tds.length; j++) {
					data.push([j, $(tds[j]).text().replace('%', '')]);
				}
				tableData.push({ label: label, data: data });
			}
		}
		
		var graphDiv = $(document.createElement('div'));
		graphDiv.attr('class', 'graph');
		graphDiv.insertAfter(table);
		
		$.plot(graphDiv, tableData, {
			bars: {
				show: true,
				autoScale: true,
				fillOpacity: 1,
				align: 'center'
			},
			xaxis: {
				ticks: xTicks
			},
			yaxis: {
				min: 0,
				max: 100
			}
		});
	}
});*/


/**
 * jQuery 'visualise' plugin
 */
var graphTable_Visualise = $.extend(true, {}, graphTable_Abstract);
/*$.extend(true, graphTable_Visualise, {
	graphLine: function (table) {
		$(table).visualize({ type: 'line' });
	},
	
	graphBar: function (table) {
		$(table).visualize({ type: 'bar' });
	},
	
	graphBar: function (table) {
		$(table).visualize({ type: 'bar' });
	}
});*/


/***************************************************************************************************
* Graph plotter interface
***************************************************************************************************/
var graphTable = {
	// Google Visualization adapter
	googleVisualization: graphTable_GoogleVisualization,
	
	// Flot adapter
	flot: graphTable_Flot,
	
	// jQuery visualise adapter
	visualise: graphTable_Visualise,
	
	// Set the default adapter
	_default: graphTable_GoogleVisualization
};

