/* --------------------------------------------------------------
 editor.js 2017-09-05
 Gambio GmbH
 http://www.gambio.de
 Copyright (c) 2017 Gambio GmbH
 Released under the GNU General Public License (Version 2)
 [http://www.gnu.org/licenses/gpl-2.0.html]
 --------------------------------------------------------------
 */

/**
 * ## Editor Widget
 *
 * This widget will initialize instances of CKEditor or CodeMirror depending the provided data attribute of
 * each textarea, within the container the widget is bound to. Purpose of this module is to provide a common
 * wrapper of the textarea and record specific editor which means that the user will be able to set an editor
 * for a specific record and textarea and store this preference in the database.
 *
 * **Currently the available editors are "ckeditor" and "codemirror".**
 *
 * Important: Make sure that you provide the required options as described below. The module is flexible enough
 * to provide a solution for each page code base.
 *
 *
 * ### Options (Container)
 *
 * The following options are bound as data attributes to the element where the module is bound on (most of the times
 * a container that includes textarea elements).
 *
 * **Selector | `data-editor-selector` | String | Optional**
 *
 * Provide a selector for the textareas to be converted to editor instances. This option defaults to "textarea" and
 * will match all the textarea elements inside the container.
 *
 * **Event Target | `data-editor-event-target` | String | Optional**
 *
 * Provide a selector that will mark the element which will start the submit/save process of the page. If provided
 * the selected editor preference will be saved through the user configuration service with an AJAX request.
 *
 * Important: There is no default value for this option.
 *
 * **Event Type | `data-editor-event-type` | String | Optional**
 *
 * Provide a JavaScript event that will mark the submit/save process of the page. If provided an event handler
 * will be bound on the element marked by the "event-target" option and AJAX requests will save the current
 * editor preference in the user configuration table.
 *
 * Important: There is no default value for this option.
 *
 * **AutoHide | `data-editor-auto-hide` | Boolean | Optional**
 *
 * Provide "true" or "false" in order to auto hide the editors, if the textareas are not visible at the beginning.
 * Defaults value is "false"
 *
 * **AutoUpdate | `data-editor-auto-update` | Boolean | Optional**
 *
 * Indicates if the corresponding textarea of the editor should be updated automatically.
 * Default value is "true"
 *
 * **Initialization Event Type | `data-editor-init-event-type` | String | Optional**
 *
 * Provide a custom initialization event which will trigger the start of the editor conversion. By default the
 * editor instances will be created once the engine is ready 'JSENGINE_INIT_FINISHED', but there are cases where
 * a custom event is required (e.g. initialization of editor widget dynamically within a dialog).
 *
 *
 * ### Options (Textarea)
 *
 * The following options are bound as data attributes to each textarea element within the parent container.
 *
 * **Editor Identifier | `data-editor-identifier` | String | Required**
 *
 * Each child textarea element needs to have a unique identifier string which needs to apply with the following
 * naming convention the "editor-{record type}-{id}-{textarea name}-{language code}
 * (e.g. editor-products-2-short_description-de). In cases where the record ID is not set yet (new record creation),
 * it is advised that you leave the {id} placeholder and replace it later on whenever the record is generated into
 * the database (more information about this edge case in the examples below).
 *
 * **Editor Type | `data-editor-type` | String | Optional**
 *
 * This option can have one of the available editor values which will also state the selected editor upon
 * initialization. It is optional and the default value is "ckeditor".
 *
 *
 * ### Events
 *
 * The '#editor-container' element is where the widget is bound on.
 *
 * ```javascript
 * // Fires up when all textareas are ready.
 * $('#editor-container').on('editor:ready', (event, $textareas) => { ... });
 *
 * // Fires up each time a single textarea is ready.
 * $('#editor-container').on('editor:textarea_ready', (event, $textarea) => { ... });
 * ```
 *
 *
 * ### Examples
 *
 * **Simple Usage**
 *
 * Notice that this example showcases the creation of a new customer which means that the customer's ID is not known
 * yet. After its initialization, the widget will create a hidden field in the form with the
 * "editor_identifiers[textarea-identifier]" name. This hidden field will have the selected editor type as value which
 * be used by the backend callback to store the correct editor identifier value, once the customer's ID is generated
 * (record inserted). Use the "UserConfigurationService" in backend for adding the value to the database.
 *
 * ```html
 * <div data-gx-widget="editor" data-editor-event-target="#customer-form"  data-editor-event-type="submit">
 *   <form id="customer-form">
 *     <!-- Other Fields ... ->
 *     <textarea class="wysiwyg" data-editor-identifier="editor-customers-{id}-notes-de"></textarea>
 *   </form>
 * </div>
 * ```
 *
 * @module Admin/Widgets/editor
 * @requires CKEditor, CodeMirror
 */
gx.widgets.module(
    'editor',

    [
        `${jse.source}/vendor/codemirror/codemirror.min.css`,
        `${jse.source}/vendor/codemirror/codemirror.min.js`,
        `${gx.source}/libs/editor_instances`,
        `${gx.source}/libs/editor_values`,
        `${gx.source}/widgets/quickselect`,
        'user_configuration_service'
    ],

    function (data) {

        'use strict';

        // ------------------------------------------------------------------------
        // VARIABLES
        // ------------------------------------------------------------------------

        /**
         * Module Selector
         *
         * @type {jQuery}
         */
        const $this = $(this);

        /**
         * Default Options
         *
         * @type {Object}
         */
        const defaults = {
            selector: 'textarea',
            autoHide: 'false',
            initEventType: 'JSENGINE_INIT_FINISHED',
            autoUpdate: 'true'
        };

        /**
         * Final Options
         *
         * @type {Object}
         */
        const options = $.extend(true, {}, defaults, data);

        /**
         * Module Object
         *
         * @type {Object}
         */
        const module = {};

        /**
         * Editor Instances
         *
         * Identifier -> instance mapping
         *
         * @type {Object}
         */
        const editors = {};

        /**
         * Available Editor Types
         *
         * @type {String[]}
         */
        const editorTypes = ['ckeditor', 'codemirror'];

        // ------------------------------------------------------------------------
        // FUNCTIONS
        // ------------------------------------------------------------------------

        /**
         * Add Editor Switch Button
         *
         * This method will add the editor switch button and bind the click event handler.
         *
         * @param {jQuery} $textarea Textarea selector to be modified.
         */
        function _addEditorSwitchButton($textarea) {
            let start = 0;
            if ($textarea.data('editorType') === 'codemirror') {
                start = 1;
            }

            $textarea
                .wrap('<div class="editor-wrapper" />')
                .parent()
                .prepend(`<div data-gx-widget="quickselect" data-quickselect-align="right" data-quickselect-start="`
                    + start + `">
							<div class="quickselect-headline-wrapper">
								<a class="editor-switch editor-switch-html" href="#html">
									${jse.core.lang.translate('BUTTON_SWITCH_EDITOR_TEXT', 'admin_buttons')}
								</a>
								<a class="editor-switch editor-switch-text" href="#text">
									${jse.core.lang.translate('BUTTON_SWITCH_EDITOR_HTML', 'admin_buttons')}
								</a>
							</div>
						</div>`)
                .find('.editor-switch')
                .on('click', _onSwitchButtonClick);

            if (!$textarea.is(':visible') && options.autoHide === 'true') {
                $textarea.parent().hide();
            }

            gx.widgets.init($textarea.parent());
        }

        /**
         * Add a hidden editor type field.
         *
         * This field will contain the type of the current editor and can be used by submit callbacks whenever the
         * record ID is not known yet and the user configuration entry is generated by the server.
         *
         * @param {jQuery} $textarea Textarea selector to be modified.
         */
        function _addEditorHiddenField($textarea) {
            $textarea
                .parent()
                .append(`
					<input type="hidden" 
						name="editor_identifiers[${$textarea.data('editorIdentifier')}]" 
						value="${$textarea.data('editorType') || 'ckeditor'}" />
				`);
        }

        /**
         * Create Editor Instance
         *
         * This method will use the "editor" library to create the appropriate editor instance, depending the textarea
         * type attribute.
         *
         * @param {jQuery} $textarea Textarea selector to be modified.
         */
        function _createEditorInstance($textarea) {
            const type = $textarea.data('editorType') || 'ckeditor';
            const identifier = $textarea.data('editorIdentifier');

            editors[identifier] = jse.libs.editor_instances.create($textarea, type);
        }

        /**
         * On Switch Button Click Event Handler
         *
         * This method will use the "editor" library to change the current editor type and update the hidden input
         * field and data attributes of the textarea. It will try to set the next available editor type.
         */
        function _onSwitchButtonClick() {
            const $switchButton = $(this);
            const $textarea = $switchButton.parents('.editor-wrapper').find('textarea');
            const identifier = $textarea.data('editorIdentifier');
            const currentType = $textarea.data('editorType');
            const newType = $switchButton.hasClass('editor-switch-text') ? editorTypes[1] : editorTypes[0];

            $textarea.siblings(`[name="editor_identifiers[${identifier}]"]`).val(newType);
            $textarea.data('editorType', newType);

            editors[identifier] = jse.libs.editor_instances.switch($textarea, currentType, newType);
            _bindAutoUpdate($textarea);
            _updateTextArea($textarea);
        }

        /**
         * On Page Submit Handler
         *
         * If the event target and type are provided this method will be triggered to save the user configuration
         * values in the database with AJAX requests.
         */
        function _onPageSubmit() {
            for (let identifier in editors) {
                jse.libs.user_configuration_service.set({
                    data: {
                        userId: 0,
                        configurationKey: identifier,
                        configurationValue: editors[identifier].type
                    }
                });
            }
        }

        /**
         * Bind Auto Update
         *
         * Binds an event handler to an editor instance to automatically update the
         * corresponding textarea.
         *
         * @param {jQuery} $textarea Textarea the auto update should be bound to
         */
        function _bindAutoUpdate($textarea) {
            if (options.autoUpdate === 'false') {
                return;
            }

            const instance = editors[$textarea.data('editorIdentifier')];

            instance.on('change', () => _updateTextArea($textarea));
        }

        /**
         * Update Text Area Value
         *
         * Transfers the value of the editor instance of the given textarea to its corresponding textarea.
         *
         * @param {jQuery} $textarea The textarea to be updated.
         */
        function _updateTextArea($textarea) {
            const editorType = $textarea.data('editorType');
            const instance = editors[$textarea.data('editorIdentifier')];

            switch (editorType) {
                case 'ckeditor':
                    instance.updateElement();
                    break;

                case 'codemirror':
                    $textarea.val(jse.libs.editor_values.getValue($textarea));
                    break;

                default:
                    throw new Error('Editor type not recognized.', editorType);
            }

            $textarea.trigger('change');
        }

        // ------------------------------------------------------------------------
        // INITIALIZATION
        // ------------------------------------------------------------------------

        module.init = function (done) {
            $(document).on('JSENGINE_INIT_FINISHED', () => {
                const dependencies = [
                    `${jse.source}/vendor/codemirror/css.min.js`,
                    `${jse.source}/vendor/codemirror/htmlmixed.min.js`,
                    `${jse.source}/vendor/codemirror/javascript.min.js`,
                    `${jse.source}/vendor/codemirror/xml.min.js`
                ];

                jse.core.module_loader.require(dependencies);
            });

            // Initialize the editors after a specific event in order to make sure that other modules will be
            // already initialized and nothing else will change the markup.
            $(window).on(options.initEventType, () => {
                const $textareas = $this.find(options.selector);

                $textareas.each((index, textarea) => {
                    const $textarea = $(textarea);

                    if (editorTypes.indexOf($textarea.data('editorType')) === -1) {
                        $textarea.data('editorType', editorTypes[0]);
                    }

                    _addEditorSwitchButton($textarea);
                    _addEditorHiddenField($textarea);
                    _createEditorInstance($textarea);
                    _bindAutoUpdate($textarea);

                    $this.trigger('editor:textarea_ready', [$textarea]);
                });

                $this.trigger('editor:ready', [$textareas]);
            });

            // If the event target and type options are available, bind the page submit handler.
            if (options.eventTarget !== undefined && options.eventType !== undefined) {
                $(options.eventTarget).on(options.eventType, _onPageSubmit);
            }

            done();
        };

        // Return data to module engine.
        return module;
    });