/**

 * @license Highcharts JS v4.1.4 (2015-03-10)

 * Data module

 *

 * (c) 2012-2014 Torstein Honsi

 *

 * License: www.highcharts.com/license

 */



// JSLint options:

/*global jQuery, HighchartsAdapter */



(function (Highcharts) {

	

	// Utilities

	var each = Highcharts.each,

		pick = Highcharts.pick,

		inArray = HighchartsAdapter.inArray,

		splat = Highcharts.splat,

		SeriesBuilder;

	

	

	// The Data constructor

	var Data = function (dataOptions, chartOptions) {

		this.init(dataOptions, chartOptions);

	};

	

	// Set the prototype properties

	Highcharts.extend(Data.prototype, {

		

	/**

	 * Initialize the Data object with the given options

	 */

	init: function (options, chartOptions) {

		this.options = options;

		this.chartOptions = chartOptions;

		this.columns = options.columns || this.rowsToColumns(options.rows) || [];

		this.firstRowAsNames = pick(options.firstRowAsNames, true);

		this.decimalRegex = options.decimalPoint && new RegExp('^([0-9]+)' + options.decimalPoint + '([0-9]+)$');



		// This is a two-dimensional array holding the raw, trimmed string values

		// with the same organisation as the columns array. It makes it possible

		// for example to revert from interpreted timestamps to string-based

		// categories.

		this.rawColumns = [];



		// No need to parse or interpret anything

		if (this.columns.length) {

			this.dataFound();



		// Parse and interpret

		} else {



			// Parse a CSV string if options.csv is given

			this.parseCSV();

			

			// Parse a HTML table if options.table is given

			this.parseTable();



			// Parse a Google Spreadsheet 

			this.parseGoogleSpreadsheet();	

		}



	},



	/**

	 * Get the column distribution. For example, a line series takes a single column for 

	 * Y values. A range series takes two columns for low and high values respectively,

	 * and an OHLC series takes four columns.

	 */

	getColumnDistribution: function () {

		var chartOptions = this.chartOptions,

			options = this.options,

			xColumns = [],

			getValueCount = function (type) {

				return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;

			},

			getPointArrayMap = function (type) {

				return Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap;

			},

			globalType = chartOptions && chartOptions.chart && chartOptions.chart.type,

			individualCounts = [],

			seriesBuilders = [],

			seriesIndex = 0,

			i;



		each((chartOptions && chartOptions.series) || [], function (series) {

			individualCounts.push(getValueCount(series.type || globalType));

		});



		// Collect the x-column indexes from seriesMapping

		each((options && options.seriesMapping) || [], function (mapping) {

			xColumns.push(mapping.x || 0);

		});



		// If there are no defined series with x-columns, use the first column as x column

		if (xColumns.length === 0) {

			xColumns.push(0);

		}



		// Loop all seriesMappings and constructs SeriesBuilders from

		// the mapping options.

		each((options && options.seriesMapping) || [], function (mapping) {

			var builder = new SeriesBuilder(),

				name,

				numberOfValueColumnsNeeded = individualCounts[seriesIndex] || getValueCount(globalType),

				seriesArr = (chartOptions && chartOptions.series) || [],

				series = seriesArr[seriesIndex] || {},

				pointArrayMap = getPointArrayMap(series.type || globalType) || ['y'];



			// Add an x reader from the x property or from an undefined column

			// if the property is not set. It will then be auto populated later.

			builder.addColumnReader(mapping.x, 'x');



			// Add all column mappings

			for (name in mapping) {

				if (mapping.hasOwnProperty(name) && name !== 'x') {

					builder.addColumnReader(mapping[name], name);

				}

			}



			// Add missing columns

			for (i = 0; i < numberOfValueColumnsNeeded; i++) {

				if (!builder.hasReader(pointArrayMap[i])) {

					//builder.addNextColumnReader(pointArrayMap[i]);

					// Create and add a column reader for the next free column index

					builder.addColumnReader(undefined, pointArrayMap[i]);

				}

			}



			seriesBuilders.push(builder);

			seriesIndex++;

		});



		var globalPointArrayMap = getPointArrayMap(globalType);

		if (globalPointArrayMap === undefined) {

			globalPointArrayMap = ['y'];

		}



		this.valueCount = {

			global: getValueCount(globalType),

			xColumns: xColumns,

			individual: individualCounts,

			seriesBuilders: seriesBuilders,

			globalPointArrayMap: globalPointArrayMap

		};

	},



	/**

	 * When the data is parsed into columns, either by CSV, table, GS or direct input,

	 * continue with other operations.

	 */

	dataFound: function () {

		

		if (this.options.switchRowsAndColumns) {

			this.columns = this.rowsToColumns(this.columns);

		}



		// Interpret the info about series and columns

		this.getColumnDistribution();



		// Interpret the values into right types

		this.parseTypes();

		

		// Handle columns if a handleColumns callback is given

		if (this.parsed() !== false) {

		

			// Complete if a complete callback is given

			this.complete();

		}

		

	},

	

	/**

	 * Parse a CSV input string

	 */

	parseCSV: function () {

		var self = this,

			options = this.options,

			csv = options.csv,

			columns = this.columns,

			startRow = options.startRow || 0,

			endRow = options.endRow || Number.MAX_VALUE,

			startColumn = options.startColumn || 0,

			endColumn = options.endColumn || Number.MAX_VALUE,

			itemDelimiter,

			lines,

			activeRowNo = 0;

			

		if (csv) {

			

			lines = csv

				.replace(/\r\n/g, "\n") // Unix

				.replace(/\r/g, "\n") // Mac

				.split(options.lineDelimiter || "\n");



			itemDelimiter = options.itemDelimiter || (csv.indexOf('\t') !== -1 ? '\t' : ',');

			

			each(lines, function (line, rowNo) {

				var trimmed = self.trim(line),

					isComment = trimmed.indexOf('#') === 0,

					isBlank = trimmed === '',

					items;

				

				if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {

					items = line.split(itemDelimiter);

					each(items, function (item, colNo) {

						if (colNo >= startColumn && colNo <= endColumn) {

							if (!columns[colNo - startColumn]) {

								columns[colNo - startColumn] = [];					

							}

							

							columns[colNo - startColumn][activeRowNo] = item;

						}

					});

					activeRowNo += 1;

				}

			});



			this.dataFound();

		}

	},

	

	/**

	 * Parse a HTML table

	 */

	parseTable: function () {

		var options = this.options,

			table = options.table,

			columns = this.columns,

			startRow = options.startRow || 0,

			endRow = options.endRow || Number.MAX_VALUE,

			startColumn = options.startColumn || 0,

			endColumn = options.endColumn || Number.MAX_VALUE;



		if (table) {

			

			if (typeof table === 'string') {

				table = document.getElementById(table);

			}

			

			each(table.getElementsByTagName('tr'), function (tr, rowNo) {

				if (rowNo >= startRow && rowNo <= endRow) {

					each(tr.children, function (item, colNo) {

						if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {

							if (!columns[colNo - startColumn]) {

								columns[colNo - startColumn] = [];					

							}

							

							columns[colNo - startColumn][rowNo - startRow] = item.innerHTML;

						}

					});

				}

			});



			this.dataFound(); // continue

		}

	},



	/**

	 */

	parseGoogleSpreadsheet: function () {

		var self = this,

			options = this.options,

			googleSpreadsheetKey = options.googleSpreadsheetKey,

			columns = this.columns,

			startRow = options.startRow || 0,

			endRow = options.endRow || Number.MAX_VALUE,

			startColumn = options.startColumn || 0,

			endColumn = options.endColumn || Number.MAX_VALUE,

			gr, // google row

			gc; // google column



		if (googleSpreadsheetKey) {

			jQuery.ajax({

				dataType: 'json', 

				url: 'https://spreadsheets.google.com/feeds/cells/' + 

				  googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +

					  '/public/values?alt=json-in-script&callback=?',

				error: options.error,

				success: function (json) {

					// Prepare the data from the spreadsheat

					var cells = json.feed.entry,

						cell,

						cellCount = cells.length,

						colCount = 0,

						rowCount = 0,

						i;

				

					// First, find the total number of columns and rows that 

					// are actually filled with data

					for (i = 0; i < cellCount; i++) {

						cell = cells[i];

						colCount = Math.max(colCount, cell.gs$cell.col);

						rowCount = Math.max(rowCount, cell.gs$cell.row);			

					}

				

					// Set up arrays containing the column data

					for (i = 0; i < colCount; i++) {

						if (i >= startColumn && i <= endColumn) {

							// Create new columns with the length of either end-start or rowCount

							columns[i - startColumn] = [];



							// Setting the length to avoid jslint warning

							columns[i - startColumn].length = Math.min(rowCount, endRow - startRow);

						}

					}

					

					// Loop over the cells and assign the value to the right

					// place in the column arrays

					for (i = 0; i < cellCount; i++) {

						cell = cells[i];

						gr = cell.gs$cell.row - 1; // rows start at 1

						gc = cell.gs$cell.col - 1; // columns start at 1



						// If both row and col falls inside start and end

						// set the transposed cell value in the newly created columns

						if (gc >= startColumn && gc <= endColumn &&

							gr >= startRow && gr <= endRow) {

							columns[gc - startColumn][gr - startRow] = cell.content.$t;

						}

					}

					self.dataFound();

				}

			});

		}

	},

	

	/**

	 * Trim a string from whitespace

	 */

	trim: function (str, inside) {

		if (typeof str === 'string') {

			str = str.replace(/^\s+|\s+$/g, '');



			// Clear white space insdie the string, like thousands separators

			if (inside && /^[0-9\s]+$/.test(str)) { 

				str = str.replace(/\s/g, '');

			}



			if (this.decimalRegex) {

				str = str.replace(this.decimalRegex, '$1.$2');

			}

		}

		return str;

	},

	

	/**

	 * Parse numeric cells in to number types and date types in to true dates.

	 */

	parseTypes: function () {

		var columns = this.columns,

			col = columns.length;



		while (col--) {

			this.parseColumn(columns[col], col);

		}



	},



	/**

	 * Parse a single column. Set properties like .isDatetime and .isNumeric.

	 */

	parseColumn: function (column, col) {

		var rawColumns = this.rawColumns,

			columns = this.columns, 

			row = column.length,

			val,

			floatVal,

			trimVal,

			trimInsideVal,

			firstRowAsNames = this.firstRowAsNames,

			isXColumn = inArray(col, this.valueCount.xColumns) !== -1,

			dateVal,

			backup = [],

			diff,

			chartOptions = this.chartOptions,

			descending,

			columnTypes = this.options.columnTypes || [],

			columnType = columnTypes[col],

			forceCategory = isXColumn && ((chartOptions && chartOptions.xAxis && splat(chartOptions.xAxis)[0].type === 'category') || columnType === 'string');

		

		if (!rawColumns[col]) {

			rawColumns[col] = [];

		}

		while (row--) {

			val = backup[row] || column[row];

			

			trimVal = this.trim(val);

			trimInsideVal = this.trim(val, true);

			floatVal = parseFloat(trimInsideVal);



			// Set it the first time

			if (rawColumns[col][row] === undefined) {

				rawColumns[col][row] = trimVal;

			}

			

			// Disable number or date parsing by setting the X axis type to category

			if (forceCategory || (row === 0 && firstRowAsNames)) {

				column[row] = trimVal;



			} else if (+trimInsideVal === floatVal) { // is numeric

			

				column[row] = floatVal;

				

				// If the number is greater than milliseconds in a year, assume datetime

				if (floatVal > 365 * 24 * 3600 * 1000 && columnType !== 'float') {

					column.isDatetime = true;

				} else {

					column.isNumeric = true;

				}



				if (column[row + 1] !== undefined) {

					descending = floatVal > column[row + 1];

				}

			

			// String, continue to determine if it is a date string or really a string

			} else {

				dateVal = this.parseDate(val);

				// Only allow parsing of dates if this column is an x-column

				if (isXColumn && typeof dateVal === 'number' && !isNaN(dateVal) && columnType !== 'float') { // is date

					backup[row] = val; 

					column[row] = dateVal;

					column.isDatetime = true;



					// Check if the dates are uniformly descending or ascending. If they 

					// are not, chances are that they are a different time format, so check

					// for alternative.

					if (column[row + 1] !== undefined) {

						diff = dateVal > column[row + 1];

						if (diff !== descending && descending !== undefined) {

							if (this.alternativeFormat) {

								this.dateFormat = this.alternativeFormat;

								row = column.length;

								this.alternativeFormat = this.dateFormats[this.dateFormat].alternative;

							} else {

								column.unsorted = true;

							}

						}

						descending = diff;

					}

				

				} else { // string

					column[row] = trimVal === '' ? null : trimVal;

					if (row !== 0 && (column.isDatetime || column.isNumeric)) {

						column.mixed = true;

					}

				}

			}

		}



		// If strings are intermixed with numbers or dates in a parsed column, it is an indication

		// that parsing went wrong or the data was not intended to display as numbers or dates and 

		// parsing is too aggressive. Fall back to categories. Demonstrated in the 

		// highcharts/demo/column-drilldown sample.

		if (isXColumn && column.mixed) {

			columns[col] = rawColumns[col];

		}



		// If the 0 column is date or number and descending, reverse all columns. 

		if (isXColumn && descending && this.options.sort) {

			for (col = 0; col < columns.length; col++) {

				columns[col].reverse();

				if (firstRowAsNames) {

					columns[col].unshift(columns[col].pop());

				}

			}

		}

	},

	

	/**

	 * A collection of available date formats, extendable from the outside to support

	 * custom date formats.

	 */

	dateFormats: {

		'YYYY-mm-dd': {

			regex: /^([0-9]{4})[\-\/\.]([0-9]{2})[\-\/\.]([0-9]{2})$/,

			parser: function (match) {

				return Date.UTC(+match[1], match[2] - 1, +match[3]);

			}

		},

		'dd/mm/YYYY': {

			regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,

			parser: function (match) {

				return Date.UTC(+match[3], match[2] - 1, +match[1]);

			},

			alternative: 'mm/dd/YYYY' // different format with the same regex

		},

		'mm/dd/YYYY': {

			regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,

			parser: function (match) {

				return Date.UTC(+match[3], match[1] - 1, +match[2]);

			}

		},

		'dd/mm/YY': {

			regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,

			parser: function (match) {

				return Date.UTC(+match[3] + 2000, match[2] - 1, +match[1]);

			},

			alternative: 'mm/dd/YY' // different format with the same regex

		},

		'mm/dd/YY': {

			regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,

			parser: function (match) {

				return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]);

			}

		}

	},

	

	/**

	 * Parse a date and return it as a number. Overridable through options.parseDate.

	 */

	parseDate: function (val) {

		var parseDate = this.options.parseDate,

			ret,

			key,

			format,

			dateFormat = this.options.dateFormat || this.dateFormat,

			match;



		if (parseDate) {

			ret = parseDate(val);

		

		} else if (typeof val === 'string') {

			// Auto-detect the date format the first time

			if (!dateFormat) {

				for (key in this.dateFormats) {

					format = this.dateFormats[key];

					match = val.match(format.regex);

					if (match) {

						this.dateFormat = dateFormat = key;

						this.alternativeFormat = format.alternative;

						ret = format.parser(match);

						break;

					}

				}

			// Next time, use the one previously found

			} else {

				format = this.dateFormats[dateFormat];

				match = val.match(format.regex);

				if (match) {

					ret = format.parser(match);

				}

			}

			// Fall back to Date.parse		

			if (!match) {

				match = Date.parse(val);

				// External tools like Date.js and MooTools extend Date object and

				// returns a date.

				if (typeof match === 'object' && match !== null && match.getTime) {

					ret = match.getTime() - match.getTimezoneOffset() * 60000;

				

				// Timestamp

				} else if (typeof match === 'number' && !isNaN(match)) {

					ret = match - (new Date(match)).getTimezoneOffset() * 60000;

				}

			}

		}

		return ret;

	},

	

	/**

	 * Reorganize rows into columns

	 */

	rowsToColumns: function (rows) {

		var row,

			rowsLength,

			col,

			colsLength,

			columns;



		if (rows) {

			columns = [];

			rowsLength = rows.length;

			for (row = 0; row < rowsLength; row++) {

				colsLength = rows[row].length;

				for (col = 0; col < colsLength; col++) {

					if (!columns[col]) {

						columns[col] = [];

					}

					columns[col][row] = rows[row][col];

				}

			}

		}

		return columns;

	},

	

	/**

	 * A hook for working directly on the parsed columns

	 */

	parsed: function () {

		if (this.options.parsed) {

			return this.options.parsed.call(this, this.columns);

		}

	},



	getFreeIndexes: function (numberOfColumns, seriesBuilders) {

		var s,

			i,

			freeIndexes = [],

			freeIndexValues = [],

			referencedIndexes;



		// Add all columns as free

		for (i = 0; i < numberOfColumns; i = i + 1) {

			freeIndexes.push(true);

		}



		// Loop all defined builders and remove their referenced columns

		for (s = 0; s < seriesBuilders.length; s = s + 1) {

			referencedIndexes = seriesBuilders[s].getReferencedColumnIndexes();



			for (i = 0; i < referencedIndexes.length; i = i + 1) {

				freeIndexes[referencedIndexes[i]] = false;

			}

		}



		// Collect the values for the free indexes

		for (i = 0; i < freeIndexes.length; i = i + 1) {

			if (freeIndexes[i]) {

				freeIndexValues.push(i);

			}

		}



		return freeIndexValues;

	},

	

	/**

	 * If a complete callback function is provided in the options, interpret the 

	 * columns into a Highcharts options object.

	 */

	complete: function () {

		

		var columns = this.columns,

			xColumns = [],

			type,

			options = this.options,

			series,

			data,

			i,

			j,

			r,

			seriesIndex,

			chartOptions,

			allSeriesBuilders = [],

			builder,

			freeIndexes,

			typeCol,

			index;



		xColumns.length = columns.length;

		if (options.complete || options.afterComplete) {



			// Get the names and shift the top row

			for (i = 0; i < columns.length; i++) {

				if (this.firstRowAsNames) {

					columns[i].name = columns[i].shift();

				}

			}

			

			// Use the next columns for series

			series = [];

			freeIndexes = this.getFreeIndexes(columns.length, this.valueCount.seriesBuilders);



			// Populate defined series

			for (seriesIndex = 0; seriesIndex < this.valueCount.seriesBuilders.length; seriesIndex++) {

				builder = this.valueCount.seriesBuilders[seriesIndex];



				// If the builder can be populated with remaining columns, then add it to allBuilders

				if (builder.populateColumns(freeIndexes)) {

					allSeriesBuilders.push(builder);

				}

			}



			// Populate dynamic series

			while (freeIndexes.length > 0) {

				builder = new SeriesBuilder();

				builder.addColumnReader(0, 'x');

				

				// Mark index as used (not free)

				index = inArray(0, freeIndexes);

				if (index !== -1) {

					freeIndexes.splice(index, 1);

				}



				for (i = 0; i < this.valueCount.global; i++) {

					// Create and add a column reader for the next free column index

					builder.addColumnReader(undefined, this.valueCount.globalPointArrayMap[i]);

				}



				// If the builder can be populated with remaining columns, then add it to allBuilders

				if (builder.populateColumns(freeIndexes)) {

					allSeriesBuilders.push(builder);

				}

			}



			// Get the data-type from the first series x column

			if (allSeriesBuilders.length > 0 && allSeriesBuilders[0].readers.length > 0) {

				typeCol = columns[allSeriesBuilders[0].readers[0].columnIndex];

				if (typeCol !== undefined) {

					if (typeCol.isDatetime) {

						type = 'datetime';

					} else if (!typeCol.isNumeric) {

						type = 'category';

					}

				}

			}

			// Axis type is category, then the "x" column should be called "name"

			if (type === 'category') {

				for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {

					builder = allSeriesBuilders[seriesIndex];

					for (r = 0; r < builder.readers.length; r++) {

						if (builder.readers[r].configName === 'x') {

							builder.readers[r].configName = 'name';

						}

					}

				}

			}



			// Read data for all builders

			for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {

				builder = allSeriesBuilders[seriesIndex];



				// Iterate down the cells of each column and add data to the series

				data = [];

				for (j = 0; j < columns[0].length; j++) { // TODO: which column's length should we use here

					data[j] = builder.read(columns, j);

				}



				// Add the series

				series[seriesIndex] = {

					data: data

				};

				if (builder.name) {

					series[seriesIndex].name = builder.name;

				}

				if (type === 'category') {

					series[seriesIndex].turboThreshold = 0;

				}

			}







			// Do the callback

			chartOptions = {

				series: series

			};

			if (type) {

				chartOptions.xAxis = {

					type: type

				};

			}

			

			if (options.complete) {

				options.complete(chartOptions);

			}



			// The afterComplete hook is used internally to avoid conflict with the externally

			// available complete option.

			if (options.afterComplete) {

				options.afterComplete(chartOptions);

			}

		}

	}

	});

	

	// Register the Data prototype and data function on Highcharts

	Highcharts.Data = Data;

	Highcharts.data = function (options, chartOptions) {

		return new Data(options, chartOptions);

	};



	// Extend Chart.init so that the Chart constructor accepts a new configuration

	// option group, data.

	Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed, userOptions, callback) {

		var chart = this;



		if (userOptions && userOptions.data) {

			Highcharts.data(Highcharts.extend(userOptions.data, {



				afterComplete: function (dataOptions) {

					var i, series;

					

					// Merge series configs

					if (userOptions.hasOwnProperty('series')) {

						if (typeof userOptions.series === 'object') {

							i = Math.max(userOptions.series.length, dataOptions.series.length);

							while (i--) {

								series = userOptions.series[i] || {};

								userOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]);

							}

						} else { // Allow merging in dataOptions.series (#2856)

							delete userOptions.series;

						}

					}



					// Do the merge

					userOptions = Highcharts.merge(dataOptions, userOptions);



					proceed.call(chart, userOptions, callback);

				}

			}), userOptions);

		} else {

			proceed.call(chart, userOptions, callback);

		}

	});



	/**

	 * Creates a new SeriesBuilder. A SeriesBuilder consists of a number

	 * of ColumnReaders that reads columns and give them a name.

	 * Ex: A series builder can be constructed to read column 3 as 'x' and

	 * column 7 and 8 as 'y1' and 'y2'.

	 * The output would then be points/rows of the form {x: 11, y1: 22, y2: 33}

	 * 

	 * The name of the builder is taken from the second column. In the above

	 * example it would be the column with index 7.

	 * @constructor

	 */

	SeriesBuilder = function () {

		this.readers = [];

		this.pointIsArray = true;

	};



	/**

	 * Populates readers with column indexes. A reader can be added without

	 * a specific index and for those readers the index is taken sequentially

	 * from the free columns (this is handled by the ColumnCursor instance).

	 * @returns {boolean}

	 */

	SeriesBuilder.prototype.populateColumns = function (freeIndexes) {

		var builder = this,

			enoughColumns = true;



		// Loop each reader and give it an index if its missing.

		// The freeIndexes.shift() will return undefined if there

		// are no more columns.

		each(builder.readers, function (reader) {

			if (reader.columnIndex === undefined) {

				reader.columnIndex = freeIndexes.shift();

			}

		});



		// Now, all readers should have columns mapped. If not

		// then return false to signal that this series should

		// not be added.

		each(builder.readers, function (reader) {

			if (reader.columnIndex === undefined) {

				enoughColumns = false;

			}

		});



		return enoughColumns;

	};



	/**

	 * Reads a row from the dataset and returns a point or array depending

	 * on the names of the readers.

	 * @param columns

	 * @param rowIndex

	 * @returns {Array | Object}

	 */

	SeriesBuilder.prototype.read = function (columns, rowIndex) {

		var builder = this,

			pointIsArray = builder.pointIsArray,

			point = pointIsArray ? [] : {},

			columnIndexes;



		// Loop each reader and ask it to read its value.

		// Then, build an array or point based on the readers names.

		each(builder.readers, function (reader) {

			var value = columns[reader.columnIndex][rowIndex];

			if (pointIsArray) {

				point.push(value);

			} else {

				point[reader.configName] = value; 

			}

		});



		// The name comes from the first column (excluding the x column)

		if (this.name === undefined && builder.readers.length >= 2) {

			columnIndexes = builder.getReferencedColumnIndexes();

			if (columnIndexes.length >= 2) {

				// remove the first one (x col)

				columnIndexes.shift();



				// Sort the remaining

				columnIndexes.sort();



				// Now use the lowest index as name column

				this.name = columns[columnIndexes.shift()].name;

			}

		}



		return point;

	};



	/**

	 * Creates and adds ColumnReader from the given columnIndex and configName.

	 * ColumnIndex can be undefined and in that case the reader will be given

	 * an index when columns are populated.

	 * @param columnIndex {Number | undefined}

	 * @param configName

	 */

	SeriesBuilder.prototype.addColumnReader = function (columnIndex, configName) {

		this.readers.push({

			columnIndex: columnIndex, 

			configName: configName

		});



		if (!(configName === 'x' || configName === 'y' || configName === undefined)) {

			this.pointIsArray = false;

		}

	};



	/**

	 * Returns an array of column indexes that the builder will use when

	 * reading data.

	 * @returns {Array}

	 */

	SeriesBuilder.prototype.getReferencedColumnIndexes = function () {

		var i,

			referencedColumnIndexes = [],

			columnReader;

		

		for (i = 0; i < this.readers.length; i = i + 1) {

			columnReader = this.readers[i];

			if (columnReader.columnIndex !== undefined) {

				referencedColumnIndexes.push(columnReader.columnIndex);

			}

		}



		return referencedColumnIndexes;

	};



	/**

	 * Returns true if the builder has a reader for the given configName.

	 * @param configName

	 * @returns {boolean}

	 */

	SeriesBuilder.prototype.hasReader = function (configName) {

		var i, columnReader;

		for (i = 0; i < this.readers.length; i = i + 1) {

			columnReader = this.readers[i];

			if (columnReader.configName === configName) {

				return true;

			}

		}

		// Else return undefined

	};







}(Highcharts));

