﻿///<reference path="~/scripts/angular.js" />
/*
 angular-file-upload v1.1.5
 https://github.com/nervgh/angular-file-upload
*/
(function (angular, factory) {
    if (typeof define === 'function' && define.amd) {
        define('angular-file-upload', ['angular'], function (angular) {
            return factory(angular);
        });
    } else {
        return factory(angular);
    }
}(typeof angular === 'undefined' ? null : angular, function (angular) {

    var module = angular.module('angularFileUpload', []);

    'use strict';

    /**
     * Classes
     *
     * FileUploader
     * FileUploader.FileLikeObject
     * FileUploader.FileItem
     * FileUploader.FileDirective
     * FileUploader.FileSelect
     * FileUploader.FileDrop
     * FileUploader.FileOver
     */

    module


        .value('fileUploaderOptions', {
            url: '/',
            alias: 'file',
            headers: {},
            queue: [],
            progress: 0,
            autoUpload: false,
            removeAfterUpload: false,
            method: 'POST',
            filters: [],
            formData: [],
            queueLimit: Number.MAX_VALUE,
            withCredentials: false
        })


        .factory('FileUploader', ['fileUploaderOptions', '$rootScope', '$http', '$window', '$compile',
            function (fileUploaderOptions, $rootScope, $http, $window, $compile) {
                /**
                 * Creates an instance of FileUploader
                 * @param {Object} [options]
                 * @constructor
                 */
                function FileUploader(options) {
                    var settings = angular.copy(fileUploaderOptions);
                    angular.extend(this, settings, options, {
                        isUploading: false,
                        _nextIndex: 0,
                        _failFilterIndex: -1,
                        _directives: { select: [], drop: [], over: [] }
                    });

                    // add default filters
                    this.filters.unshift({ name: 'queueLimit', fn: this._queueLimitFilter });
                    this.filters.unshift({ name: 'folder', fn: this._folderFilter });
                }
                /**********************
                 * PUBLIC
                 **********************/
                /**
                 * Checks a support the html5 uploader
                 * @returns {Boolean}
                 * @readonly
                 */
                FileUploader.prototype.isHTML5 = !!($window.File && $window.FormData);
                /**
                 * Adds items to the queue
                 * @param {File|HTMLInputElement|Object|FileList|Array<Object>} files
                 * @param {Object} [options]
                 * @param {Array<Function>|String} filters
                 */
                FileUploader.prototype.addToQueue = function (files, options, filters) {
                    var list = this.isArrayLikeObject(files) ? files : [files];
                    var arrayOfFilters = this._getFilters(filters);
                    var count = this.queue.length;
                    var addedFileItems = [];

                    angular.forEach(list, function (some /*{File|HTMLInputElement|Object}*/) {
                        var temp = new FileUploader.FileLikeObject(some);

                        if (this._isValidFile(temp, arrayOfFilters, options)) {
                            var fileItem = new FileUploader.FileItem(this, some, options);
                            addedFileItems.push(fileItem);
                            this.queue.push(fileItem);
                            this._onAfterAddingFile(fileItem);
                        } else {
                            var filter = this.filters[this._failFilterIndex];
                            this._onWhenAddingFileFailed(temp, filter, options);
                        }
                    }, this);

                    if (this.queue.length !== count) {
                        this._onAfterAddingAll(addedFileItems);
                        this.progress = this._getTotalProgress();
                    }

                    this._render();
                    if (this.autoUpload) this.uploadAll();
                };
                /**
                 * Remove items from the queue. Remove last: index = -1
                 * @param {FileItem|Number} value
                 */
                FileUploader.prototype.removeFromQueue = function (value) {
                    var index = this.getIndexOfItem(value);
                    var item = this.queue[index];
                    if (item.isUploading) item.cancel();
                    this.queue.splice(index, 1);
                    item._destroy();
                    this.progress = this._getTotalProgress();
                };
                /**
                 * Clears the queue
                 */
                FileUploader.prototype.clearQueue = function () {
                    while (this.queue.length) {
                        this.queue[0].remove();
                    }
                    this.progress = 0;
                };
                /**
                 * Uploads a item from the queue
                 * @param {FileItem|Number} value
                 */
                FileUploader.prototype.uploadItem = function (value) {
                    var index = this.getIndexOfItem(value);
                    var item = this.queue[index];
                    var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport';

                    item._prepareToUploading();
                    if (this.isUploading) return;

                    this.isUploading = true;
                    this[transport](item);
                };
                /**
                 * Cancels uploading of item from the queue
                 * @param {FileItem|Number} value
                 */
                FileUploader.prototype.cancelItem = function (value) {
                    var index = this.getIndexOfItem(value);
                    var item = this.queue[index];
                    var prop = this.isHTML5 ? '_xhr' : '_form';
                    if (item && item.isUploading) item[prop].abort();
                };
                /**
                 * Uploads all not uploaded items of queue
                 */
                FileUploader.prototype.uploadAll = function () {
                    var items = this.getNotUploadedItems().filter(function (item) {
                        return !item.isUploading;
                    });
                    if (!items.length) return;

                    angular.forEach(items, function (item) {
                        item._prepareToUploading();
                    });
                    items[0].upload();
                };
                /**
                 * Cancels all uploads
                 */
                FileUploader.prototype.cancelAll = function () {
                    var items = this.getNotUploadedItems();
                    angular.forEach(items, function (item) {
                        item.cancel();
                    });
                };
                /**
                 * Returns "true" if value an instance of File
                 * @param {*} value
                 * @returns {Boolean}
                 * @private
                 */
                FileUploader.prototype.isFile = function (value) {
                    var fn = $window.File;
                    return (fn && value instanceof fn);
                };
                /**
                 * Returns "true" if value an instance of FileLikeObject
                 * @param {*} value
                 * @returns {Boolean}
                 * @private
                 */
                FileUploader.prototype.isFileLikeObject = function (value) {
                    return value instanceof FileUploader.FileLikeObject;
                };
                /**
                 * Returns "true" if value is array like object
                 * @param {*} value
                 * @returns {Boolean}
                 */
                FileUploader.prototype.isArrayLikeObject = function (value) {
                    return (angular.isObject(value) && 'length' in value);
                };
                /**
                 * Returns a index of item from the queue
                 * @param {Item|Number} value
                 * @returns {Number}
                 */
                FileUploader.prototype.getIndexOfItem = function (value) {
                    return angular.isNumber(value) ? value : this.queue.indexOf(value);
                };
                /**
                 * Returns not uploaded items
                 * @returns {Array}
                 */
                FileUploader.prototype.getNotUploadedItems = function () {
                    return this.queue.filter(function (item) {
                        return !item.isUploaded;
                    });
                };
                /**
                 * Returns items ready for upload
                 * @returns {Array}
                 */
                FileUploader.prototype.getReadyItems = function () {
                    return this.queue
                        .filter(function (item) {
                            return (item.isReady && !item.isUploading);
                        })
                        .sort(function (item1, item2) {
                            return item1.index - item2.index;
                        });
                };
                /**
                 * Destroys instance of FileUploader
                 */
                FileUploader.prototype.destroy = function () {
                    angular.forEach(this._directives, function (key) {
                        angular.forEach(this._directives[key], function (object) {
                            object.destroy();
                        }, this);
                    }, this);
                };
                /**
                 * Callback
                 * @param {Array} fileItems
                 */
                FileUploader.prototype.onAfterAddingAll = function (fileItems) { };
                /**
                 * Callback
                 * @param {FileItem} fileItem
                 */
                FileUploader.prototype.onAfterAddingFile = function (fileItem) { };
                /**
                 * Callback
                 * @param {File|Object} item
                 * @param {Object} filter
                 * @param {Object} options
                 * @private
                 */
                FileUploader.prototype.onWhenAddingFileFailed = function (item, filter, options) { };
                /**
                 * Callback
                 * @param {FileItem} fileItem
                 */
                FileUploader.prototype.onBeforeUploadItem = function (fileItem) { };
                /**
                 * Callback
                 * @param {FileItem} fileItem
                 * @param {Number} progress
                 */
                FileUploader.prototype.onProgressItem = function (fileItem, progress) { };
                /**
                 * Callback
                 * @param {Number} progress
                 */
                FileUploader.prototype.onProgressAll = function (progress) { };
                /**
                 * Callback
                 * @param {FileItem} item
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 */
                FileUploader.prototype.onSuccessItem = function (item, response, status, headers) { };
                /**
                 * Callback
                 * @param {FileItem} item
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 */
                FileUploader.prototype.onErrorItem = function (item, response, status, headers) { };
                /**
                 * Callback
                 * @param {FileItem} item
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 */
                FileUploader.prototype.onCancelItem = function (item, response, status, headers) { };
                /**
                 * Callback
                 * @param {FileItem} item
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 */
                FileUploader.prototype.onCompleteItem = function (item, response, status, headers) { };
                /**
                 * Callback
                 */
                FileUploader.prototype.onCompleteAll = function () { };
                /**********************
                 * PRIVATE
                 **********************/
                /**
                 * Returns the total progress
                 * @param {Number} [value]
                 * @returns {Number}
                 * @private
                 */
                FileUploader.prototype._getTotalProgress = function (value) {
                    if (this.removeAfterUpload) return value || 0;

                    var notUploaded = this.getNotUploadedItems().length;
                    var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length;
                    var ratio = 100 / this.queue.length;
                    var current = (value || 0) * ratio / 100;

                    return Math.round(uploaded * ratio + current);
                };
                /**
                 * Returns array of filters
                 * @param {Array<Function>|String} filters
                 * @returns {Array<Function>}
                 * @private
                 */
                FileUploader.prototype._getFilters = function (filters) {
                    if (angular.isUndefined(filters)) return this.filters;
                    if (angular.isArray(filters)) return filters;
                    var names = filters.match(/[^\s,]+/g);
                    return this.filters.filter(function (filter) {
                        return names.indexOf(filter.name) !== -1;
                    }, this);
                };
                /**
                 * Updates html
                 * @private
                 */
                FileUploader.prototype._render = function () {
                    if (!$rootScope.$$phase) $rootScope.$apply();
                };
                /**
                 * Returns "true" if item is a file (not folder)
                 * @param {File|FileLikeObject} item
                 * @returns {Boolean}
                 * @private
                 */
                FileUploader.prototype._folderFilter = function (item) {
                    return !!(item.size || item.type);
                };
                /**
                 * Returns "true" if the limit has not been reached
                 * @returns {Boolean}
                 * @private
                 */
                FileUploader.prototype._queueLimitFilter = function () {
                    return this.queue.length < this.queueLimit;
                };
                /**
                 * Returns "true" if file pass all filters
                 * @param {File|Object} file
                 * @param {Array<Function>} filters
                 * @param {Object} options
                 * @returns {Boolean}
                 * @private
                 */
                FileUploader.prototype._isValidFile = function (file, filters, options) {
                    this._failFilterIndex = -1;
                    return !filters.length ? true : filters.every(function (filter) {
                        this._failFilterIndex++;
                        return filter.fn.call(this, file, options);
                    }, this);
                };
                /**
                 * Checks whether upload successful
                 * @param {Number} status
                 * @returns {Boolean}
                 * @private
                 */
                FileUploader.prototype._isSuccessCode = function (status) {
                    return (status >= 200 && status < 300) || status === 304;
                };
                /**
                 * Transforms the server response
                 * @param {*} response
                 * @param {Object} headers
                 * @returns {*}
                 * @private
                 */
                FileUploader.prototype._transformResponse = function (response, headers) {
                    var headersGetter = this._headersGetter(headers);
                    angular.forEach($http.defaults.transformResponse, function (transformFn) {
                        response = transformFn(response, headersGetter);
                    });
                    return response;
                };
                /**
                 * Parsed response headers
                 * @param headers
                 * @returns {Object}
                 * @see https://github.com/angular/angular.js/blob/master/src/ng/http.js
                 * @private
                 */
                FileUploader.prototype._parseHeaders = function (headers) {
                    var parsed = {}, key, val, i;

                    if (!headers) return parsed;

                    angular.forEach(headers.split('\n'), function (line) {
                        i = line.indexOf(':');
                        key = line.slice(0, i).trim().toLowerCase();
                        val = line.slice(i + 1).trim();

                        if (key) {
                            parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
                        }
                    });

                    return parsed;
                };
                /**
                 * Returns function that returns headers
                 * @param {Object} parsedHeaders
                 * @returns {Function}
                 * @private
                 */
                FileUploader.prototype._headersGetter = function (parsedHeaders) {
                    return function (name) {
                        if (name) {
                            return parsedHeaders[name.toLowerCase()] || null;
                        }
                        return parsedHeaders;
                    };
                };
                /**
                 * The XMLHttpRequest transport
                 * @param {FileItem} item
                 * @private
                 */
                FileUploader.prototype._xhrTransport = function (item) {
                    var xhr = item._xhr = new XMLHttpRequest();
                    var form = new FormData();
                    var that = this;

                    that._onBeforeUploadItem(item);

                    angular.forEach(item.formData, function (obj) {
                        angular.forEach(obj, function (value, key) {
                            form.append(key, value);
                        });
                    });

                    form.append(item.alias, item._file, item.file.name);

                    xhr.upload.onprogress = function (event) {
                        var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
                        that._onProgressItem(item, progress);
                    };

                    xhr.onload = function () {
                        var headers = that._parseHeaders(xhr.getAllResponseHeaders());
                        var response = that._transformResponse(xhr.response, headers);
                        var gist = that._isSuccessCode(xhr.status) ? 'Success' : 'Error';
                        var method = '_on' + gist + 'Item';
                        that[method](item, response, xhr.status, headers);
                        that._onCompleteItem(item, response, xhr.status, headers);
                    };

                    xhr.onerror = function () {
                        var headers = that._parseHeaders(xhr.getAllResponseHeaders());
                        var response = that._transformResponse(xhr.response, headers);
                        that._onErrorItem(item, response, xhr.status, headers);
                        that._onCompleteItem(item, response, xhr.status, headers);
                    };

                    xhr.onabort = function () {
                        var headers = that._parseHeaders(xhr.getAllResponseHeaders());
                        var response = that._transformResponse(xhr.response, headers);
                        that._onCancelItem(item, response, xhr.status, headers);
                        that._onCompleteItem(item, response, xhr.status, headers);
                    };

                    xhr.open(item.method, item.url, true);

                    xhr.withCredentials = item.withCredentials;

                    angular.forEach(item.headers, function (value, name) {
                        xhr.setRequestHeader(name, value);
                    });

                    xhr.send(form);
                    this._render();
                };
                /**
                 * The IFrame transport
                 * @param {FileItem} item
                 * @private
                 */
                FileUploader.prototype._iframeTransport = function (item) {
                    var form = angular.element('<form style="display: none;" />');
                    var iframe = angular.element('<iframe name="iframeTransport' + Date.now() + '">');
                    var input = item._input;
                    var that = this;

                    if (item._form) item._form.replaceWith(input); // remove old form
                    item._form = form; // save link to new form

                    that._onBeforeUploadItem(item);

                    input.prop('name', item.alias);

                    angular.forEach(item.formData, function (obj) {
                        angular.forEach(obj, function (value, key) {
                            var element = angular.element('<input type="hidden" name="' + key + '" />');
                            element.val(value);
                            form.append(element);
                        });
                    });

                    form.prop({
                        action: item.url,
                        method: 'POST',
                        target: iframe.prop('name'),
                        enctype: 'multipart/form-data',
                        encoding: 'multipart/form-data' // old IE
                    });

                    iframe.bind('load', function () {
                        try {
                            // Fix for legacy IE browsers that loads internal error page
                            // when failed WS response received. In consequence iframe
                            // content access denied error is thrown becouse trying to
                            // access cross domain page. When such thing occurs notifying
                            // with empty response object. See more info at:
                            // http://stackoverflow.com/questions/151362/access-is-denied-error-on-accessing-iframe-document-object
                            // Note that if non standard 4xx or 5xx error code returned
                            // from WS then response content can be accessed without error
                            // but 'XHR' status becomes 200. In order to avoid confusion
                            // returning response via same 'success' event handler.

                            // fixed angular.contents() for iframes
                            var html = iframe[0].contentDocument.body.innerHTML;
                        } catch (e) { }

                        var xhr = { response: html, status: 200, dummy: true };
                        var headers = {};
                        var response = that._transformResponse(xhr.response, headers);

                        that._onSuccessItem(item, response, xhr.status, headers);
                        that._onCompleteItem(item, response, xhr.status, headers);
                    });

                    form.abort = function () {
                        var xhr = { status: 0, dummy: true };
                        var headers = {};
                        var response;

                        iframe.unbind('load').prop('src', 'javascript:false;');
                        form.replaceWith(input);

                        that._onCancelItem(item, response, xhr.status, headers);
                        that._onCompleteItem(item, response, xhr.status, headers);
                    };

                    input.after(form);
                    form.append(input).append(iframe);

                    form[0].submit();
                    this._render();
                };
                /**
                 * Inner callback
                 * @param {File|Object} item
                 * @param {Object} filter
                 * @param {Object} options
                 * @private
                 */
                FileUploader.prototype._onWhenAddingFileFailed = function (item, filter, options) {
                    this.onWhenAddingFileFailed(item, filter, options);
                };
                /**
                 * Inner callback
                 * @param {FileItem} item
                 */
                FileUploader.prototype._onAfterAddingFile = function (item) {
                    this.onAfterAddingFile(item);
                };
                /**
                 * Inner callback
                 * @param {Array<FileItem>} items
                 */
                FileUploader.prototype._onAfterAddingAll = function (items) {
                    this.onAfterAddingAll(items);
                };
                /**
                 *  Inner callback
                 * @param {FileItem} item
                 * @private
                 */
                FileUploader.prototype._onBeforeUploadItem = function (item) {
                    item._onBeforeUpload();
                    this.onBeforeUploadItem(item);
                };
                /**
                 * Inner callback
                 * @param {FileItem} item
                 * @param {Number} progress
                 * @private
                 */
                FileUploader.prototype._onProgressItem = function (item, progress) {
                    var total = this._getTotalProgress(progress);
                    this.progress = total;
                    item._onProgress(progress);
                    this.onProgressItem(item, progress);
                    this.onProgressAll(total);
                    this._render();
                };
                /**
                 * Inner callback
                 * @param {FileItem} item
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 * @private
                 */
                FileUploader.prototype._onSuccessItem = function (item, response, status, headers) {
                    item._onSuccess(response, status, headers);
                    this.onSuccessItem(item, response, status, headers);
                };
                /**
                 * Inner callback
                 * @param {FileItem} item
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 * @private
                 */
                FileUploader.prototype._onErrorItem = function (item, response, status, headers) {
                    item._onError(response, status, headers);
                    this.onErrorItem(item, response, status, headers);
                };
                /**
                 * Inner callback
                 * @param {FileItem} item
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 * @private
                 */
                FileUploader.prototype._onCancelItem = function (item, response, status, headers) {
                    item._onCancel(response, status, headers);
                    this.onCancelItem(item, response, status, headers);
                };
                /**
                 * Inner callback
                 * @param {FileItem} item
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 * @private
                 */
                FileUploader.prototype._onCompleteItem = function (item, response, status, headers) {
                    item._onComplete(response, status, headers);
                    this.onCompleteItem(item, response, status, headers);

                    var nextItem = this.getReadyItems()[0];
                    this.isUploading = false;

                    if (angular.isDefined(nextItem)) {
                        nextItem.upload();
                        return;
                    }

                    this.onCompleteAll();
                    this.progress = this._getTotalProgress();
                    this._render();
                };
                /**********************
                 * STATIC
                 **********************/
                /**
                 * @borrows FileUploader.prototype.isFile
                 */
                FileUploader.isFile = FileUploader.prototype.isFile;
                /**
                 * @borrows FileUploader.prototype.isFileLikeObject
                 */
                FileUploader.isFileLikeObject = FileUploader.prototype.isFileLikeObject;
                /**
                 * @borrows FileUploader.prototype.isArrayLikeObject
                 */
                FileUploader.isArrayLikeObject = FileUploader.prototype.isArrayLikeObject;
                /**
                 * @borrows FileUploader.prototype.isHTML5
                 */
                FileUploader.isHTML5 = FileUploader.prototype.isHTML5;
                /**
                 * Inherits a target (Class_1) by a source (Class_2)
                 * @param {Function} target
                 * @param {Function} source
                 */
                FileUploader.inherit = function (target, source) {
                    target.prototype = Object.create(source.prototype);
                    target.prototype.constructor = target;
                    target.super_ = source;
                };
                FileUploader.FileLikeObject = FileLikeObject;
                FileUploader.FileItem = FileItem;
                FileUploader.FileDirective = FileDirective;
                FileUploader.FileSelect = FileSelect;
                FileUploader.FileDrop = FileDrop;
                FileUploader.FileOver = FileOver;

                // ---------------------------

                /**
                 * Creates an instance of FileLikeObject
                 * @param {File|HTMLInputElement|Object} fileOrInput
                 * @constructor
                 */
                function FileLikeObject(fileOrInput) {
                    var isInput = angular.isElement(fileOrInput);
                    var fakePathOrObject = isInput ? fileOrInput.value : fileOrInput;
                    var postfix = angular.isString(fakePathOrObject) ? 'FakePath' : 'Object';
                    var method = '_createFrom' + postfix;
                    this[method](fakePathOrObject);
                }

                /**
                 * Creates file like object from fake path string
                 * @param {String} path
                 * @private
                 */
                FileLikeObject.prototype._createFromFakePath = function (path) {
                    this.lastModifiedDate = null;
                    this.size = null;
                    this.type = 'like/' + path.slice(path.lastIndexOf('.') + 1).toLowerCase();
                    this.name = path.slice(path.lastIndexOf('/') + path.lastIndexOf('\\') + 2);
                };
                /**
                 * Creates file like object from object
                 * @param {File|FileLikeObject} object
                 * @private
                 */
                FileLikeObject.prototype._createFromObject = function (object) {
                    this.lastModifiedDate = angular.copy(object.lastModifiedDate);
                    this.size = object.size;
                    this.type = object.type;
                    this.name = object.name;
                };

                // ---------------------------

                /**
                 * Creates an instance of FileItem
                 * @param {FileUploader} uploader
                 * @param {File|HTMLInputElement|Object} some
                 * @param {Object} options
                 * @constructor
                 */
                function FileItem(uploader, some, options) {
                    var isInput = angular.isElement(some);
                    var input = isInput ? angular.element(some) : null;
                    var file = !isInput ? some : null;

                    angular.extend(this, {
                        url: uploader.url,
                        alias: uploader.alias,
                        headers: angular.copy(uploader.headers),
                        formData: angular.copy(uploader.formData),
                        removeAfterUpload: uploader.removeAfterUpload,
                        withCredentials: uploader.withCredentials,
                        method: uploader.method
                    }, options, {
                        uploader: uploader,
                        file: new FileUploader.FileLikeObject(some),
                        isReady: false,
                        isUploading: false,
                        isUploaded: false,
                        isSuccess: false,
                        isCancel: false,
                        isError: false,
                        progress: 0,
                        index: null,
                        _file: file,
                        _input: input
                    });

                    if (input) this._replaceNode(input);
                }
                /**********************
                 * PUBLIC
                 **********************/
                /**
                 * Uploads a FileItem
                 */
                FileItem.prototype.upload = function () {
                    this.uploader.uploadItem(this);
                };
                /**
                 * Cancels uploading of FileItem
                 */
                FileItem.prototype.cancel = function () {
                    this.uploader.cancelItem(this);
                };
                /**
                 * Removes a FileItem
                 */
                FileItem.prototype.remove = function () {
                    this.uploader.removeFromQueue(this);
                };
                /**
                 * Callback
                 * @private
                 */
                FileItem.prototype.onBeforeUpload = function () { };
                /**
                 * Callback
                 * @param {Number} progress
                 * @private
                 */
                FileItem.prototype.onProgress = function (progress) { };
                /**
                 * Callback
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 */
                FileItem.prototype.onSuccess = function (response, status, headers) { };
                /**
                 * Callback
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 */
                FileItem.prototype.onError = function (response, status, headers) { };
                /**
                 * Callback
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 */
                FileItem.prototype.onCancel = function (response, status, headers) { };
                /**
                 * Callback
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 */
                FileItem.prototype.onComplete = function (response, status, headers) { };
                /**********************
                 * PRIVATE
                 **********************/
                /**
                 * Inner callback
                 */
                FileItem.prototype._onBeforeUpload = function () {
                    this.isReady = true;
                    this.isUploading = true;
                    this.isUploaded = false;
                    this.isSuccess = false;
                    this.isCancel = false;
                    this.isError = false;
                    this.progress = 0;
                    this.onBeforeUpload();
                };
                /**
                 * Inner callback
                 * @param {Number} progress
                 * @private
                 */
                FileItem.prototype._onProgress = function (progress) {
                    this.progress = progress;
                    this.onProgress(progress);
                };
                /**
                 * Inner callback
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 * @private
                 */
                FileItem.prototype._onSuccess = function (response, status, headers) {
                    this.isReady = false;
                    this.isUploading = false;
                    this.isUploaded = true;
                    this.isSuccess = true;
                    this.isCancel = false;
                    this.isError = false;
                    this.progress = 100;
                    this.index = null;
                    this.onSuccess(response, status, headers);
                };
                /**
                 * Inner callback
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 * @private
                 */
                FileItem.prototype._onError = function (response, status, headers) {
                    this.isReady = false;
                    this.isUploading = false;
                    this.isUploaded = true;
                    this.isSuccess = false;
                    this.isCancel = false;
                    this.isError = true;
                    this.progress = 0;
                    this.index = null;
                    this.onError(response, status, headers);
                };
                /**
                 * Inner callback
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 * @private
                 */
                FileItem.prototype._onCancel = function (response, status, headers) {
                    this.isReady = false;
                    this.isUploading = false;
                    this.isUploaded = false;
                    this.isSuccess = false;
                    this.isCancel = true;
                    this.isError = false;
                    this.progress = 0;
                    this.index = null;
                    this.onCancel(response, status, headers);
                };
                /**
                 * Inner callback
                 * @param {*} response
                 * @param {Number} status
                 * @param {Object} headers
                 * @private
                 */
                FileItem.prototype._onComplete = function (response, status, headers) {
                    this.onComplete(response, status, headers);
                    if (this.removeAfterUpload) this.remove();
                };
                /**
                 * Destroys a FileItem
                 */
                FileItem.prototype._destroy = function () {
                    if (this._input) this._input.remove();
                    if (this._form) this._form.remove();
                    delete this._form;
                    delete this._input;
                };
                /**
                 * Prepares to uploading
                 * @private
                 */
                FileItem.prototype._prepareToUploading = function () {
                    this.index = this.index || ++this.uploader._nextIndex;
                    this.isReady = true;
                };
                /**
                 * Replaces input element on his clone
                 * @param {JQLite|jQuery} input
                 * @private
                 */
                FileItem.prototype._replaceNode = function (input) {
                    var clone = $compile(input.clone())(input.scope());
                    clone.prop('value', null); // FF fix
                    input.css('display', 'none');
                    input.after(clone); // remove jquery dependency
                };

                // ---------------------------

                /**
                 * Creates instance of {FileDirective} object
                 * @param {Object} options
                 * @param {Object} options.uploader
                 * @param {HTMLElement} options.element
                 * @param {Object} options.events
                 * @param {String} options.prop
                 * @constructor
                 */
                function FileDirective(options) {
                    angular.extend(this, options);
                    this.uploader._directives[this.prop].push(this);
                    this._saveLinks();
                    this.bind();
                }
                /**
                 * Map of events
                 * @type {Object}
                 */
                FileDirective.prototype.events = {};
                /**
                 * Binds events handles
                 */
                FileDirective.prototype.bind = function () {
                    for (var key in this.events) {
                        var prop = this.events[key];
                        this.element.bind(key, this[prop]);
                    }
                };
                /**
                 * Unbinds events handles
                 */
                FileDirective.prototype.unbind = function () {
                    for (var key in this.events) {
                        this.element.unbind(key, this.events[key]);
                    }
                };
                /**
                 * Destroys directive
                 */
                FileDirective.prototype.destroy = function () {
                    var index = this.uploader._directives[this.prop].indexOf(this);
                    this.uploader._directives[this.prop].splice(index, 1);
                    this.unbind();
                    // this.element = null;
                };
                /**
                 * Saves links to functions
                 * @private
                 */
                FileDirective.prototype._saveLinks = function () {
                    for (var key in this.events) {
                        var prop = this.events[key];
                        this[prop] = this[prop].bind(this);
                    }
                };

                // ---------------------------

                FileUploader.inherit(FileSelect, FileDirective);

                /**
                 * Creates instance of {FileSelect} object
                 * @param {Object} options
                 * @constructor
                 */
                function FileSelect(options) {
                    FileSelect.super_.apply(this, arguments);

                    if (!this.uploader.isHTML5) {
                        this.element.removeAttr('multiple');
                    }
                    this.element.prop('value', null); // FF fix
                }
                /**
                 * Map of events
                 * @type {Object}
                 */
                FileSelect.prototype.events = {
                    $destroy: 'destroy',
                    change: 'onChange'
                };
                /**
                 * Name of property inside uploader._directive object
                 * @type {String}
                 */
                FileSelect.prototype.prop = 'select';
                /**
                 * Returns options
                 * @return {Object|undefined}
                 */
                FileSelect.prototype.getOptions = function () { };
                /**
                 * Returns filters
                 * @return {Array<Function>|String|undefined}
                 */
                FileSelect.prototype.getFilters = function () { };
                /**
                 * If returns "true" then HTMLInputElement will be cleared
                 * @returns {Boolean}
                 */
                FileSelect.prototype.isEmptyAfterSelection = function () {
                    return !!this.element.attr('multiple');
                };
                /**
                 * Event handler
                 */
                FileSelect.prototype.onChange = function () {
                    var files = this.uploader.isHTML5 ? this.element[0].files : this.element[0];
                    var options = this.getOptions();
                    var filters = this.getFilters();

                    if (!this.uploader.isHTML5) this.destroy();
                    this.uploader.addToQueue(files, options, filters);
                    if (this.isEmptyAfterSelection()) this.element.prop('value', null);
                };

                // ---------------------------

                FileUploader.inherit(FileDrop, FileDirective);

                /**
                 * Creates instance of {FileDrop} object
                 * @param {Object} options
                 * @constructor
                 */
                function FileDrop(options) {
                    FileDrop.super_.apply(this, arguments);
                }
                /**
                 * Map of events
                 * @type {Object}
                 */
                FileDrop.prototype.events = {
                    $destroy: 'destroy',
                    drop: 'onDrop',
                    dragover: 'onDragOver',
                    dragleave: 'onDragLeave'
                };
                /**
                 * Name of property inside uploader._directive object
                 * @type {String}
                 */
                FileDrop.prototype.prop = 'drop';
                /**
                 * Returns options
                 * @return {Object|undefined}
                 */
                FileDrop.prototype.getOptions = function () { };
                /**
                 * Returns filters
                 * @return {Array<Function>|String|undefined}
                 */
                FileDrop.prototype.getFilters = function () { };
                /**
                 * Event handler
                 */
                FileDrop.prototype.onDrop = function (event) {
                    var transfer = this._getTransfer(event);
                    if (!transfer) return;
                    var options = this.getOptions();
                    var filters = this.getFilters();
                    this._preventAndStop(event);
                    angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
                    this.uploader.addToQueue(transfer.files, options, filters);
                };
                /**
                 * Event handler
                 */
                FileDrop.prototype.onDragOver = function (event) {
                    var transfer = this._getTransfer(event);
                    if (!this._haveFiles(transfer.types)) return;
                    transfer.dropEffect = 'copy';
                    this._preventAndStop(event);
                    angular.forEach(this.uploader._directives.over, this._addOverClass, this);
                };
                /**
                 * Event handler
                 */
                FileDrop.prototype.onDragLeave = function (event) {
                    if (event.currentTarget !== this.element[0]) return;
                    this._preventAndStop(event);
                    angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
                };
                /**
                 * Helper
                 */
                FileDrop.prototype._getTransfer = function (event) {
                    return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; // jQuery fix;
                };
                /**
                 * Helper
                 */
                FileDrop.prototype._preventAndStop = function (event) {
                    event.preventDefault();
                    event.stopPropagation();
                };
                /**
                 * Returns "true" if types contains files
                 * @param {Object} types
                 */
                FileDrop.prototype._haveFiles = function (types) {
                    if (!types) return false;
                    if (types.indexOf) {
                        return types.indexOf('Files') !== -1;
                    } else if (types.contains) {
                        return types.contains('Files');
                    } else {
                        return false;
                    }
                };
                /**
                 * Callback
                 */
                FileDrop.prototype._addOverClass = function (item) {
                    item.addOverClass();
                };
                /**
                 * Callback
                 */
                FileDrop.prototype._removeOverClass = function (item) {
                    item.removeOverClass();
                };

                // ---------------------------

                FileUploader.inherit(FileOver, FileDirective);

                /**
                 * Creates instance of {FileDrop} object
                 * @param {Object} options
                 * @constructor
                 */
                function FileOver(options) {
                    FileOver.super_.apply(this, arguments);
                }
                /**
                 * Map of events
                 * @type {Object}
                 */
                FileOver.prototype.events = {
                    $destroy: 'destroy'
                };
                /**
                 * Name of property inside uploader._directive object
                 * @type {String}
                 */
                FileOver.prototype.prop = 'over';
                /**
                 * Over class
                 * @type {string}
                 */
                FileOver.prototype.overClass = 'nv-file-over';
                /**
                 * Adds over class
                 */
                FileOver.prototype.addOverClass = function () {
                    this.element.addClass(this.getOverClass());
                };
                /**
                 * Removes over class
                 */
                FileOver.prototype.removeOverClass = function () {
                    this.element.removeClass(this.getOverClass());
                };
                /**
                 * Returns over class
                 * @returns {String}
                 */
                FileOver.prototype.getOverClass = function () {
                    return this.overClass;
                };

                return FileUploader;
            }])


        .directive('nvFileSelect', ['$parse', 'FileUploader', function ($parse, FileUploader) {
            return {
                link: function (scope, element, attributes) {
                    var uploader = scope.$eval(attributes.uploader);

                    if (!(uploader instanceof FileUploader)) {
                        throw new TypeError('"Uploader" must be an instance of FileUploader');
                    }

                    var object = new FileUploader.FileSelect({
                        uploader: uploader,
                        element: element
                    });

                    object.getOptions = $parse(attributes.options).bind(object, scope);
                    object.getFilters = function () { return attributes.filters; };
                }
            };
        }])


        .directive('nvFileDrop', ['$parse', 'FileUploader', function ($parse, FileUploader) {
            return {
                link: function (scope, element, attributes) {
                    var uploader = scope.$eval(attributes.uploader);

                    if (!(uploader instanceof FileUploader)) {
                        throw new TypeError('"Uploader" must be an instance of FileUploader');
                    }

                    if (!uploader.isHTML5) return;

                    var object = new FileUploader.FileDrop({
                        uploader: uploader,
                        element: element
                    });

                    object.getOptions = $parse(attributes.options).bind(object, scope);
                    object.getFilters = function () { return attributes.filters; };
                }
            };
        }])


        .directive('nvFileOver', ['FileUploader', function (FileUploader) {
            return {
                link: function (scope, element, attributes) {
                    var uploader = scope.$eval(attributes.uploader);

                    if (!(uploader instanceof FileUploader)) {
                        throw new TypeError('"Uploader" must be an instance of FileUploader');
                    }

                    var object = new FileUploader.FileOver({
                        uploader: uploader,
                        element: element
                    });

                    object.getOverClass = function () {
                        return attributes.overClass || this.overClass;
                    };
                }
            };
        }])

    return module;
}));