/*
 * jQuery File Upload User Interface Plugin 6.0.3
 * https://github.com/blueimp/jQuery-File-Upload
 *
 * Copyright 2010, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 */

/*jslint nomen: true, unparam: true, regexp: true */
/*global window, document, URL, webkitURL, FileReader, jQuery */

(function ($) {
    'use strict';

    // The UI version extends the basic fileupload widget and adds
    // a complete user interface based on the given upload/download
    // templates.
    $.widget('blueimpUI.fileupload', $.blueimp.fileupload, {

        options: {
            // By default, files added to the widget are uploaded as soon
            // as the user clicks on the start buttons. To enable automatic
            // uploads, set the following option to true:
            autoUpload: false,
            // The following option limits the number of files that are
            // allowed to be uploaded using this widget:
            maxNumberOfFiles: undefined,
            // The maximum allowed file size:
            maxFileSize: undefined,
            // The minimum allowed file size:
            minFileSize: 0,
            // The regular expression for allowed file types, matches
            // against either file type or file name:
            acceptFileTypes:  /.+$/i,
            // The regular expression to define for which files a preview
            // image is shown, matched against the file type:
            previewFileTypes: /^image\/(gif|jpeg|png)$/,
            // The maximum file size for preview images:
            previewMaxFileSize: 5000000, // 5MB
            // The maximum width of the preview images:
            previewMaxWidth: 80,
            // The maximum height of the preview images:
            previewMaxHeight: 80,
            // By default, preview images are displayed as canvas elements
            // if supported by the browser. Set the following option to false
            // to always display preview images as img elements:
            previewAsCanvas: true,
            // The expected data type of the upload response, sets the dataType
            // option of the $.ajax upload requests:
            dataType: 'json',

            // The add callback is invoked as soon as files are added to the fileupload
            // widget (via file input selection, drag & drop or add API call).
            // See the basic file upload widget for more information:
            add: function (e, data) {
                var that = $(this).data('fileupload'),
                    files = data.files;
                that._adjustMaxNumberOfFiles(-files.length);
                data.isAdjusted = true;
                data.files.valid = data.isValidated = that._validate(files);
                data.context = that._renderUpload(files)
                    .appendTo(that._files)
                    .data('data', data);
                // Force reflow:
                that._reflow = that._transition && data.context[0].offsetWidth;
                data.context.addClass('in');
                if ((that.options.autoUpload || data.autoUpload) &&
                        data.isValidated) {
                    data.submit();
                }
            },
            // Callback for the start of each file upload request:
            send: function (e, data) {
                if (!data.isValidated) {
                    var that = $(this).data('fileupload');
                    if (!data.isAdjusted) {
                        that._adjustMaxNumberOfFiles(-data.files.length);
                    }
                    if (!that._validate(data.files)) {
                        return false;
                    }
                }
                if (data.context && data.dataType &&
                        data.dataType.substr(0, 6) === 'iframe') {
                    // Iframe Transport does not support progress events.
                    // In lack of an indeterminate progress bar, we set
                    // the progress to 100%, showing the full animated bar:
                    data.context.find('.progressbar div').css(
                        'width',
                        parseInt(100, 10) + '%'
                    );
                }
            },
            // Callback for successful uploads:
            done: function (e, data) {
                var that = $(this).data('fileupload'),
                    template,
                    preview;
                if (data.context) {
                    data.context.each(function (index) {
                        var file = ($.isArray(data.result) &&
                                data.result[index]) || {error: 'emptyResult'};
                        if (file.error) {
                            that._adjustMaxNumberOfFiles(1);
                        }
                        that._transitionCallback(
                            $(this).removeClass('in'),
                            function (node) {
                                template = that._renderDownload([file]);
                                preview = node
                                    .find('.preview img, .preview canvas');
                                if (preview.length) {
                                    template.find('.preview img')
                                        .prop('width', preview.prop('width'))
                                        .prop('height', preview.prop('height'));
                                }
                                template
                                    .replaceAll(node);
                                // Force reflow:
                                that._reflow = that._transition &&
                                    template[0].offsetWidth;
                                template.addClass('in');
                            }
                        );
                    });
                } else {
                    template = that._renderDownload(data.result)
                        .appendTo(that._files);
                    // Force reflow:
                    that._reflow = that._transition && template[0].offsetWidth;
                    template.addClass('in');
                }
            },
            // Callback for failed (abort or error) uploads:
            fail: function (e, data) {
                var that = $(this).data('fileupload'),
                    template;
                that._adjustMaxNumberOfFiles(data.files.length);
                if (data.context) {
                    data.context.each(function (index) {
                        if (data.errorThrown !== 'abort') {
                            var file = data.files[index];
                            file.error = file.error || data.errorThrown ||
                                true;
                            that._transitionCallback(
                                $(this).removeClass('in'),
                                function (node) {
                                    template = that._renderDownload([file])
                                        .replaceAll(node);
                                    // Force reflow:
                                    that._reflow = that._transition &&
                                        template[0].offsetWidth;
                                    template.addClass('in');
                                }
                            );
                        } else {
                            that._transitionCallback(
                                $(this).removeClass('in'),
                                function (node) {
                                    node.remove();
                                }
                            );
                        }
                    });
                } else if (data.errorThrown !== 'abort') {
                    that._adjustMaxNumberOfFiles(-data.files.length);
                    data.context = that._renderUpload(data.files)
                        .appendTo(that._files)
                        .data('data', data);
                    // Force reflow:
                    that._reflow = that._transition && data.context[0].offsetWidth;
                    data.context.addClass('in');
                }
            },
            // Callback for upload progress events:
            progress: function (e, data) {
                if (data.context) {
                    data.context.find('.progressbar div').css(
                        'width',
                        parseInt(data.loaded / data.total * 100, 10) + '%'
                    );
                }
            },
            // Callback for global upload progress events:
            progressall: function (e, data) {
                $(this).find('.fileupload-progressbar div').css(
                    'width',
                    parseInt(data.loaded / data.total * 100, 10) + '%'
                );
            },
            // Callback for uploads start, equivalent to the global ajaxStart event:
            start: function () {
                $(this).find('.fileupload-progressbar')
                    .addClass('in').find('div').css('width', '0%');
            },
            // Callback for uploads stop, equivalent to the global ajaxStop event:
            stop: function () {
                $(this).find('.fileupload-progressbar')
                    .removeClass('in').find('div').css('width', '0%');
            },
            // Callback for file deletion:
            destroy: function (e, data) {
                var that = $(this).data('fileupload');
                if (data.url) {
                    $.ajax(data);
                }
                that._adjustMaxNumberOfFiles(1);
                that._transitionCallback(
                    data.context.removeClass('in'),
                    function (node) {
                        node.remove();
                    }
                );
            }
        },

        // Link handler, that allows to download files
        // by drag & drop of the links to the desktop:
        _enableDragToDesktop: function () {
            var link = $(this),
                url = link.prop('href'),
                name = decodeURIComponent(url.split('/').pop())
                    .replace(/:/g, '-'),
                type = 'application/octet-stream';
            link.bind('dragstart', function (e) {
                try {
                    e.originalEvent.dataTransfer.setData(
                        'DownloadURL',
                        [type, name, url].join(':')
                    );
                } catch (err) {}
            });
        },

        _adjustMaxNumberOfFiles: function (operand) {
            if (typeof this.options.maxNumberOfFiles === 'number') {
                this.options.maxNumberOfFiles += operand;
                if (this.options.maxNumberOfFiles < 1) {
                    this._disableFileInputButton();
                } else {
                    this._enableFileInputButton();
                }
            }
        },

        _formatFileSize: function (bytes) {
            if (typeof bytes !== 'number') {
                return '';
            }
            if (bytes >= 1000000000) {
                return (bytes / 1000000000).toFixed(2) + ' GB';
            }
            if (bytes >= 1000000) {
                return (bytes / 1000000).toFixed(2) + ' MB';
            }
            return (bytes / 1000).toFixed(2) + ' KB';
        },

        _hasError: function (file) {
            if (file.error) {
                return file.error;
            }
            // The number of added files is subtracted from
            // maxNumberOfFiles before validation, so we check if
            // maxNumberOfFiles is below 0 (instead of below 1):
            if (this.options.maxNumberOfFiles < 0) {
                return 'maxNumberOfFiles';
            }
            // Files are accepted if either the file type or the file name
            // matches against the acceptFileTypes regular expression, as
            // only browsers with support for the File API report the type:
            if (!(this.options.acceptFileTypes.test(file.type) ||
                    this.options.acceptFileTypes.test(file.name))) {
                return 'acceptFileTypes';
            }
            if (this.options.maxFileSize &&
                    file.size > this.options.maxFileSize) {
                return 'maxFileSize';
            }
            if (typeof file.size === 'number' &&
                    file.size < this.options.minFileSize) {
                return 'minFileSize';
            }
            return null;
        },

        _validate: function (files) {
            var that = this,
                valid = !!files.length;
            $.each(files, function (index, file) {
                file.error = that._hasError(file);
                if (file.error) {
                    valid = false;
                }
            });
            return valid;
        },

        _renderTemplate: function (func, files) {
            return $(this.options.templateContainer).html(func({
                files: files,
                formatFileSize: this._formatFileSize,
                options: this.options
            })).children();
        },

        _renderUpload: function (files) {
            var that = this,
                options = this.options,
                nodes = this._renderTemplate(options.uploadTemplate, files);
            nodes.find('.preview span').each(function (index, node) {
                var file = files[index];
                if (options.previewFileTypes.test(file.type) &&
                        (!options.previewMaxFileSize ||
                        file.size < options.previewMaxFileSize)) {
                    window.loadImage(
                        files[index],
                        function (img) {
                            $(node).append(img);
                            // Force reflow:
                            that._reflow = that._transition &&
                                node.offsetWidth;
                            $(node).addClass('in');
                        },
                        {
                            maxWidth: options.previewMaxWidth,
                            maxHeight: options.previewMaxHeight,
                            canvas: options.previewAsCanvas
                        }
                    );
                }
            });
            return nodes;
        },

        _renderDownload: function (files) {
            var nodes = this._renderTemplate(
                this.options.downloadTemplate,
                files
            );
            nodes.find('a').each(this._enableDragToDesktop);
            return nodes;
        },

        _startHandler: function (e) {
            e.preventDefault();
            var button = $(this),
                tmpl = button.closest('.template-upload'),
                data = tmpl.data('data');
            if (data && data.submit && !data.jqXHR && data.submit()) {
                button.prop('disabled', true);
            }
        },

        _cancelHandler: function (e) {
            e.preventDefault();
            var tmpl = $(this).closest('.template-upload'),
                data = tmpl.data('data') || {};
            if (!data.jqXHR) {
                data.errorThrown = 'abort';
                e.data.fileupload._trigger('fail', e, data);
            } else {
                data.jqXHR.abort();
            }
        },

        _deleteHandler: function (e) {
            e.preventDefault();
            var button = $(this);
            e.data.fileupload._trigger('destroy', e, {
                context: button.closest('.template-download'),
                url: button.attr('data-url'),
                type: button.attr('data-type'),
                dataType: e.data.fileupload.options.dataType
            });
        },

        _transitionCallback: function (node, callback) {
            var that = this;
            if (this._transition && node.hasClass('fade')) {
                node.bind(
                    this._transitionEnd,
                    function (e) {
                        // Make sure we don't respond to other transitions events
                        // in the container element, e.g. from button elements:
                        if (e.target === node[0]) {
                            node.unbind(that._transitionEnd);
                            callback.call(that, node);
                        }
                    }
                );
            } else {
                callback.call(this, node);
            }
        },

        _initTransitionSupport: function () {
            var that = this,
                style = (document.body || document.documentElement).style,
                suffix = '.' + that.options.namespace;
            that._transition = style.transition !== undefined ||
                style.WebkitTransition !== undefined ||
                style.MozTransition !== undefined ||
                style.MsTransition !== undefined ||
                style.OTransition !== undefined;
            if (that._transition) {
                that._transitionEnd = [
                    'MSTransitionEnd',
                    'webkitTransitionEnd',
                    'transitionend',
                    'oTransitionEnd'
                ].join(suffix + ' ') + suffix;
            }
        },

        _initButtonBarEventHandlers: function () {
            var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
                filesList = this._files,
                ns = this.options.namespace;
            fileUploadButtonBar.find('.start')
                .bind('click.' + ns, function (e) {
                    e.preventDefault();
                    filesList.find('.start button').click();
                });
            fileUploadButtonBar.find('.cancel')
                .bind('click.' + ns, function (e) {
                    e.preventDefault();
                    filesList.find('.cancel button').click();
                });
            fileUploadButtonBar.find('.delete')
                .bind('click.' + ns, function (e) {
                    e.preventDefault();
                    filesList.find('.delete input:checked')
                        .siblings('button').click();
                });
            fileUploadButtonBar.find('.toggle')
                .bind('change.' + ns, function (e) {
                    filesList.find('.delete input').prop(
                        'checked',
                        $(this).is(':checked')
                    );
                });
        },

        _destroyButtonBarEventHandlers: function () {
            this.element.find('.fileupload-buttonbar button')
                .unbind('click.' + this.options.namespace);
            this.element.find('.fileupload-buttonbar .toggle')
                .unbind('change.' + this.options.namespace);
        },

        _initEventHandlers: function () {
            $.blueimp.fileupload.prototype._initEventHandlers.call(this);
            var eventData = {fileupload: this};
            this._files
                .delegate(
                    '.start button',
                    'click.' + this.options.namespace,
                    eventData,
                    this._startHandler
                )
                .delegate(
                    '.cancel button',
                    'click.' + this.options.namespace,
                    eventData,
                    this._cancelHandler
                )
                .delegate(
                    '.delete button',
                    'click.' + this.options.namespace,
                    eventData,
                    this._deleteHandler
                );
            this._initButtonBarEventHandlers();
            this._initTransitionSupport();
        },

        _destroyEventHandlers: function () {
            this._destroyButtonBarEventHandlers();
            this._files
                .undelegate('.start button', 'click.' + this.options.namespace)
                .undelegate('.cancel button', 'click.' + this.options.namespace)
                .undelegate('.delete button', 'click.' + this.options.namespace);
            $.blueimp.fileupload.prototype._destroyEventHandlers.call(this);
        },

        _enableFileInputButton: function () {
            this.element.find('.fileinput-button input')
                .prop('disabled', false)
                .parent().removeClass('disabled');
        },

        _disableFileInputButton: function () {
            this.element.find('.fileinput-button input')
                .prop('disabled', true)
                .parent().addClass('disabled');
        },

        _initTemplates: function () {
            this.options.templateContainer = document.createElement(
                this._files.prop('nodeName')
            );
            this.options.uploadTemplate = window.tmpl('template-upload');
            this.options.downloadTemplate = window.tmpl('template-download');
        },

        _initFiles: function () {
            this._files = this.element.find('.files');
        },

        _create: function () {
            this._initFiles();
            $.blueimp.fileupload.prototype._create.call(this);
            this._initTemplates();
        },

        destroy: function () {
            $.blueimp.fileupload.prototype.destroy.call(this);
        },

        enable: function () {
            $.blueimp.fileupload.prototype.enable.call(this);
            this.element.find('input, button').prop('disabled', false);
            this._enableFileInputButton();
        },

        disable: function () {
            this.element.find('input, button').prop('disabled', true);
            this._disableFileInputButton();
            $.blueimp.fileupload.prototype.disable.call(this);
        }

    });

}(jQuery));
