/**
 * Creates an inline editor for the preview view.
 *
 * The `ep.uiPrevieweditor` method creates an inline editor for the preview view. In addition it provides necessary methods to connect the external toolbar to the CKEditor plugins.
 *
 * ### Examples
 * Create a preview editor.
 *
 * JavaScript:
 *
 *     ep('#editorID').uiPrevieweditor({
 *       'name' : 'editorName',
 *       'navbar' : 'Content',
 *       'pageBreak': false,
 *       'height': $('#baseId').height(),
 *       'width': $('#baseId').width(),
 *       'holder': $('#holderId'),
 *       'base': $('#baseId')
 *     });
 *
 * HTML:
 *
 *     <div id="baseId">Some Content</div>
 *     <div id="holderId" style="clear:both; position:relative;display: none;">
 *       <textarea style="visiblity:hidden;" name="editorName" id="editorID">Some Content</textarea>
 *     </div>
 *
 *
 * @class jQuery.ui.uiPrevieweditor
 * @extends jQuery.widget
 *
 * @uses ep
 * @uses jQuery.ui.widget
 * @uses jQuery.ckeditor.adapters.jquery
 * @uses ep.alert
 * @since 6.15.0
 */

/**
 * @cfg {String} name Name of the editor (corresponds to the name attribute of the textarea).
 */

/**
 * @cfg {Boolean} pageBreak Flag that indicates if the pagebreak icon is shown (e.g. for blog posts).
 */

/**
 * @cfg {String} navbar Add class to the html element inside the iframe to set special CSS for preview inline mode.
 */

/**
 * @cfg {Integer} width Width for the editor.
 */

/**
 * @cfg {Integer} height Height for the editor.
 */

/**
 * @cfg {Object} holder jQuery object of the DOM element which holds the textarea/editor.
 */

/**
 * @cfg {Object} base jQuery object of the DOM element which contents the source html.
 */

/**
 * See `jQuery.ui.uiPrevieweditor` for details.
 *
 * @param {Object} options A map of additional options pass to the method.
 * @param {String} name Name of the editor (corresponds to the name attribute of the textarea).
 * @param {Boolean} pageBreak Flag that indicates if the pagebreak icon is shown (e.g. for blog posts).
 * @param {String} navbar Add class to the html element inside the iframe to set special CSS for preview inline mode.
 * @param {Integer} width Width for the editor.
 * @param {Integer} height Height for the editor.
 * @param {Object} holder jQuery object of the DOM element which holds the textarea/editor.
 * @param {Object} base jQuery object of the DOM element which contents the source html.
 *
 * @method uiPrevieweditor
 * @member jQuery
 *
 * @since 6.15.0
 */

/*
 * @copyright       © Copyright 2006-2010, epages GmbH, All Rights Reserved.
 *
 * @module          ep.ui.dialog
 *
 * @revision        $Revision: 1.21 $
 */
/*jslint nomen: true, browser: true, plusplus: true, ass: true, unparam: true*/
/*global define,top,jq,$$,epages*/

// load ep.alert, because it is used in CKEditor plugin epPageBreak
define("ep/ui/previeweditor", [
    "jquery",
    "ep",
    "ckeditor/ckeditor",

    "jquery/ui/widget",
    "jquery/ckeditor",
    "ep/alert",
    "ep/ui/core"
], function ($, ep, CKEDITOR) {
    'use strict';

    /*
    {
        'Text' : {
            'editors' : [node1,node2], //jQuery objects of editor nodes with this particular name
            'toolbar' : node,
            'leftEditor' : $Object,
            'rightEditor' : $Object,
            'disabledLayer': $Object
            }
    }
    */
    var editorStack = {},
        top$ = $;

    ep('<div>').prependTo('body').attr('id', 'uieditor_preview_toolbar').hide();

    try {
        if (top.jQuery) {
            top$ = top.jQuery;
        }
    } catch (ignore) {}

    // on window resize -> adjust width of editors to avoid display errors
    $(window).on('resize', function () {
        // move to another thread
        setTimeout(function () {
            var key, instance, iframe;

            // loop over editor instances
            for (key in CKEDITOR.instances) {
                if (CKEDITOR.instances.hasOwnProperty(key)) {
                    instance = CKEDITOR.instances[key];
                    iframe = jq(instance.container.$).find('iframe');

                    // set possible width
                    iframe.width('100%');
                }
            }
        }, 1);
    });

    // register method to manage availability of toolbar elements
    CKEDITOR.manageToolbar = function (disabled) {
        var toolbarElements = top$('#EditorTools *[data-ep-uieditor], #EditorTools select');

        //toolbarElements[!!disabled ? 'addClass' : 'removeClass']('Disabled');
        toolbarElements.toggleClass('Disabled', !!disabled);
    };

    // define ckeditor on parent frame
    $.widget('ui.uiPrevieweditor', {
        editorInstance: undefined,
        toolbarId: 'uieditor_preview_toolbar',
        heightCorrect: 25,
        options: {
            //  name: undefined,
            //  navbar: undefined,
            pageBreak: false,
            holder: undefined,
            base: undefined,
            height: 300,
            width: 400
        },

        _create: function () {
            var self = this,
                o = self.options,
                elem = self.element,
                editorName = o.name = o.name || ('editor_' + new Date().getTime());

            self.id = elem.attr('id');

            // create new stack entry if necessary
            if (!editorStack[editorName]) {
                editorStack[editorName] = {};
            }

            // register toolbar in stack
            editorStack[editorName].toolbar = $('#' + self.toolbarId);
            // create editor
            self._createEditor();
        },

        _createEditor: function () {
            var self = this,
                o = self.options,
                elem = self.element;

            // create editor
            elem.ckeditor(
                function (textarea) {
                    var undoObject,
                        undoNode,
                        ckeIframe;

                    self.editorInstance = elem.ckeditorGet();
                    //#JSCOVERAGE_IF false
                    // commented out for jscoverage because it is just relevant for the preview with an external toolbar in a higher leveled iframe
                    // set flag to identify if PageBreak icon has to be shown in external toolbar
                    self.editorInstance.pageBreak = !!o.pageBreak;

                    // register textarea for undo
                    if ($$ !== undefined) {
                        undoObject = $$('undoWidget').getUndoObject();
                        undoNode = $('<input type="hidden">').appendTo('body').attr('name', elem.attr('name'));

                        undoNode.val(elem.val());
                        undoObject.registerInput(undoNode[0]);

                        // call toggleButtons to change the save button state when focus is on editorInstance
                        self.editorInstance.on('focus', function () {
                            var isTyping = undoObject.isTyping;

                            undoObject.isTyping = true;
                            $$('undoWidget').toggleButtons();
                            undoObject.isTyping = isTyping;
                        });

                        self.editorInstance.toggleView = function (show) {
                            // hide base div if defined
                            if (o.base) {
                                o.base[show ? 'hide' : 'show']();
                            }

                            // show editor
                            if (o.holder) {
                                o.holder[show ? 'show' : 'hide']();
                            }

                            if (show) {
                                // set focus to editor
                                self.editorInstance.focusManager.focus();
                                // set cursor in editor
                                self.setCursor(self.editorInstance, CKEDITOR.POSITION_BEFORE_START); // POSITION_AFTER_END
                            } else {
                                // set to source mode if wysiwyg is active to update content
                                if (self.editorInstance.mode === 'wysiwyg') {
                                    self.editorInstance.execCommand('source');
                                }

                                self.editorInstance.focusManager.blur();
                                // write textarea data to base DIV
                                o.base.html(self.editorInstance._.data);
                                // trigger event to render gadgets in updated preview
                                $('body').trigger('updatepreview');

                                // trigger mode change after a delay to make sure that wysiwyg mode is active
                                window.setTimeout(function() {
                                    if (self.editorInstance.mode === 'source') {
                                        self.editorInstance.execCommand('source');
                                    }
                                }, 200);
                            }
                        };

                        //only change ckeditor content when input was changed via undo/redo call
                        undoNode.on('change', function () {
                            if (top.epages.isUndoCall) {
                                self.editorInstance.setData(undoNode[0].value);
                            }
                        });
                    }

                    // handler if editor gets the focus
                    self.editorInstance.focusManager.focus = function () {
                        self.toggleToolbar(true);
                        self.editorInstance.fire('focus');
                        // manage visibility of the two external toolbars
                        top.jq('#PageTools').addClass('NavBarInvisible').removeClass('NavBarVisible');
                        top.jq('#EditorTools').addClass('NavBarVisible').removeClass('NavBarInvisible');

                        // show/hide PageBreak icon
                        top.jq('#EditorTools .MCEElementsBox-InsertPageBreak')[self.editorInstance.pageBreak ? 'removeClass' : 'addClass']('HideElement');
                    };

                    // handler if editor loses the focus
                    self.editorInstance.focusManager.blur = function () {
                        // update textarea
                        self.editorInstance.updateElement();
                        self.toggleToolbar(false);

                        //check if something was changed inside the editor and write it back to the hidden input for undo handling
                        if (undoNode && undoNode.val() !== elem.val()) {
                            undoNode.val(elem.val());
                            epages.event.fire(undoNode[0], 'change');
                        }
                    };

                    // autogrow handling (extraPlugin)
                    self.editorInstance.on('autogrow', function () {
                        o.holder.height(ckeIframe.height());
                    });

                    //check change content on ckeditor undo handling and add it to epages undo handling
                    self.editorInstance.on('saveSnapshot', function () {
                        if (undoNode && undoNode.val() !== elem.val()) {
                            undoNode.val(elem.val());
                            epages.event.fire(undoNode[0], 'change');
                        }
                    });

                    self.toggleToolbar(false);

                    // handling to mark toolbar elements if the current selection has changed
                    self.editorInstance.on('selectionChange', function (ev) {
                        var pathElements = ev.data.path.elements,
                            toolbarCommands = top$('#EditorTools *[data-ep-uieditor]'),
                            commandsArray = [],
                            // takes all commands (handed over by classes)
                            pathArray = [],
                            // takes tag names (lowercase) of all path elements
                            styles = {},
                            // takes all the relevant styles
                            styleTupel,
                            styleArray,
                            styleKey,
                            idKey,
                            attributes,
                            key,
                            _key,
                            styleCount,
                            styleCountLength,
                            className,
                            element,
                            replaceFn = function (strMatch, strBracket, strPos, strSrc) {
                                return strBracket.toUpperCase();
                            };

                        // loop over all elements depending on the selection
                        for (key in pathElements) {
                            if (pathElements.hasOwnProperty(key)) {
                                idKey = pathElements[key].$.tagName.toLowerCase();
                                attributes = pathElements[key].$.attributes[0];

                                // check if path element has an attributes and style property
                                if (attributes !== undefined) {
                                    // if a style is set
                                    if (attributes.name === "style") {
                                        // split style definitions
                                        styleArray = attributes.value.substr(0, attributes.value.length - 1).split(";");

                                        // loop over style definitions
                                        styleCountLength = styleArray.length;
                                        for (styleCount = 0; styleCount < styleCountLength; styleCount++) {
                                            // split value (e.g. [font-size]['9px']
                                            styleTupel = styleArray[styleCount].split(":");

                                            // generate camel-case key (e.g. font-size -> fontSize)
                                            styleKey = styleTupel[0].replace(/\-(\w)/g, replaceFn);

                                            // set style definition only from the deepest selection
                                            if (styles[styleKey] === undefined) {
                                                switch (styleKey) {
                                                // indent -> data-ep-uieditor="indent"
                                                case 'marginLeft':
                                                    styles[styleKey] = $.merge(['indent', idKey], styleTupel);
                                                    break;
                                                // color -> data-ep-uieditor="fontcolor"
                                                case 'color':
                                                    styles[styleKey] = $.merge(['fontcolor', idKey], styleTupel);
                                                    break;
                                                // backgroundColor -> data-ep-uieditor="backgroundColor"
                                                case 'backgroundColor':
                                                    styles[styleKey] = $.merge(['backgroundColor', idKey], styleTupel);
                                                    break;
                                                // default -> data-ep-uieditor="$.trim(styleTupel[1])"
                                                default:
                                                    styles[styleKey] = $.merge([$.trim(styleTupel[1]), idKey], styleTupel);
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }

                                // push tag name in array
                                pathArray.push(idKey);
                                // get element's class name, concatenate it with tag name and push in array
                                className = pathElements[key].$.className.toLowerCase();

                                if (className) {
                                    idKey += '_' + className;
                                }
                                commandsArray.push(idKey);
                            }
                        }

                        // loop over all toolbar elements (reset select fields)
                        toolbarCommands.each(function (i, elem) {
                            // reset options
                            if (elem.tagName.toLowerCase() === 'option') {
                                $(elem).closest('select')[0].selectedIndex = 0;
                            }
                        });

                        // loop over all toolbar elements
                        toolbarCommands.each(function (i, elem) {
                            var dataValue = $(elem).data('epUieditor');

                            // select/mark or reset element
                            if (elem.tagName.toLowerCase() === 'option') {
                                if ($.inArray(dataValue, pathArray) !== -1) {
                                    $(elem).closest('select').val(dataValue);
                                }
                            } else {
                                $(elem)[$.inArray(dataValue, commandsArray) !== -1 ? 'addClass' : 'removeClass']('Selected');
                            }
                        });

                        // loop over registered style properties
                        for (_key in styles) {
                            if (styles.hasOwnProperty(_key)) {
                                // get associated toolbar element
                                element = top$('#EditorTools *[data-ep-uieditor="' + styles[_key][0] + '"]');

                                // select or mark element
                                if (element[0].tagName.toLowerCase() === 'option') {
                                    element.closest('select').val(element.val());
                                } else {
                                    element.addClass('Selected');
                                    // if an element exists to display the current color
                                    if (element.find('div.ColorBox').length) {
                                        element.find('div.ColorBox').css('background-color', styles[_key][3]);
                                    }
                                }
                                delete styles[_key];
                            }
                        }

                        // set default for text-align when nothing is selected
                        if ($.grep(['right', 'center', 'justify'], function (value, index) {
                                return top$('#EditorTools *[data-ep-uieditor="' + value + '"]').hasClass('Selected');
                            }).length === 0) {
                            top$('#EditorTools *[data-ep-uieditor="left"]').addClass('Selected');
                        }

                    });

                    self.editorInstance.on('dataReady', function( e ){
                        self.editorInstance.epDataReady = true;
                    });

                    elem.on('change', function () {
                        self.editorInstance.setData();
                    });

                    //#JSCOVERAGE_ENDIF
                    // get iframe from editor instance
                    ckeIframe = $(self.editorInstance.container.$).find('iframe');

                    // add class to iframe to set special CSS for preview inline mode and remove inline styles build by CKEditor
                    ckeIframe.addClass('cke_preview').removeAttr('style').parent().removeAttr('style');

                    // add class to the html element inside the iframe to set special CSS for preview inline mode
                    ckeIframe.contents().find('html').addClass(function () {
                        var addedClass = 'cke_preview';
                        if (o.navbar) {
                            addedClass += ' cke_' + o.navbar;
                        }
                        return addedClass;
                    });

                    // hide base div if defined
                    if (o.base) {
                        o.base.hide();
                    }

                    // show editor
                    if (o.holder) {
                        o.holder
                            .width(o.width)
                            .show();
                    }

                    // set initial dimensions
                    ckeIframe
                        .height('100%')
                        .width(o.holder ? "100%" : o.width);

                    // set focus to editor
                    self.editorInstance.focusManager.focus();
                    // set cursor to editor
                    self.setCursor(self.editorInstance, CKEDITOR.POSITION_BEFORE_START); // POSITION_AFTER_END
                    // save initial width
                    self.editorInstance.initWidth = o.width;
                    $(window).trigger('resize');
                },
                //options for editor
                {
                    extraPlugins: 'autogrow',
                    autoGrow_minHeight: 80,
                    sharedSpaces: {
                        top: 'uieditor_preview_toolbar'
                    },
                    toolbar: o.pageBreak ? 'FullPageBreak' : 'Full'
                }
            );
        },

        // set cursor to editor
        // position can be: POSITION_AFTER_END, POSITION_BEFORE_START
        // call: self.setCursor(self.editorInstance, CKEDITOR.POSITION_BEFORE_START);
        setCursor: function (editorInstance, position) {
            editorInstance.focus();

            var selection = editorInstance.getSelection(),
                range = selection.getRanges()[0],
                paragraph,
                newRange;

            if (range) {
                paragraph = (range.startContainer.getName && range.startContainer.getName() === "body") ? range.startContainer : range.startContainer.getAscendant({p: 2}, true);
                newRange = new CKEDITOR.dom.range(range.document);

                if (newRange.endOffset !== null) {
                    newRange.moveToPosition(paragraph, position);
                    newRange.select();
                }
            }
        },

        /**
         * Handles settings for the current editor if the external toolbar is toggled.
         *
         * @param {Boolean} blnShow Specifies path and name of the file containing the new preview image.
         *
         * @method toggleToolbar
         * @member jQuery.ui.uiPrevieweditor
         *
         * @since 6.15.0
         */
        toggleToolbar: function (show) {
            try {
                if (show && this.editorInstance) {
                    top.EP_CKEDITOR = this.editorInstance;
                }
            } catch (e) {
                $.error(e);
            }

            return this;
        },

        destroy: function () {
            var self = this,
                id = self.id;

            // destroy according ckeditor instance
            CKEDITOR.instances[id].destroy();

            self._superApply(arguments);
        }
    });

    return ep;

});