/**
 * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */

/* exported CKCONSOLE */

'use strict';

var CKCONSOLE = ( function() {
	var	that = {
			container: null,
			definitions: {},

			add: function( consoleName, definition ) {
				this.definitions[ consoleName ] = definition;
				definition.name = consoleName;
			},

			create: function( consoleName, config ) {
				var editor = config.editor,
					definition = CKEDITOR.tools.prototypedCopy( this.definitions[ consoleName ] );

				if ( typeof editor == 'string' )
					editor = CKEDITOR.instances[ editor ];

				createConsole( editor, definition, config );
			},

			panels: {
				box: panelBox,
				log: panelLog
			}
		};

	function createConsole( editor, definition, config ) {
		var container = createContainer(),
			editorPanel = createEditorPanel( editor, container, definition, config ),
			panels = [],
			panelDefinition,
			panel;

		for ( var i = 0; i < definition.panels.length; ++i ) {
			panelDefinition = definition.panels[ i ];
			panel = new that.panels[ panelDefinition.type ]( editor, editorPanel, panelDefinition );

			if ( panel.refreshOn )
				setupPanelRefresh( editor, panel );

			panels.push( panel );
		}

		editor.on( 'focus', function() {
			editorPanel.addClass( 'ckconsole_active' );
		} );

		editor.on( 'blur', function() {
			editorPanel.removeClass( 'ckconsole_active' );
		} );
	}

	function createContainer() {
		var container = that.container;

		if ( !container ) {
			container = that.container = fromHtml( '<div class="ckconsole cke_reset_all"></div>' );
			CKEDITOR.document.getBody().append( container );

			var link = new CKEDITOR.dom.element( 'link' );
			link.setAttributes( {
				rel: 'stylesheet',
				href: CKEDITOR.getUrl( 'dev/console/console.css' )
			} );
			CKEDITOR.document.getHead().append( link );
		}

		return container;
	}

	function createEditorPanel( editor, container, definition, config ) {
		var el = fromHtml( editorPanelTpl, {
			name: editor.name + '/' + definition.name
		} );

		el.findOne( '.ckconsole_header' ).on( 'click', function() {
			if ( el.hasClass( 'ckconsole_folded' ) )
				el.removeClass( 'ckconsole_folded' );
			else
				el.addClass( 'ckconsole_folded' );
		} );

		if ( config.folded )
			el.addClass( 'ckconsole_folded' );

		container.append( el );

		return el;
	}

	function createPanel( editorPanel, data ) {
		var el = fromHtml( panelTpl, data );

		el.findOne( '.ckconsole_header' ).on( 'click', function() {
			if ( el.hasClass( 'ckconsole_folded' ) )
				el.removeClass( 'ckconsole_folded' );
			else
				el.addClass( 'ckconsole_folded' );
		} );

		editorPanel.append( el );

		return el;
	}

	function fromHtml( html, data ) {
		if ( html instanceof CKEDITOR.template )
			html = html.output( data );

		return CKEDITOR.dom.element.createFromHtml( html );
	}

	function setupPanelRefresh( editor, panel ) {
		var timer,
			buffer = CKEDITOR.tools.eventsBuffer( 500, function() {
				panel.refresh();
				panel.container.addClass( 'ckconsole_refreshed' );

				if ( timer )
					clearTimeout( timer );
				setTimeout( removeClass, 200 );
			} );

		editor.on( 'pluginsLoaded', function() {
			panel.refreshOn( editor, buffer.input );
		} );

		function removeClass() {
			panel.container.removeClass( 'ckconsole_refreshed' );
		}
	}


	//
	// PANELS -----------------------------------------------------------------
	//

	function panelBox( editor, editorPanel, panelDefinition ) {
		var container = createPanel( editorPanel, {
			header: '<span class="ckconsole_value" data-value="header"></span>',
			content: '<div class="ckconsole_content">' + panelDefinition.content + '</div>'
		} );

		var valuesElements = container.find( '.ckconsole_value' ),
			values = {};

		for ( var el, i = 0, l = valuesElements.count(); i < l; ++i ) {
			el = valuesElements.getItem( i );
			values[ el.data( 'value' ) ] = el;
		}

		return {
			editor: editor,
			definition: panelDefinition,
			container: container,
			valuesElements: values,
			refreshOn: panelDefinition.refreshOn,
			refresh: function() {
				var values = this.definition.refresh( this.editor ),
					valueName;

				for ( valueName in values )
					this.valuesElements[ valueName ].setHtml( values[ valueName ] );
			}
		};
	}

	function panelLog( editor, editorPanel, panelDefinition ) {
		var container = createPanel( editorPanel, {
			header: 'Console',
			content: '<ul class="ckconsole_log"></ul>'
		} );

		var logList = container.findOne( '.ckconsole_log' );

		editor.on( 'pluginsLoaded', function() {
			panelDefinition.on( editor, log, logFn );
		} );

		// Scroll list to the end after it has been shrinked.
		editor.on( 'blur', function() {
			logList.$.scrollTop = logList.$.scrollHeight;
		}, null, null, 999 );

		function log( msg ) {
			var time = new Date().toString().match( /\d\d:\d\d:\d\d/ )[ 0 ],
				item = fromHtml( logItemTpl, { time: time, msg: msg } );

			logList.append( item );
			logList.$.scrollTop = logList.$.scrollHeight;

			setTimeout( function() {
				item.removeClass( 'ckconsole_fresh_log_item' );
			}, 250 );
		}

		function logFn( msg ) {
			return function() {
				log( msg );
			};
		}

		return {
			editor: editor,
			definition: panelDefinition,
			container: container
		};
	}

	var editorPanelTpl = new CKEDITOR.template(
			'<div class="ckconsole_editor_panel"><h1 class="ckconsole_header ckconsole_editor_header" onmousedown="return false">Editor: {name}</h1></div>'
		),
		panelTpl = new CKEDITOR.template(
			'<div class="ckconsole_panel ckconsole_folded">' +
				'<h1 class="ckconsole_header ckconsole_panel_header" onmousedown="return false">{header}</h1>' +
				'{content}' +
			'</div>'
		),
		logItemTpl = new CKEDITOR.template(
			'<li class="ckconsole_log_item ckconsole_fresh_log_item"><time datetime="{time}" class="ckconsole_time">{time}</time> <code>{msg}</code></li>'
		);
	return that;
} )();

/**
 * Usage:
 *
 * CKCONSOLE.addEventPanel( 'drop' );
 * CKCONSOLE.create( 'drop', { editor: 'inline' } );
 *
 * or (with observed types):
 *
 * CKCONSOLE.addEventPanel( 'drop', [ '$', 'target' ] );
 * CKCONSOLE.create( 'drop', { editor: 'inline' } );
 *
 * or (with observed types mapping):
 *
 * CKCONSOLE.addEventPanel( 'drop',
 * 		[ '$', 'target' ],
 * 		function( evt ) {
 * 			return {
 * 				'$': evt.data.$,
 * 				'target': evt.data.target
 * 			};
 * 		}
 * );
 * CKCONSOLE.create( 'drop', { editor: 'inline' } );
 *
 */
CKCONSOLE.addEventPanel = function( evtName, types, mapping ) {
	var values,
		content = '<ul class="ckconsole_list">';

	if ( !types ) {
		types = [];
	}
	if ( !mapping ) {
		mapping = function( evt ) {
			var ret = {};

			for ( var i = 0; i < types.length; i++ ) {
				ret[ types[ i ] ] = evt.data[ types[ i ] ];
			}
			return ret;
		};
	}

	for ( var i = 0; i < types.length; i++ ) {
		content += '<li>' + types[ i ] + ': <span class="ckconsole_value" data-value="' + types[ i ] + '"></span></li>';
	}
	content += '</ul>';

	CKCONSOLE.add( evtName, {
		panels: [
			{
				type: 'box',
				content: content,

				refresh: function() {
					values.header = evtName;
					return values;
				},

				refreshOn: function( editor, refresh ) {
					editor.on( evtName, function( evt ) {
						values = mapping( evt );
						refresh();
					} );
				}
			},
			{
				type: 'log',
				on: function( editor, log, logFn ) {
					editor.on( evtName, function( evt ) {
						var logStr = evtName + '; ';
						values = mapping( evt );

						for ( var i = 0; i < types.length; i++ ) {
							logStr += types[ i ] + ': ' + values[ types[ i ] ] + '; ';
						}

						logFn( logStr )();
					} );
				}
			}
		]
	} );
};
