/**
 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

/**
 * @fileOverview Charts for CKEditor using Chart.js.
 */

/* global alert:false, Chart:false */

'use strict';

// TODO IE8 fallback to a table maybe?
// TODO a11y http://www.w3.org/html/wg/wiki/Correct_Hidden_Attribute_Section_v4
( function() {
	CKEDITOR.plugins.add( 'chart', {
		// Required plugins
		requires: 'widget,dialog',
		// Name of the file in the "icons" folder
		icons: 'chart',
		// Supported languages
		lang: 'en,pl,ru',

		// Load library that renders charts inside CKEditor, if Chart object is not already available.
		afterInit: function() {
			var plugin = this;

			if ( typeof Chart  === 'undefined' ) {
				// Chart library is loaded asynchronously, so we can draw anything only once it's loaded.
				CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( plugin.path + 'lib/chart.min.js' ), function() {
					plugin.drawCharts();
				} );
			}
		},

		// Function called on initialization of every editor instance created in the page.
		init: function( editor ) {
			var plugin = this;
			var chartDefaultHeight = editor.config.chart_height || 300;
			// Default hardcoded values used if config.chart_colors is not provided.
			var colors = editor.config.chart_colors ||
			{
				// Colors for Bar/Line chart: http://www.chartjs.org/docs/#bar-chart-data-structure
				fillColor: 'rgba(151,187,205,0.5)',
				strokeColor: 'rgba(151,187,205,0.8)',
				highlightFill: 'rgba(151,187,205,0.75)',
				highlightStroke: 'rgba(151,187,205,1)',
				// Colors for Doughnut/Pie/PolarArea charts: http://www.chartjs.org/docs/#doughnut-pie-chart-data-structure
				data: [ '#B33131', '#B66F2D', '#B6B330', '#71B232', '#33B22D', '#31B272', '#2DB5B5', '#3172B6', '#3232B6', '#6E31B2', '#B434AF', '#B53071' ]
			};
			var config = {
				Bar: editor.config.chart_configBar || { animation: false },
				Doughnut: editor.config.chart_configDoughnut || { animateRotate: false },
				Line: editor.config.chart_configLine || { animation: false },
				Pie: editor.config.chart_configPie || { animateRotate: false },
				PolarArea: editor.config.chart_configPolarArea || { animateRotate: false }
			};
			// The number of rows in Edit Chart dialog window.
			var inputRows = editor.config.chart_maxItems || 8;

			// Inject required CSS stylesheet to classic editors because the <iframe> needs it.
			// Inline editors will ignore this, the developer is supposed to load chart.css directly on a page.
			// "this.path" is a path to the current plugin.
			editor.addContentsCss( CKEDITOR.getUrl( plugin.path + 'chart.css' ) );

			// A little bit of magic to support "Preview" feature in CKEditor (in a popup).
			// In order to transform downcasted widgets into nice charts we need to:
			// 1. Pass color settings and charts configuration through JSON.
			// 2. Load the Chart.js library
			// 3. Load a helper script that will "upcast" widgets and initiate charts.
			editor.on( 'contentPreview', function( evt ) {
				evt.data.dataValue = evt.data.dataValue.replace( /<\/head>/,
					'<script>var chartjs_colors_json = "' + JSON.stringify( colors ).replace( /\"/g, '\\"' ) + '";<\/script>' +
					'<script>var chartjs_config_json = "' + JSON.stringify( config ).replace( /\"/g, '\\"' ) + '";<\/script>' +
						'<script src="' + CKEDITOR.getUrl( plugin.path + 'lib/chart.min.js' ) + '"><\/script>' +
						'<script src="' + CKEDITOR.getUrl( plugin.path + 'widget2chart.js' ) + '"><\/script><\/head>' );
			} );

			// The dialog window to insert / edit a chart.
			CKEDITOR.dialog.add( 'chart', function( editor ) {
				var dialog = {
					title: editor.lang.chart.dialogTitle,
					minWidth: 200,
					minHeight: 100,
					// Executed every time a dialog is shown.
					onShow: function() {
						var widget = editor.widgets.focused;
						if ( !widget )
							return;
						// We edit an existing widget, so we have already some data and should set input values accordingly.
						// The dialog consists of multiple rows with two input elements each.
						// We could use "setup" callbacks for each UI element, but the we'd end up with lots of data properties.
						// So instead we merge all the values into a single object, ending with an array like:
						// [ {"value":45,"label":"Yes"}, {}, .... ]
						// to make it easier to pass it to Chart.js later.
						for ( var j = 0; j < inputRows; j++ ) {
							if ( widget.data.values[j] ) {
								// toString() is used here to set correctly zero values.
								this.setValueOf( 'data', 'value' + j, widget.data.values[j].value.toString() );
								this.setValueOf( 'data', 'label' + j, widget.data.values[j].label );
							}
						}
					},
					// Executed every time a dialog is closed (OK is pressed).
					onOk: function() {
						// ATTENTION: this.widget is not available here in CKEditor by default.
						// We added this in the "init" function of a widget ("Pass the reference to this widget to the dialog."),
						var widget = this.widget,
							values = [], value;

						// We could use "commit" callbacks in every input element to set widget data.
						// But we decided to keep multiple values in a single object (see comment in "onShow" for more details).
						for ( var j = 0; j < inputRows; j++ ) {
							value = this.getValueOf( 'data', 'value' + j );
							if ( value )
								values.push( { value: parseFloat( this.getValueOf( 'data', 'value' + j ) ), label: this.getValueOf( 'data', 'label' + j ) } );
						}
						widget.setData( 'values', values );
						widget.setData( 'chart', this.getValueOf( 'data', 'chart' ) );
						widget.setData( 'height', this.getValueOf( 'data', 'height' ) );
					},
					// Define elements in a dialog window.
					contents: [
						{
							id: 'data',
							elements: [
								{
									type: 'hbox',
									children:
										[
											{
												id: 'chart',
												type: 'select',
												label: editor.lang.chart.chartType,
												labelLayout: 'horizontal',
												// Align vertically, otherwise labels are a bit misplaced.
												labelStyle: 'display:block;padding: 4px 6px;',
												items: [
													[ editor.lang.chart.bar, 'bar' ],
													[ editor.lang.chart.line, 'line' ],
													[ editor.lang.chart.pie, 'pie' ],
													[ editor.lang.chart.polar, 'polar' ],
													[ editor.lang.chart.doughnut, 'doughnut' ]
												],
												style: 'margin-bottom:10px',
												setup: function( widget ) {
													// Set radios to the correct value based on the widget type.
													this.setValue( widget.data.chart );
												}
											},
											{
												id: 'height',
												type: 'text',
												label: editor.lang.chart.height,
												labelLayout: 'horizontal',
												// Align vertically, otherwise labels are a bit misplaced.
												labelStyle: 'display:block;padding: 4px 6px;',
												width: '50px',
												setup: function( widget ) {
													this.setValue( widget.data.height );
												},
												validate: function() {
													var value = this.getValue(),
														pass = ( !value || !!( CKEDITOR.dialog.validate.number( value ) && value >= 0 ) );

													if ( !pass ) {
														alert( editor.lang.common.validateNumberFailed );
														this.select();
													}

													return pass;
												}
											}
										]
								}
							]
						}
					]
				};
				// Rarely elements in dialog definitions are generated in loops.
				// Here we decided to make the number of "data" rows configurable, so a loop is handy.
				for ( var i = 0; i < inputRows; i++ ) {
					dialog.contents[0].elements.push( {
						type: 'hbox',
						children:
							[
								{
									id: 'value' + i,
									type: 'text',
									labelLayout: 'horizontal',
									label: editor.lang.chart.value,
									// Align vertically, otherwise labels are a bit misplaced.
									labelStyle: 'display:block;padding: 4px 6px;',
									width: '50px',
									validate: function() {
										var value = this.getValue(),
											pass = ( !value || !!( CKEDITOR.dialog.validate.number( value ) && value >= 0 ) );

										if ( !pass ) {
											alert( editor.lang.common.validateNumberFailed );
											this.select();
										}

										return pass;
									}
								},
								{
									id: 'label' + i,
									type: 'text',
									label: editor.lang.chart.label,
									labelLayout: 'horizontal',
									// Align vertically, otherwise labels are a bit misplaced.
									labelStyle: 'display:block;padding: 4px 6px;',
									width: '200px'
								}
							]
					} );
				}
				return dialog;
			} );

			// Helper function that we'd like to run in case Chart.js library was loaded asynchronously.
			this.drawCharts = function() {
				// All available widgets are stored in an object, not an array.
				for ( var id in editor.widgets.instances ) {
					// The name was provided in editor.widgets.add()
					if ( editor.widgets.instances[id].name == 'chart' ) {
						// Our "data" callback draws widgets, so let's call it.
						editor.widgets.instances[id].fire( 'data' );
					}
				}
			};

			function renderChart( canvas, data, legend ) {
				var values = data.values,
					chartType = data.chart;

				// The code below is the same as in widget2chart.js.
				// ########## RENDER CHART START ##########
				// Prepare canvas and chart instance.
				var i, ctx = canvas.getContext( '2d' ),
					chart = new Chart( ctx ); // jshint ignore:line

				// Set some extra required colors by Pie/Doughnut charts.
				// Ugly charts will be drawn if colors are not provided for each data.
				// http://www.chartjs.org/docs/#doughnut-pie-chart-data-structure
				if ( chartType != 'bar' ) {
					for ( i = 0; i < values.length; i++ ) {
						values[i].color = colors.data[i];
						values[i].highlight = colors.data[i];
					}
				}

				// Prepare data for bar/line charts.
				if ( chartType == 'bar' || chartType == 'line' ) {
					var data = {
						// Chart.js supports multiple datasets.
						// http://www.chartjs.org/docs/#bar-chart-data-structure
						// This plugin is simple, so it supports just one.
						// Need more features? Create a Pull Request :-)
						datasets: [
							{
								label: '',
								fillColor: colors.fillColor,
								strokeColor: colors.strokeColor,
								highlightFill: colors.highlightFill,
								highlightStroke: colors.highlightStroke,
								data: []
							} ],
						labels: []
					};
					// Bar charts accept different data format than Pie/Doughnut.
					// We need to pass values inside datasets[0].data.
					for ( i = 0; i < values.length; i++ ) {
						if ( values[i].value ) {
							data.labels.push( values[i].label );
							data.datasets[0].data.push( values[i].value );
						}
					}
					// Legend makes sense only with more than one dataset.
					legend.innerHTML = '';
				}

				// Render Bar chart.
				if ( chartType == 'bar' ) {
					chart.Bar( data, config.Bar );
				}
				// Render Line chart.
				else if ( chartType == 'line' ) {
					chart.Line( data, config.Line );
				}
				// Render Line chart.
				else if ( chartType == 'polar' ) {
					//chart.PolarArea( values );
					legend.innerHTML = chart.PolarArea( values, config.PolarArea ).generateLegend();
				}
				// Render Pie chart and legend.
				else if ( chartType == 'pie' ) {
					legend.innerHTML = chart.Pie( values, config.Pie ).generateLegend();
				}
				// Render Doughnut chart and legend.
				else {
					legend.innerHTML = chart.Doughnut( values, config.Doughnut ).generateLegend();
				}
				// ########## RENDER CHART END ##########
			}

			// Here we define the widget itself.
			editor.widgets.add( 'chart', {
				// The *label* for the button. The button *name* is assigned automatically based on the widget name.
				button: editor.lang.chart.chart,
				// Connect widget with a dialog defined earlier. So our toolbar button will open a dialog window.
				dialog: 'chart',
				// Based on this template a widget will be created automatically once user exits the dialog window.
				template: '<div class="chartjs" data-chart="bar" data-chart-height="' + chartDefaultHeight + '"><canvas height="' + chartDefaultHeight + '"></canvas><div class="chartjs-legend"></div></div>',
				// In order to provide styles (classes) for this widget through config.stylesSet we need to explicitly define the stylable elements.
				styleableElements: 'div',
				// Name to be displayed in the elements path (at the bottom of CKEditor),
				pathName: 'chart',

				// Run when initializing widget (thank you, captain obvious!).
				// It is common to use the init method to populate widget data with information loaded from the DOM.
				init: function() {
					// When an empty widget is initialized after clicking a button in the toolbar, we do not have yet chart values.
					if ( this.element.data( 'chart-value' ) ) {
						this.setData( 'values', JSON.parse( this.element.data( 'chart-value' ) ) );
					}
					// Chart is specified in a template, so it is available even in an empty widget.
					this.setData( 'chart', this.element.data( 'chart' ) );
					// Height is specified in a template, so it is available even in an empty widget.
					this.setData( 'height', this.element.data( 'chart-height' ) );

					// Pass the reference to this widget to the dialog. See "onOk" in the dialog definition, we needed widget there.
					this.on( 'dialog', function( evt ) {
						evt.data.widget = this;
					}, this );
				},

				// Run when widget data is changed (widget is rendered for the first time, inserted, changed).
				data: function() {
					// Just in case Chart.js was loaded asynchronously and is not available yet.
					if ( typeof Chart === 'undefined' )
						return;

					// It's hard to draw a chart without numbers.
					if ( !this.data.values )
						return;

					// It looks like Chart.js does not handle well updating charts.
					// When hovering over updated canvas old data is picked up sometimes, so we need to always replace an old canvas.
					var canvas = editor.document.createElement( 'canvas', { attributes: { height: this.data.height } } );
					canvas.replace( this.element.getChild( 0 ) );

					// Unify variable names with the one used in widget2chart.js.
					var legend = this.element.getChild( 1 ).$;
					canvas = canvas.$;

					// IE8 can't handle the next part (without the help of excanvas etc.).
					if ( !canvas.getContext )
						return;

					var data = this.data;
					// Without timeout the chart does not render immediately after inserting into the editing area.
					setTimeout( function() { renderChart( canvas, data, legend ) }, 0 );
				},

				// ACF settings. Without allowing elements introduced by this plugin, CKEditor built-in filter would remove it.
				allowedContent: 'div(!chartjs)[data-*];',
				requiredContent: 'div(chartjs)[data-chart-value,data-chart,data-chart-height]',

				// Executed when CKEditor loads content, when switching from source to wysiwyg mode. Makes HTML content a widget.
				upcast: function( element ) {
					if ( element.name == 'div' && element.hasClass( 'chartjs' ) ) {
						// Downcasted <div> could have contained some text like "chart" or &nbsp; which was there just to prevent <div>s from being deleted.
						// Get rid of it when upcasting.
						element.setHtml( '' );
						// Chart.js work on canvas elements, Prepare one.
						var canvas = new CKEDITOR.htmlParser.element( 'canvas', { height: element.attributes[ 'data-chart-height' ] } );
						element.add( canvas );
						// And make place for a legend.
						var div = new CKEDITOR.htmlParser.element( 'div', { 'class': 'chartjs-legend' } );
						element.add( div );
						return element;
					}
				},

				// Executed when CKEditor returns content, when switching from wysiwyg to source mode. Transforms a widget back to a downcasted form.
				downcast: function( element ) {
					var data = [];

					// Should not happen unless someone has accidentally messed up ACF rules.
					if ( !this.data.values )
						return;

					for ( var i = 0; i < this.data.values.length; i++ ) {
						// Get data from widget into an object in order to save it as data-chart-value attribute.
						// We could simply save this.data.values, but it contains some additional temporary data which we want to skip (like colors).
						data.push( {
							value: this.data.values[i].value,
							label: this.data.values[i].label
						} );
					}

					// Create the downcasted form of a widget (a simple <div>).
					var el = new CKEDITOR.htmlParser.element( 'div', {
						// We could pass here hardcoded "chartjs" class, but this way we would lose here all the classes applied through the Styles dropdown.
						// (In case someone defined his own styles for the chart widget)
						'class': element.attributes['class'],
						'data-chart': this.data.chart,
						'data-chart-height': this.data.height,
						// Feature detection (editor.getSelectedHtml) to check if CKEditor 4.5+ is used.
						// CKEditor < 4.5 and CKEditor 4.5+ require different code due to https://dev.ckeditor.com/ticket/13105
						'data-chart-value': editor.getSelectedHtml ? JSON.stringify( data ) : CKEDITOR.tools.htmlEncodeAttr( JSON.stringify( data ) )
					} );
					return el;
				}
			} );
		}
	} );
} )();

/**
 * The default chart height (in pixels) in the Edit Chart dialog window.
 *
 *		// Set default height to 400px.
 *		config.chart_height = 400;
 *
 * @cfg {Integer} [chart_height=300]
 * @member CKEDITOR.config
 */

/**
 * The number of rows (items to enter) in the Edit Chart dialog window.
 *
 *		// Set number of rows to 12.
 *		config.chart_maxItems = 12;
 *
 * @cfg {Integer} [chart_maxItems=12]
 * @member CKEDITOR.config
 */

/**
 * Colors used to draw charts. See <a href="http://www.chartjs.org/docs/#bar-chart-data-structure">Bar chart data structure</a> and
 * <a href="http://www.chartjs.org/docs/#doughnut-pie-chart-data-structure">Pie chart data structure</a>.
 *
 *		config.chart_colors =
 *		{
 *			// Colors for Bar/Line chart.
 *			fillColor: 'rgba(151,187,205,0.5)',
 *			strokeColor: 'rgba(151,187,205,0.8)',
 *			highlightFill: 'rgba(151,187,205,0.75)',
 *			highlightStroke: 'rgba(151,187,205,1)',
 *			// Colors for Doughnut/Pie/PolarArea charts.
 *			data: [ '#B33131', '#B66F2D', '#B6B330', '#71B232', '#33B22D', '#31B272', '#2DB5B5', '#3172B6', '#3232B6', '#6E31B2', '#B434AF', '#B53071' ]
 *		}
 *
 * @cfg {Array} chart_colors
 * @member CKEDITOR.config
 */

/**
 * Chart.js configuration to use for Bar charts.
 *
 * @cfg {Object} [chart_configBar={ animation: false }]
 * @member CKEDITOR.config
 */

/**
 * Chart.js configuration to use for Doughnut charts.
 *
 * @cfg {Object} [chart_configDoughnut={ animateRotate: false }]
 * @member CKEDITOR.config
 */

/**
 * Chart.js configuration to use for Line charts.
 *
 * @cfg {Object} [chart_configLine={ animation: false }]
 * @member CKEDITOR.config
 */

/**
 * Chart.js configuration to use for Pie charts.
 *
 * @cfg {Object} [chart_configPie={ animateRotate: false }]
 * @member CKEDITOR.config
 */

/**
 * Chart.js configuration to use for PolarArea charts.
 *
 * @cfg {Object} [chart_configPolarArea={ animateRotate: false }]
 * @member CKEDITOR.config
 */
