# Source: Highchart.js

    "use strict";

    // Import external names locally
    var Shared,
    	Collection,
    	CollectionInit,
    	Overload;

    Shared = require('./Shared');
    Overload = require('./Overload');

    /**
     * The constructor.
     *
     * @constructor
     */
    var Highchart = function (collection, options) {
    	this.init.apply(this, arguments);
    };

    Highchart.prototype.init = function (collection, options) {
    	this._options = options;
    	this._selector = window.jQuery(this._options.selector);

    	if (!this._selector[0]) {
    		throw(this.classIdentifier() + ' "' + collection.name() + '": Chart target element does not exist via selector: ' + this._options.selector);
    	}

    	this._listeners = {};
    	this._collection = collection;

    	// Setup the chart
    	this._options.series = [];

    	// Disable attribution on highcharts
    	options.chartOptions = options.chartOptions || {};
    	options.chartOptions.credits = false;

    	// Set the data for the chart
    	var data,
    		seriesObj,
    		chartData;

    	switch (this._options.type) {
    		case 'pie':
    			// Create chart from data
    			this._selector.highcharts(this._options.chartOptions);
    			this._chart = this._selector.highcharts();

    			// Generate graph data from collection data
    			data = this._collection.find();

    			seriesObj = {
    				allowPointSelect: true,
    				cursor: 'pointer',
    				dataLabels: {
    					enabled: true,
    					format: '<b>{point.name}</b>: {y} ({point.percentage:.0f}%)',
    					style: {
    						color: (window.Highcharts.theme && window.Highcharts.theme.contrastTextColor) || 'black'
    					}
    				}
    			};

    			chartData = this.pieDataFromCollectionData(data, this._options.keyField, this._options.valField);

    			window.jQuery.extend(seriesObj, this._options.seriesOptions);

    			window.jQuery.extend(seriesObj, {
    				name: this._options.seriesName,
    				data: chartData
    			});

    			this._chart.addSeries(seriesObj, true, true);
    			break;

    		case 'line':
    		case 'area':
    		case 'column':
    		case 'bar':
    			// Generate graph data from collection data
    			chartData = this.seriesDataFromCollectionData(
    				this._options.seriesField,
    				this._options.keyField,
    				this._options.valField,
    				this._options.orderBy,
    				this._options
    			);

    			this._options.chartOptions.xAxis = chartData.xAxis;
    			this._options.chartOptions.series = chartData.series;

    			this._selector.highcharts(this._options.chartOptions);
    			this._chart = this._selector.highcharts();
    			break;

    		default:
    			throw(this.classIdentifier() + ' "' + collection.name() + '": Chart type specified is not currently supported by ForerunnerDB: ' + this._options.type);
    	}

    	// Hook the collection events to auto-update the chart
    	this._hookEvents();
    };

    Shared.addModule('Highchart', Highchart);

    Collection = Shared.modules.Collection;
    CollectionInit = Collection.prototype.init;

    Shared.mixin(Highchart.prototype, 'Mixin.Common');
    Shared.mixin(Highchart.prototype, 'Mixin.Events');

    /**
     * Gets / sets the current state.
     * @param {String=} val The name of the state to set.
     * @returns {*}
     */
    Shared.synthesize(Highchart.prototype, 'state');

    /**
     * Generate pie-chart series data from the given collection data array.
     * @param data
     * @param keyField
     * @param valField
     * @returns {Array}
     */
    Highchart.prototype.pieDataFromCollectionData = function (data, keyField, valField) {
    	var graphData = [],
    		i;

    	for (i = 0; i < data.length; i++) {
    		graphData.push([data[i][keyField], data[i][valField]]);
    	}

    	return graphData;
    };

    /**
     * Generate line-chart series data from the given collection data array.
     * @param seriesField
     * @param keyField
     * @param valField
     * @param orderBy
     */
    Highchart.prototype.seriesDataFromCollectionData = function (seriesField, keyField, valField, orderBy, options) {
    	var data = this._collection.distinct(seriesField),
    		seriesData = [],
    		xAxis = options && options.chartOptions && options.chartOptions.xAxis ? options.chartOptions.xAxis : {
    			categories: []
    		},
    		seriesName,
    		query,
    		dataSearch,
    		seriesValues,
    		sData,
    		i, k;

    	// What we WANT to output:
    	/*series: [{
    		name: 'Responses',
    		data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6]
    	}]*/

    	// Loop keys
    	for (i = 0; i < data.length; i++) {
    		seriesName = data[i];
    		query = {};
    		query[seriesField] = seriesName;

    		seriesValues = [];
    		dataSearch = this._collection.find(query, {
    			orderBy: orderBy
    		});

    		// Loop the keySearch data and grab the value for each item
    		for (k = 0; k < dataSearch.length; k++) {
    			if (xAxis.categories) {
    				xAxis.categories.push(dataSearch[k][keyField]);
    				seriesValues.push(dataSearch[k][valField]);
    			} else {
    				seriesValues.push([dataSearch[k][keyField], dataSearch[k][valField]]);
    			}
    		}

    		sData = {
    			name: seriesName,
    			data: seriesValues
    		};

    		if (options.seriesOptions) {
    			for (k in options.seriesOptions) {
    				if (options.seriesOptions.hasOwnProperty(k)) {
    					sData[k] = options.seriesOptions[k];
    				}
    			}
    		}

    		seriesData.push(sData);
    	}

    	return {
    		xAxis: xAxis,
    		series: seriesData
    	};
    };

    /**
     * Hook the events the chart needs to know about from the internal collection.
     * @private
     */
    Highchart.prototype._hookEvents = function () {
    	var self = this;

    	self._collection.on('change', function () {
    		self._changeListener.apply(self, arguments);
    	});

    	// If the collection is dropped, clean up after ourselves
    	self._collection.on('drop', function () {
    		self.drop.apply(self);
    	});
    };

    /**
     * Handles changes to the collection data that the chart is reading from and then
     * updates the data in the chart display.
     * @private
     */
    Highchart.prototype._changeListener = function () {
    	var self = this;

    	// Update the series data on the chart
    	if (typeof self._collection !== 'undefined' && self._chart) {
    		var data = self._collection.find(),
    			i;

    		switch (self._options.type) {
    			case 'pie':
    				self._chart.series[0].setData(
    					self.pieDataFromCollectionData(
    						data,
    						self._options.keyField,
    						self._options.valField
    					),
    					true,
    					true
    				);
    				break;

    			case 'bar':
    			case 'line':
    			case 'area':
    			case 'column':
    				var seriesData = self.seriesDataFromCollectionData(
    					self._options.seriesField,
    					self._options.keyField,
    					self._options.valField,
    					self._options.orderBy,
    					self._options
    				);

    				if (seriesData.xAxis.categories) {
    					self._chart.xAxis[0].setCategories(
    						seriesData.xAxis.categories
    					);
    				}

    				for (i = 0; i < seriesData.series.length; i++) {
    					if (self._chart.series[i]) {
    						// Series exists, set it's data
    						self._chart.series[i].setData(
    							seriesData.series[i].data,
    							true,
    							true
    						);
    					} else {
    						// Series data does not yet exist, add a new series
    						self._chart.addSeries(
    							seriesData.series[i],
    							true,
    							true
    						);
    					}
    				}
    				break;

    			default:
    				break;
    		}
    	}
    };

    /**
     * Destroys the chart and all internal references.
     * @returns {Boolean}
     */
    Highchart.prototype.drop = function (callback) {
    	if (!this.isDropped()) {
    		this._state = 'dropped';

    		if (this._chart) {
    			this._chart.destroy();
    		}

    		if (this._collection) {
    			this._collection.off('change', this._changeListener);
    			this._collection.off('drop', this.drop);

    			if (this._collection._highcharts) {
    				delete this._collection._highcharts[this._options.selector];
    			}
    		}

    		delete this._chart;
    		delete this._options;
    		delete this._collection;

    		this.emit('drop', this);

    		if (callback) {
    			callback(false, true);
    		}

    		delete this._listeners;

    		return true;
    	} else {
    		return true;
    	}
    };

    // Extend collection with highchart init
    Collection.prototype.init = function () {
    	this._highcharts = {};
    	CollectionInit.apply(this, arguments);
    };

    /**
     * Creates a pie chart from the collection.
     * @type {Overload}
     */
    Collection.prototype.pieChart = new Overload({
    	/**
    	 * Chart via options object.
    	 * @func pieChart
    	 * @memberof Highchart
    	 * @param {Object} options The options object.
    	 * @returns {*}
    	 */
    	'object': function (options) {
    		options.type = 'pie';

    		options.chartOptions = options.chartOptions || {};
    		options.chartOptions.chart = options.chartOptions.chart || {};
    		options.chartOptions.chart.type = 'pie';

    		if (!this._highcharts[options.selector]) {
    			// Store new chart in charts array
    			this._highcharts[options.selector] = new Highchart(this, options);
    		}

    		return this._highcharts[options.selector];
    	},

    	/**
    	 * Chart via defined params and an options object.
    	 * @func pieChart
    	 * @memberof Highchart
    	 * @param {String|jQuery} selector The element to render the chart to.
    	 * @param {String} keyField The field to use as the data key.
    	 * @param {String} valField The field to use as the data value.
    	 * @param {String} seriesName The name of the series to display on the chart.
    	 * @param {Object} options The options object.
    	 */
    	'*, string, string, string, ...': function (selector, keyField, valField, seriesName, options) {
    		options = options || {};

    		options.selector = selector;
    		options.keyField = keyField;
    		options.valField = valField;
    		options.seriesName = seriesName;

    		// Call the main chart method
    		this.pieChart(options);
    	}
    });

    /**
     * Creates a line chart from the collection.
     * @type {Overload}
     */
    Collection.prototype.lineChart = new Overload({
    	/**
    	 * Chart via selector.
    	 * @func lineChart
    	 * @memberof Highchart
    	 * @param {String} selector The chart selector.
    	 * @returns {*}
    	 */
    	'string': function (selector) {
    		return this._highcharts[selector];
    	},

    	/**
    	 * Chart via options object.
    	 * @func lineChart
    	 * @memberof Highchart
    	 * @param {Object} options The options object.
    	 * @returns {*}
    	 */
    	'object': function (options) {
    		options.type = 'line';

    		options.chartOptions = options.chartOptions || {};
    		options.chartOptions.chart = options.chartOptions.chart || {};
    		options.chartOptions.chart.type = 'line';

    		if (!this._highcharts[options.selector]) {
    			// Store new chart in charts array
    			this._highcharts[options.selector] = new Highchart(this, options);
    		}

    		return this._highcharts[options.selector];
    	},

    	/**
    	 * Chart via defined params and an options object.
    	 * @func lineChart
    	 * @memberof Highchart
    	 * @param {String|jQuery} selector The element to render the chart to.
    	 * @param {String} seriesField The name of the series to plot.
    	 * @param {String} keyField The field to use as the data key.
    	 * @param {String} valField The field to use as the data value.
    	 * @param {Object} options The options object.
    	 */
    	'*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) {
    		options = options || {};

    		options.seriesField = seriesField;
    		options.selector = selector;
    		options.keyField = keyField;
    		options.valField = valField;

    		// Call the main chart method
    		this.lineChart(options);
    	}
    });

    /**
     * Creates an area chart from the collection.
     * @type {Overload}
     */
    Collection.prototype.areaChart = new Overload({
    	/**
    	 * Chart via options object.
    	 * @func areaChart
    	 * @memberof Highchart
    	 * @param {Object} options The options object.
    	 * @returns {*}
    	 */
    	'object': function (options) {
    		options.type = 'area';

    		options.chartOptions = options.chartOptions || {};
    		options.chartOptions.chart = options.chartOptions.chart || {};
    		options.chartOptions.chart.type = 'area';

    		if (!this._highcharts[options.selector]) {
    			// Store new chart in charts array
    			this._highcharts[options.selector] = new Highchart(this, options);
    		}

    		return this._highcharts[options.selector];
    	},

    	/**
    	 * Chart via defined params and an options object.
    	 * @func areaChart
    	 * @memberof Highchart
    	 * @param {String|jQuery} selector The element to render the chart to.
    	 * @param {String} seriesField The name of the series to plot.
    	 * @param {String} keyField The field to use as the data key.
    	 * @param {String} valField The field to use as the data value.
    	 * @param {Object} options The options object.
    	 */
    	'*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) {
    		options = options || {};

    		options.seriesField = seriesField;
    		options.selector = selector;
    		options.keyField = keyField;
    		options.valField = valField;

    		// Call the main chart method
    		this.areaChart(options);
    	}
    });

    /**
     * Creates a column chart from the collection.
     * @type {Overload}
     */
    Collection.prototype.columnChart = new Overload({
    	/**
    	 * Chart via options object.
    	 * @func columnChart
    	 * @memberof Highchart
    	 * @param {Object} options The options object.
    	 * @returns {*}
    	 */
    	'object': function (options) {
    		options.type = 'column';

    		options.chartOptions = options.chartOptions || {};
    		options.chartOptions.chart = options.chartOptions.chart || {};
    		options.chartOptions.chart.type = 'column';

    		if (!this._highcharts[options.selector]) {
    			// Store new chart in charts array
    			this._highcharts[options.selector] = new Highchart(this, options);
    		}

    		return this._highcharts[options.selector];
    	},

    	/**
    	 * Chart via defined params and an options object.
    	 * @func columnChart
    	 * @memberof Highchart
    	 * @param {String|jQuery} selector The element to render the chart to.
    	 * @param {String} seriesField The name of the series to plot.
    	 * @param {String} keyField The field to use as the data key.
    	 * @param {String} valField The field to use as the data value.
    	 * @param {Object} options The options object.
    	 */
    	'*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) {
    		options = options || {};

    		options.seriesField = seriesField;
    		options.selector = selector;
    		options.keyField = keyField;
    		options.valField = valField;

    		// Call the main chart method
    		this.columnChart(options);
    	}
    });

    /**
     * Creates a bar chart from the collection.
     * @type {Overload}
     */
    Collection.prototype.barChart = new Overload({
    	/**
    	 * Chart via options object.
    	 * @func barChart
    	 * @memberof Highchart
    	 * @param {Object} options The options object.
    	 * @returns {*}
    	 */
    	'object': function (options) {
    		options.type = 'bar';

    		options.chartOptions = options.chartOptions || {};
    		options.chartOptions.chart = options.chartOptions.chart || {};
    		options.chartOptions.chart.type = 'bar';

    		if (!this._highcharts[options.selector]) {
    			// Store new chart in charts array
    			this._highcharts[options.selector] = new Highchart(this, options);
    		}

    		return this._highcharts[options.selector];
    	},

    	/**
    	 * Chart via defined params and an options object.
    	 * @func barChart
    	 * @memberof Highchart
    	 * @param {String|jQuery} selector The element to render the chart to.
    	 * @param {String} seriesField The name of the series to plot.
    	 * @param {String} keyField The field to use as the data key.
    	 * @param {String} valField The field to use as the data value.
    	 * @param {Object} options The options object.
    	 */
    	'*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) {
    		options = options || {};

    		options.seriesField = seriesField;
    		options.selector = selector;
    		options.keyField = keyField;
    		options.valField = valField;

    		// Call the main chart method
    		this.barChart(options);
    	}
    });

    /**
     * Creates a stacked bar chart from the collection.
     * @type {Overload}
     */
    Collection.prototype.stackedBarChart = new Overload({
    	/**
    	 * Chart via options object.
    	 * @func stackedBarChart
    	 * @memberof Highchart
    	 * @param {Object} options The options object.
    	 * @returns {*}
    	 */
    	'object': function (options) {
    		options.type = 'bar';

    		options.chartOptions = options.chartOptions || {};
    		options.chartOptions.chart = options.chartOptions.chart || {};
    		options.chartOptions.chart.type = 'bar';

    		options.plotOptions = options.plotOptions || {};
    		options.plotOptions.series = options.plotOptions.series || {};
    		options.plotOptions.series.stacking = options.plotOptions.series.stacking || 'normal';

    		if (!this._highcharts[options.selector]) {
    			// Store new chart in charts array
    			this._highcharts[options.selector] = new Highchart(this, options);
    		}

    		return this._highcharts[options.selector];
    	},

    	/**
    	 * Chart via defined params and an options object.
    	 * @func stackedBarChart
    	 * @memberof Highchart
    	 * @param {String|jQuery} selector The element to render the chart to.
    	 * @param {String} seriesField The name of the series to plot.
    	 * @param {String} keyField The field to use as the data key.
    	 * @param {String} valField The field to use as the data value.
    	 * @param {Object} options The options object.
    	 */
    	'*, string, string, string, ...': function (selector, seriesField, keyField, valField, options) {
    		options = options || {};

    		options.seriesField = seriesField;
    		options.selector = selector;
    		options.keyField = keyField;
    		options.valField = valField;

    		// Call the main chart method
    		this.stackedBarChart(options);
    	}
    });

    /**
     * Removes a chart from the page by it's selector.
     * @memberof Collection
     * @param {String} selector The chart selector.
     */
    Collection.prototype.dropChart = function (selector) {
    	if (this._highcharts && this._highcharts[selector]) {
    		this._highcharts[selector].drop();
    	}
    };

    Shared.finishModule('Highchart');
    module.exports = Highchart;