/*
 *  Copyright 2017 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * Class to handle media insertion in inline editor
 * @private
 */
Ext.define('Ametys.plugins.inlinemedia.Media', {
    singleton: true,

    /**
     * Insert a Youtube video
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    insertYoutube: function(controller)
    {
        var editor = this._getEditor();
        if (editor != null)
        {
            this._insertYoutube(editor, [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '));
        }
    },
    
    /**
     * Insert a Youtube video
     * @param {tinymce.Editor} editor The richtext editor
     * @param {String} glyph The glyph to use
     * @private
     */
    _insertYoutube: function(editor, glyph)
    {
        Ametys.plugins.inlinemedia.InsertMediaHelper.open(
            editor,
            "{{i18n PLUGINS_INLINEMEDIA_YOUTUBE_DIALOG_TITLE}}",
            glyph,
            "{{i18n PLUGINS_INLINEMEDIA_YOUTUBE_DIALOG_MESSAGE}}",
            "youtube",
            [ "[^\?\&\#]*v=([^\?\&\#]+)", "[^\?\&\#]*youtu.be\/([^\?\&\#]+)" ],
            480,
            385
        );
    },

    /**
     * Insert a Dailymotion video
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    insertDailymotion: function(controller)
    {
        var editor = this._getEditor();
        if (editor != null)
        {
            this._insertDailymotion(editor, [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '));
        }
    },
    
    /**
     * Insert a Dailymotion video
     * @param {tinymce.Editor} editor The richtext editor
     * @param {String} glyph The glyph to use
     * @private
     */
    _insertDailymotion: function(editor, glyph)
    {
        Ametys.plugins.inlinemedia.InsertMediaHelper.open(
            editor,
            "{{i18n PLUGINS_INLINEMEDIA_DAILYMOTION_DIALOG_TITLE}}",
            glyph,
            "{{i18n PLUGINS_INLINEMEDIA_DAILYMOTION_DIALOG_MESSAGE}}",
            "dailymotion",
            [ "[^\?\&\#]*\/video\/([a-zA-Z0-9]+).*" ],
            480,
            360
        );
    },
    
    /**
     * Insert a local video
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    insertLocalVideo : function (controller)
    {
        var editorInfos = this._getEditor();
        this._insertLocalVideo(editorInfos, [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '));
    },
    
    /**
     * Insert a local vdeo
     * @param {tinymce.Editor} editor The richtext editor
     * @param {String} glyph The glyph to use
     * @private
     */
    _insertLocalVideo : function (editor, glyph, uploadUrl)
    {
        var afterUpload = Ext.bind(this._insertLocalVideoCb, this, [editor], 0);
        
        Ametys.helper.FileUpload.open({
          iconCls: glyph,
          title: "{{i18n PLUGINS_INLINEMEDIA_LOCAL_DIALOG_TITLE}}",
          helpmessage: "{{i18n PLUGINS_INLINEMEDIA_LOCAL_DIALOG_MESSAGE}}",
          callback: afterUpload,
          uploadUrl: uploadUrl,
          allowedExtensions: ['ogv', 'ogg', 'mp4', 'webm']
        });
    },
    
    /**
     * Callback function called after uploading local video
     * @param {tinymce.Editor} editor The richtext editor
     * @param {String} id Id of the uploading video
     * @param {String} filename File name of the uploading video
     * @param {String} size File length of the uploading video
     * @param {String} viewHref The URL to view the uploaded video
     * @param {String} downloadHref The URL to download the uploaded video
     * @private
     */
    _insertLocalVideoCb: function(editor, id, filename, size, viewHref, downloadHref)
    {
        editor.focus();
        if (id != null)
        {
            var tmpId = Ext.id();
            
            editor.execCommand('mceBeginUndoLevel');
            editor.execCommand('mceInsertContent', false, '<img id="' + tmpId + '"  class="video" _mce_ribbon_select="1" marker="marker" media="video" width="300" height="200" src="' + Ametys.getPluginResourcesPrefix('inlinemedia') + '/img/trans.gif" data-ametys-type="temp" data-ametys-temp-src="' + id + '"/>');
            editor.execCommand('mceEndUndoLevel');
            
            var node = editor.dom.get(tmpId);
            node.removeAttribute("id");
            editor.selection.select(node);
        }
    },
    
    /**
     * Insert a resource video (from the resources explorer)
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    insertResourceVideo : function (controller)
    {
        var editor = this._getEditor();
        if (editor != null)
        {
            this._insertResourceVideo(editor, [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '), true);
        }
    },
    
    /**
     * Insert a resource video (from the resources explorer)
     * @param {tinymce.Editor} editor The richtext editor
     * @param {String} iconCls icon class
     * @param {Boolean} allowDragAndDrop True to allow drag and drop
     */
    _insertResourceVideo : function (editor, glyph, allowDragAndDrop)
    {
        var afterUpload = Ext.bind(this._insertResourceVideoCb, this, [editor], 0);
        
        Ametys.cms.uihelper.ChooseResource.open({
            iconCls: glyph,
            title: "{{i18n PLUGINS_INLINEMEDIA_RESSOURCES_DIALOG_TITLE}}",
            helpmessage: "{{i18n PLUGINS_INLINEMEDIA_RESSOURCES_DIALOG_MESSAGE}}",
            callback: afterUpload,
            allowDragAndDrop: allowDragAndDrop,
            filter: Ametys.explorer.tree.ExplorerTree.VIDEO_FILTER
        });
    },
    
    /**
     * Callback function called after uploading a resource video
     * @param {String} id Id of the uploading video
     * @param {String} filename File name of the uploaded video
     * @param {String} size File length of the uploaded video
     * @param {String} viewHref The URL to view the uploaded video
     * @param {String} downloadHref The URL to download the uploaded video
     * @private
     */
    _insertResourceVideoCb: function(editor, id, filename, size, viewHref, downloadHref)
    {
        editor.focus();
        if (id != null)
        {
            editor.execCommand('mceBeginUndoLevel');
            editor.execCommand('mceInsertContent', false, '<img class="video" _mce_ribbon_select="1" marker="marker" media="video" width="300" height="200" src="' + Ametys.getPluginResourcesPrefix('inlinemedia') + '/img/trans.gif" data-ametys-src="' + id + '" data-ametys-type="explorer" />');
            editor.execCommand('mceEndUndoLevel');
        }
    },
    
    /**
     * Insert a local audio
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    insertLocalAudio : function (controller)
    {
        Ametys.helper.FileUpload.open({
          iconCls: [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '),
          title: "{{i18n PLUGINS_INLINEMEDIA_AUDIO_LOCAL_DIALOG_TITLE}}",
          helpmessage: "{{i18n PLUGINS_INLINEMEDIA_AUDIO_LOCAL_DIALOG_MESSAGE}}",
          callback: this._insertLocalAudioCb,
          allowedExtensions: ['mp3', 'oga', 'ogg', 'wav']
        });
    },
    
    /**
     * Callback function called after uploading local audio
     * @param {String} id Id of the uploading audio
     * @param {String} filename File name of the uploading audio
     * @param {String} size File length of the uploading audio
     * @param {String} viewHref The URL to view the uploaded audio
     * @param {String} downloadHref The URL to download the uploaded audio
     * @private
     */
    _insertLocalAudioCb: function(id, filename, size, viewHref, downloadHref)
    {
        // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
        tinyMCE.activeEditor.focus();
        if (id != null)
        {
            var message = Ametys.message.MessageBus.getCurrentSelectionMessage();
            var contentTarget = message.getTarget(function(target) { return target.getId() == "content" });
            
            if (contentTarget != null)
            {
                var fieldTarget = contentTarget.getSubtarget(function(target) { return target.getId() == "field" });
                if (fieldTarget != null)
                {
                    var tmpId = Ext.id();
                    
                    tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
                    tinyMCE.activeEditor.execCommand('mceInsertContent', false, '<img id="' + tmpId + '"  class="audio" _mce_ribbon_select="1" marker="marker" media="audio" width="300" height="40" src="' + Ametys.getPluginResourcesPrefix('inlinemedia') + '/img/trans.gif" data-ametys-type="temp" data-ametys-temp-src="' + id + '"/>');
                    tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
                    
                    var node = tinyMCE.activeEditor.dom.get(tmpId);
                    node.removeAttribute("id");
                    tinyMCE.activeEditor.selection.select(node);
                }
            }
        }
    },
    
    /**
     * Insert a resource audio (from the resources explorer)
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    insertResourceAudio : function (controller)
    {
        Ametys.cms.uihelper.ChooseResource.open({
            iconCls: [controller.getInitialConfig('icon-glyph'), controller.getInitialConfig('icon-decorator')].join(' '),
            title: "{{i18n PLUGINS_INLINEMEDIA_AUDIO_RESSOURCES_DIALOG_TITLE}}",
            helpmessage: "{{i18n PLUGINS_INLINEMEDIA_AUDIO_RESSOURCES_DIALOG_MESSAGE}}",
            callback: this._insertResourceAudioCb,
            filter: Ametys.explorer.tree.ExplorerTree.SOUND_FILTER
        });
    },
    
    /**
     * Callback function called after uploading a resource audio
     * @param {String} id Id of the uploading audio
     * @param {String} filename File name of the uploaded audio
     * @param {String} size File length of the uploaded audio
     * @param {String} viewHref The URL to view the uploaded audio
     * @param {String} downloadHref The URL to download the uploaded audio
     * @private
     */
    _insertResourceAudioCb: function(id, filename, size, viewHref, downloadHref)
    {
        // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
        tinyMCE.activeEditor.focus();
        if (id != null)
        {
            tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
            tinyMCE.activeEditor.execCommand('mceInsertContent', false, '<img class="audio" _mce_ribbon_select="1" marker="marker" media="audio" width="300" src="' + Ametys.getPluginResourcesPrefix('inlinemedia') + '/img/trans.gif" data-ametys-src="' + id + '" data-ametys-type="explorer" />');
            tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
        }
    },
    
    /**
     * Get the image node from current cursor position
     * @return {HTMLElement} The image node or null if not found
     * @private
     */
    _getMedia: function(editor, node)
    {
        if (node != null)
        {
            var marker = node.getAttribute("marker");
            var media = node.getAttribute("media");
            
            if (marker != 'marker' || !media)
            {
                node = null;
            }
        }
        
        return node;
    },
    
    // ---------------------------------------- //
    //                  WIDTH                   //
    // ---------------------------------------- //
    /**
     * Set the width of current <img> node when the input loses the focus
     * @param {Ext.form.field.Text} input The input text
     */
    setMediaWidthOnBlur: function(input)
    {
        this.setWidth(input.getValue());
    },
    
    /**
     * Set the width of current <img> node when the spinner is made to spin up or down
     * @param {Ext.form.field.Text} input The input text
     * @param {String} direction The direction: 'up' if spinning up, or 'down' if spinning down.
     */
    setMediaWidthOnSpin: function(input, direction)
    {
        this.setWidth(input.getValue() + (direction == 'up' ? 1 : -1));
    },
    
    /**
     * Set the width of current <img> node when pressing ENTER or ESC key.
     * @param {Ext.form.field.Text} input The input text
     * @param {Ext.event.Event} e The event object
     */
    setMediaWidthOnSpecialKey: function(input, e)
    {
        if (e.getKey() == e.ENTER) 
        {
            e.preventDefault();
            e.stopPropagation();
            this.setWidth(input.getValue());
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.focus();
        }   
        else if (e.getKey() == e.ESC) 
        {
            e.preventDefault();
            e.stopPropagation();
            input.setValue(this._currentNode.style.width);
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.focus();
        }
    },
    
    /**
     * Set 'width' style to current <img> node
     * @param {String} value The value
     */
    setWidth: function (value)
    {
        var img = this._currentNode;
        if (img != null)
        {
            var ratio = img.getAttribute("data-ratio");
            if (!ratio)
            {
                // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
                ratio = tinyMCE.activeEditor.dom.get(img).height / tinyMCE.activeEditor.dom.get(img).width;
                img.setAttribute("data-ratio", ratio);
            }
            
            img.style.width = value + "px";
            img.style.height = Math.round(value*ratio) + "px";
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
        }
    },
    
    /**
     * Enable/disable controller and set input value according the selected image
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
     * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
     * @param {HTMLElement} node The current selected node. Can be null.
     */
    widthListener: function (controller, field, node)
    {
        var img = field != null && node != null  ? this._getMedia(field.getEditor(), node) : null;
        if (img)
        {
            controller.setValue (controller.getCurrentField().getEditor().dom.get(img).width);
        }
        else
        {
            controller.disable();
            controller.setValue ('');
        }
        
        this._currentNode = img;
    },
    
    // ---------------------------------------- //
    //                  HEIGHT                  //
    // ---------------------------------------- //
    /**
     * Set the height of current <img> node when the input loses the focus
     * @param {Ext.form.field.Text} input The input text
     */
    setMediaHeightOnBlur: function(input)
    {
        this.setHeight(input.getValue());
    },
    
    /**
     * Set the height of current <img> node when the spinner is made to spin up or down
     * @param {Ext.form.field.Text} input The input text
     * @param {String} direction The direction: 'up' if spinning up, or 'down' if spinning down.
     */
    setMediaHeightOnSpin: function(input, direction)
    {
        this.setHeight(input.getValue() + (direction == 'up' ? 1 : -1));
    },
    
    /**
     * Set the height of current <img> node when pressing ENTER or ESC key.
     * @param {Ext.form.field.Text} input The input text
     * @param {Ext.event.Event} e The event object
     */
    setMediaHeightOnSpecialKey: function(input, e)
    {
        if (e.getKey() == e.ENTER) 
        {
            e.preventDefault();
            e.stopPropagation();
            this.setWidth(input.getValue());
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.focus();
        }   
        else if (e.getKey() == e.ESC) 
        {
            e.preventDefault();
            e.stopPropagation();
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            input.setValue(tinyMCE.activeEditor.dom.get(this._currentNode).height);
            tinyMCE.activeEditor.focus();
        }
    },
    
    /**
     * Set 'height' style to current <img> node
     * @param {String} value The value
     */
    setHeight: function (value)
    {
        var img = this._currentNode;
        if (img != null)
        {
            var ratio = img.getAttribute("data-ratio");
            if (!ratio)
            {
                ratio = tinyMCE.activeEditor.dom.get(img).height / tinyMCE.activeEditor.dom.get(img).width;
                img.setAttribute("data-ratio", ratio);
            }
            
            img.style.width = Math.round(value/ratio) + "px";
            img.style.height = value + "px";
            tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
        }
    },
    
    /**
     * Enable/disable controller and set input value according the selected media
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
     * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
     * @param {HTMLElement} node The current selected node. Can be null.
     */
    heightListener: function (controller, field, node)
    {
        var img = field != null && node != null  ? this._getMedia(field.getEditor(), node) : null;
        if (img)
        {
            controller.setValue (field.getEditor().dom.get(img).height);
        }
        else
        {
            controller.disable();
            controller.setValue ('');
        }
        
        this._currentNode = img;
    },
    
    // ---------------------------------------- //
    //              ALTERNATIVE                 //
    // ---------------------------------------- //
    
    /**
     * Set 'alt' attribute to current <img> node
     * @param {Ext.form.field.Text} input The input text
     */
    setAlternativeOnBlur: function (input)
    {
        this.setAlternative(input.getValue());
    },
    
    /**
     * Set 'alt' attribute to current <img> node when pressing ENTER or ESC key.
     * @param {Ext.form.field.Text} input The input text
     * @param {Ext.event.Event} e The event object
     */
    setAlternativeOnSpecialKey: function (input, e)
    {
        if (e.getKey() == e.ENTER) 
        {
            e.preventDefault();
            e.stopPropagation();
            this.setAlternative(input.getValue());
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.focus();
        }   
        else if (e.getKey() == e.ESC) 
        {
            e.preventDefault();
            e.stopPropagation();
            input.setValue(this._getAlternative());
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.focus();
        }
    },
    
    /**
     * Set 'alt' attribute to current <img> node
     * @param {String} value The value
     */
    setAlternative: function (value)
    {
        var img = this._currentNode;
        if (img != null)
        {
            if (value == '')
            {
                img.removeAttribute("alt");
            }
            else
            {
                img.alt = value;
            }
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
        }
    },
    
    /**
     * Get the 'alt' attribute of a <img> node
     * @param {HTMLElement} node The <img> node. Can be null to retrieve <img> node from cursor current position
     * @return {String} The alt attribute
     * @private
     */
    _getAlternative: function (node)
    {
        return node.alt || "";
    },
    
    /**
     * Enable/disable controller and set input value according the selected media
     * @param {Ametys.ribbon.element.ui.FieldController} controller The controller
     * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
     * @param {HTMLElement} node The current selected node. Can be null.
     */
    alternativeListener: function (controller, field, node)
    {
        var img = field != null && node != null  ? this._getMedia(field.getEditor(), node) : null;
        if (img)
        {
            controller.setValue (this._getAlternative(img));
        }
        else
        {
            controller.disable();
            controller.setValue ('');
        }
        
        this._currentNode = img;
    },
    
    // ---------------------------------------- //
    //                  FLOAT                   //
    // ---------------------------------------- //
    /**
     * Align image on the text left
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    applyFloatLeft: function (controller) 
    {
        if (this._currentNode != null && controller.getCurrentField() != null)
        {
            var editor = controller.getCurrentField().getEditor();
            editor.execCommand('mceBeginUndoLevel');
            editor.dom.addClass(this._currentNode, "floatleft");
            editor.dom.removeClass(this._currentNode, "floatright");
            editor.execCommand('mceEndUndoLevel');
            editor.focus();
        }
    },
    
    /**
     * Align image on the text right
     * @param {Ametys.ribbon.element.ui.ButtonController}  controller The controller calling this function
     */
    applyFloatRight: function (controller) 
    {
        if (this._currentNode != null && controller.getCurrentField() != null)
        {
            var editor = controller.getCurrentField().getEditor();
            editor.execCommand('mceBeginUndoLevel');
            editor.dom.addClass(this._currentNode, "floatright");
            editor.dom.removeClass(this._currentNode, "floatleft");
            editor.execCommand('mceEndUndoLevel');
            editor.focus();
        }
    },
    
    /**
     * Align image with the text
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
     */
    applyNoFloat: function (controller) 
    {
        if (this._currentNode != null && controller.getCurrentField() != null)
        {
            var editor = controller.getCurrentField().getEditor();
            editor.execCommand('mceBeginUndoLevel');
            editor.dom.removeClass(this._currentNode, "floatright");
            editor.dom.removeClass(this._currentNode, "floatleft");
            editor.execCommand('mceEndUndoLevel');
            editor.focus();
        }
    },
    
    /**
     * Enable/disable and toggle/untoggle controller according the image alignment
     * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
     * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
     * @param {HTMLElement} node The current selected node. Can be null.
     */
    floatListener: function (controller, field, node)
    {
        var img = field != null && node != null  ? this._getMedia(field.getEditor(), node) : null;
        if (img)
        {
            controller.toggle (field.getEditor().dom.hasClass(img, controller.getInitialConfig('css-class')));
        }
        else
        {
            controller.toggle (false);
            controller.disable();
        }

        this._currentNode = img;
    },
    
    /**
     * Enable/disable and toggle/untoggle controller according the image alignment
     * @param {Ametys.ribbon.element.ui.ButtonController}controller The controller
     * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
     * @param {HTMLElement} node The current selected node. Can be null.
     */
    noFloatListener: function (controller, field, node)
    {
        var img = field != null && node != null  ? this._getMedia(field.getEditor(), node) : null;
        if (img)
        {
            controller.toggle (!field.getEditor().dom.hasClass(img, 'floatleft') && !field.getEditor().dom.hasClass(img, 'floatright'));
        }
        else
        {
            controller.toggle (false);
            controller.disable();
        }

        this._currentNode = img;
    },
    
    /**
     * Find the tinyMCE editor impacted by this controler
     * @return {tinymce.Editor} The editor linked to this controller
     * @private
     */
    _getEditor : function ()
    {
        var message = Ametys.message.MessageBus.getCurrentSelectionMessage();
        var contentTarget = message.getTarget(function(target) { return target.getId() == "content" });
        
        if (contentTarget != null)
        {
            var formTarget = contentTarget.getSubtarget(function(target) { return target.getId() == "form" });
            
            if (formTarget != null)
            {
                var form = formTarget.getParameters()['object'];
                var fieldTarget = contentTarget.getSubtarget(function(target) { return target.getId() == "field" });
                if (fieldTarget != null)
                {
                    var field = form.findField(fieldTarget.getParameters()['name']);
                    return field.getEditor();
                }
            }
        }
        return null;
    }

});