/*
 *  Copyright 2019 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.
 */

/**
 * This dialog box enables to crop an image.<br>
 */
Ext.define('Ametys.helper.crop.CropDialog', {
    alternateClassName: ['Ametys.helper.CropDialog'],
    extend: 'Ametys.window.DialogBox',
    
    /**
     * @cfg {String} imageId (required) The id of the image to be cropped.
     */
    /**
     * @cfg {String} imageName (required) The name of the image to be cropped.
     */
    /**
     * @cfg {Number} imageSize (required) The size in bytes of the image to be cropped.
     */
    /**
     * @cfg {String} imageViewHref (required) A URL to view the image.
     */
    /**
     * @cfg {String} imageDownloadHref (required) A URL to download the image.
     */
    
    
    /**
     * @cfg {Object} cropUrl The URL for cropping
     * @cfg {String} cropUrl.plugincore The plugin for the crop URL
     * @cfg {String} [cropUrl.url="upload/crop-and-store"] The URL for cropping
     */
    /**
     * @private
     * @property {String} _cropPlugin The plugin for the crop URL
     */
    /**
     * @private
     * @property {String} _cropUrl The URL for cropping
     */
    
    /**
     * @cfg {Function} afterCropFn The callback function to execute after the crop has been made, after the 'Ok' button was clicked.
     * @param {String} id The uploaded image id.
     * @param {String} filename The uploaded image name.
     * @param {Number} fileSize The uploaded image size in bytes.
     * @param {String} viewHref A URL to view the image.
     * @param {String} downloadHref A URL to download the image.
     * @param {String} type The type of the image resource. Can be null.
     */
    /**
     * @private
     * @property {Function} _afterCropFn The callback function to execute after the crop has been made
     */
    
    statics: {
        /**
         * @private
         * @property {Number} __IMAGE_MAX_HEIGHT The max height for displaying the image
         * @readonly
         */
        __IMAGE_MAX_HEIGHT: 600,
        /**
         * @private
         * @property {Number} __IMAGE_MAX_WIDTH The max width for displaying the image
         * @readonly
         */
        __IMAGE_MAX_WIDTH: 600
    },
    
    constructor: function(config)
    {
        Ext.applyIf(config, this._getDefaultConfig(config));
        this.callParent(arguments);
        
        var cropUrlCfg = config.cropUrl || {};
        this._cropPlugin = cropUrlCfg.plugin || 'core';
        this._cropUrl = cropUrlCfg.url || 'upload/crop-and-store';
        
        this._afterCropFn = config.afterCropFn || Ext.emptyFn;
    },
    
    /**
     * Gets the default configuration
     * @param {Object} config The configuration.
     * @param {String} config.imageId The id of the image to be cropped.
     * @param {String} config.imageName The name of the image to be cropped.
     * @param {Number} config.imageSize The size in bytes of the image to be cropped.
     * @param {String} config.imageViewHref A URL to view the image.
     * @param {String} config.imageDownloadHref A URL to download the image.
     * @param {Object} config.actionResult The result of the upload action.
     * @private
     */
    _getDefaultConfig: function(config)
    {
        var fileId = config.imageId;
        var filename = config.imageName;
        var fileSize = config.imageSize;
        var viewHref = config.imageViewHref;
        var downloadHref = config.imageDownloadHref;
        var actionResult = config.actionResult;
        
        return {
            title: "{{i18n PLUGINS_CORE_UI_WIDGET_CROP_IMAGE_DIALOG_TITLE}}",
            iconCls: 'ametysicon-code-html-image decorator-ametysicon-desktop-scissors',
            
            maxHeight: this.statics().__IMAGE_MAX_HEIGHT + 200,
            width: this.statics().__IMAGE_MAX_WIDTH + 100,
            bodyPadding: '10',
            layout: {
                type: "vbox"
            },
            
            defaults: {
                width: '100%'
            },
            items: [{
                xtype: 'component',
                html: "{{i18n PLUGINS_CORE_UI_WIDGET_CROP_IMAGE_DIALOG_HINT1}}",
                cls: 'a-text'
            },
            this._imageComponentCfg(viewHref, filename)
            , {
                xtype: 'component',
                html: "<span class=\"ametysicon-sign-caution\"></span><span style=\"padding-left:5px;\">{{i18n PLUGINS_CORE_UI_WIDGET_CROP_IMAGE_DIALOG_HINT2}}</span>",
                cls: 'a-text-warning',
                style: {
                    textAlign: 'right'
                }
            }],
            
            buttons: [{
                text: "{{i18n PLUGINS_CORE_UI_WIDGET_CROP_IMAGE_DIALOG_BUTTON_OK}}",
                reference: 'okButton',
                handler: Ext.bind(this._onOkButtonClick, this, [fileId, filename, fileSize, viewHref, downloadHref, actionResult], 1),
                scope: this
            }, {
                text: "{{i18n PLUGINS_CORE_UI_WIDGET_CROP_IMAGE_DIALOG_BUTTON_CANCEL}}",
                handler: function(btn) { this.close(); },
                scope: this
            }],
            
            referenceHolder: true,
            defaultButton: 'okButton',
            defaultButtonTarget: 'el',
            closeAction: 'destroy',
            listeners: {
                'destroy': function() {
                    this.getLogger().info('Destroying Jcrop');
                    this._jcropApi.destroy();
                    this._jcropApi = null;
                },
                scope: this
            }
        };
    },
    
    /**
     * @private
     * Called when 'Ok' button is pressed
     * @param {Ext.button.Button} btn The 'Ok' button
     * @param {String} id The uploaded file id.
     * @param {String} filename The uploaded file name.
     * @param {Number} fileSize The uploaded file size in bytes.
     * @param {String} viewHref A URL to view the file.
     * @param {String} downloadHref A URL to download the file.
     * @param {Object} actionResult The result of the upload action.
     */
    _onOkButtonClick: function(btn, id, filename, fileSize, viewHref, downloadHref, actionResult)
    {
        var cropping = this._getCropping();
        if (cropping === '100%')
        {
            this.close();
            this._afterCropFn(id, filename, fileSize, viewHref, downloadHref, actionResult);
        }
        else
        {
            this._sendCropping(id, filename, fileSize, viewHref, downloadHref, actionResult, cropping);
        }
    },
    
    /**
     * @private
     * Gets the configuration for the Image component
     * @param {String} viewHref The href for the image
     * @param {String} filename The image file name
     * @return {Object} the configuration for the Image component
     */
    _imageComponentCfg: function(viewHref, filename)
    {
        // we could have used https://developer.mozilla.org/en-US/docs/Web/API/URL to better manage URL, but for browser compatibility purpose, we have to do this :(
        var src = viewHref + (viewHref.indexOf('?') === -1 ? '?' : '&') + 'maxHeight=' + this.statics().__IMAGE_MAX_HEIGHT + '&maxWidth=' + this.statics().__IMAGE_MAX_WIDTH;
        
        return {
            xtype: 'container',
            itemId: 'imageContainer',
            layout: {
                type: "vbox",
                align: "middle"
            },
            items: [{
                // we do not use xtype:'image' component as it will break layout when plugin Jcrop will modify the <img> element and add elements next to it
                xtype: 'component',
                itemId: 'image',
                padding: '15 0 10 0',
                html: '<img src="' + src + '" alt="' + filename + '">',
                listeners: {
                    'afterrender': this._afterImageComponentRendering,
                    scope: this
                }
            }]
        };
    },
    
    /**
     * @private
     * Callback called after the image component is rendered.
     * @param {Ext.Component} cmp The image component
     */
    _afterImageComponentRendering: function(cmp)
    {
        var cmpEl = cmp.getEl();
        var cmpDom = cmpEl.dom;
        var imageHtmlElement = cmpDom.tagName.toLowerCase() === 'img' ? cmpDom : cmpEl.down('img', true);
        var me = this;
        var logger = this.getLogger();
        
        if (imageHtmlElement.complete)
        {
            onImageLoaded();
        }
        else
        {
            imageHtmlElement.addEventListener('load', onImageLoaded);
        }
        
        function onImageLoaded()
        {
            logger.info('Image loaded by browser');
            cmp.updateLayout();
            attachJcrop();
        }
        
        function attachJcrop()
        {
            var imageWidth = imageHtmlElement.width;
            var imageHeight = imageHtmlElement.height;
            me._attachJcrop.apply(me, [imageHtmlElement, cmp, imageWidth, imageHeight]);
        }
    },
    
    /**
     * @private
     * Attaches the Jcrop plugin to the image component
     * @param {HTMLImageElement} imageHtmlElement The HTML element for the image
     * @param {Ext.Component} cmp The image component
     * @param {Number} imageWidth The image width
     * @param {Number} imageHeight The image height
     */
    _attachJcrop: function(imageHtmlElement, cmp, imageWidth, imageHeight)
    {
        var me = this;
        this.getLogger().info('Attaching Jcrop plugin');
        
        var options = {
            bgFade: true,
            bgOpacity: 0.2,
            bgColor: 'white'
        };
        
        jQuery(imageHtmlElement).Jcrop(options, function() {
            me._jcropApi = this;
            cmp.updateLayout();
        });
    },
    
    /**
     * @private
     * Gets the current cropping. Values are between 0 and 1 (as ratio)
     * @return {String/Object} The current cropping. Is a String valued to '100%' if it's a full image cropping, is an object (containing keys x1, y1, w, h) otherwise.
     */
    _getCropping: function()
    {
        var bounds = this._jcropApi.getBounds();
        var maxWidth = bounds[0];
        var maxHeight = bounds[1];
        var coords = this._jcropApi.tellSelect();
        
        if (coords.x === 0 && coords.y === 0 && coords.w === maxWidth && coords.h === maxHeight /* jcrop selection is full */
            || coords.w === 0 && coords.h === 0 /* jcrop selection is released */)
        {
            return '100%';
        }
        
        var relativeX1 = coords.x / maxWidth;
        var relativeY1 = coords.y / maxHeight;
//        var relativeX2 = coords.x2 / maxWidth;
//        var relativeY2 = coords.y2 / maxHeight;
        var relativeWidth = coords.w / maxWidth;
        var relativeHeight = coords.h / maxHeight;
        
        return {
            x1: relativeX1,
            y1: relativeY1,
            w: relativeWidth,
            h: relativeHeight
        };
    },
    
    /**
     * @private
     * Send the cropping values to the server, which will effectively crop the original image
     * @param {String} id The uploaded file id.
     * @param {String} filename The uploaded file name.
     * @param {Number} fileSize The uploaded file size in bytes.
     * @param {String} viewHref A URL to view the file.
     * @param {String} downloadHref A URL to download the file.
     * @param {Object} actionResult The result of the upload action.
     * @param {Object} cropping The cropping object
     * @param {Number} cropping.x1 The X coordinate of the upper-left corner of the rectangular cropping region
     * @param {Number} cropping.y1 The Y coordinate of the upper-left corner of the rectangular cropping region
     * @param {Number} cropping.w The width of the rectangular cropping region
     * @param {Number} cropping.h The height of the rectangular cropping region
     */
    _sendCropping: function(id, filename, fileSize, viewHref, downloadHref, actionResult, cropping)
    {
        Ametys.data.ServerComm.send({
            plugin: this._cropPlugin,
            url: this._cropUrl,
            parameters: {
                imageId: id,
                x1: cropping.x1,
                y1: cropping.y1,
                width: cropping.w,
                height: cropping.h
            },
            callback: {
                handler: this._afterCroppingSent,
                scope: this
            },
            responseType: 'text',
            waitMessage: {
                msg: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_SUBMITFORM_MSG}}",
                target: this,
                style: {
                    zIndex: 1000 // in order to be above the jcrop-tracker (which is at z-index: 360)
                }
            },
            errorMessage: {
                msg: "{{i18n PLUGINS_CORE_UI_FILEUPLOAD_ERROR_ON_SERVER}}"
            }
        });
    },
    
    /**
     * @private
     * Callback called after the server successfully cropped the original image with the given values.
     * @param {Object} response The response
     * @param {Object[]} arguments The arguments
     */
    _afterCroppingSent: function(response, arguments)
    {
        var result = JSON.parse(response.textContent);
        this.close();
        this._afterCropFn(result.id, result.filename, result.size, Ametys.CONTEXT_PATH + result.viewHref, Ametys.CONTEXT_PATH + result.downloadHref, result);
    }
});