;(function($) {

    var methods = {

        init : function(options) {
            
            return this.each(function() {

                var $this = $(this);
                    $this.data('uploadifive', {
                        inputs     : {},
                        inputCount : 0,
                        fileID     : 0,
                        queue      : {
                                         count      : 0,
                                         selected   : 0,
                                         replaced   : 0,
                                         errors     : 0,
                                         queued     : 0,
                                         cancelled  : 0
                                     },
                        uploads    : {
                                         current    : 0,
                                         attempts   : 0,
                                         successful : 0,
                                         errors     : 0,
                                         count      : 0
                                     }
                    });
                var $data = $this.data('uploadifive');

                var settings = $data.settings = $.extend({
                    'auto'            : true,
                    'buttonClass'     : false,
                    'buttonText'      : 'Select Files',
                    'checkScript'     : false,
                    'dnd'             : true,
                    'dropTarget'      : false,
                    'fileObjName'     : 'Filedata',
                    'fileSizeLimit'   : 0,
                    'fileType'        : false,
                    'formData'        : {},
                    'height'          : 30,
                    'itemTemplate'    : false,
                    'method'          : 'post',
                    'multi'           : true,
                    'overrideEvents'  : [],
                    'queueID'         : false,
                    'queueSizeLimit'  : 0,
                    'removeCompleted' : false,
                    'simUploadLimit'  : 0,
                    'truncateLength'  : 0,
                    'uploadLimit'     : 0,
                    'uploadScript'    : 'uploadifive.php',
                    'width'           : 100

                }, options);

                if (isNaN(settings.fileSizeLimit)) {
                    var fileSizeLimitBytes = parseInt(settings.fileSizeLimit) * 1.024
                    if (settings.fileSizeLimit.indexOf('KB') > -1) {
                        settings.fileSizeLimit = fileSizeLimitBytes * 1000;
                    } else if (settings.fileSizeLimit.indexOf('MB') > -1) {
                        settings.fileSizeLimit = fileSizeLimitBytes * 1000000;
                    } else if (settings.fileSizeLimit.indexOf('GB') > -1) {
                        settings.fileSizeLimit = fileSizeLimitBytes * 1000000000;
                    }
                } else {
                    settings.fileSizeLimit = settings.fileSizeLimit * 1024;
                }

                $data.inputTemplate = $('<input type="file">')
                .css({
                    'font-size' : settings.height + 'px',
                    'opacity'   : 0,
                    'position'  : 'absolute',
                    'right'     : '-3px',
                    'top'       : '-3px',
                    'z-index'   : 999 
                });

                $data.createInput = function() {

                    var input     = $data.inputTemplate.clone();
                    var inputName = input.name = 'input' + $data.inputCount++;
                    if (settings.multi) {
                        input.attr('multiple', true);
                    }
                    if (settings.fileType) {
                        input.attr('accept', settings.fileType);
                    }
                    input.bind('change', function() {
                        $data.queue.selected = 0;
                        $data.queue.replaced = 0;
                        $data.queue.errors   = 0;
                        $data.queue.queued   = 0;
                        var limit = this.files.length;
                        $data.queue.selected = limit;
                        if (($data.queue.count + limit) > settings.queueSizeLimit && settings.queueSizeLimit !== 0) {
                            if ($.inArray('onError', settings.overrideEvents) < 0) {
                                alert('The maximum number of queue items has been reached (' + settings.queueSizeLimit + ').  Please select fewer files.');
                            }
                            if (typeof settings.onError === 'function') {
                                settings.onError.call($this, 'QUEUE_LIMIT_EXCEEDED');
                            }
                        } else {
                            for (var n = 0; n < limit; n++) {
                                file = this.files[n];
                                $data.addQueueItem(file);
                            }
                            $data.inputs[inputName] = this;
                            $data.createInput();
                        }
                        if (settings.auto) {
                            methods.upload.call($this);
                        }
                        if (typeof settings.onSelect === 'function') {
                            settings.onSelect.call($this, $data.queue);
                        }
                    });
                    if ($data.currentInput) {
                        $data.currentInput.hide();
                    }
                    $data.button.append(input);
                    $data.currentInput = input;
                }

                $data.destroyInput = function(key) {
                    $($data.inputs[key]).remove();
                    delete $data.inputs[key];
                    $data.inputCount--;
                }

                $data.drop = function(e) {
                    $data.queue.selected = 0;
                    $data.queue.replaced = 0;
                    $data.queue.errors   = 0;
                    $data.queue.queued   = 0;

                    var fileData = e.dataTransfer;

                    var inputName = fileData.name = 'input' + $data.inputCount++;
                    var limit = fileData.files.length;
                    $data.queue.selected = limit;
                    if (($data.queue.count + limit) > settings.queueSizeLimit && settings.queueSizeLimit !== 0) {
                        if ($.inArray('onError', settings.overrideEvents) < 0) {
                            alert('The maximum number of queue items has been reached (' + settings.queueSizeLimit + ').  Please select fewer files.');
                        }
                        if (typeof settings.onError === 'function') {
                            settings.onError.call($this, 'QUEUE_LIMIT_EXCEEDED');
                        }
                    } else {
                        for (var n = 0; n < limit; n++) {
                            file = fileData.files[n];
                            $data.addQueueItem(file);
                        }
                        $data.inputs[inputName] = fileData;
                    }

                    if (settings.auto) {
                        methods.upload.call($this);
                    }

                    if (typeof settings.onDrop === 'function') {
                        settings.onDrop.call($this, fileData.files, fileData.files.length);
                    }

                    e.preventDefault();
                    e.stopPropagation();
                }

                $data.fileExistsInQueue = function(file) {
                    for (var key in $data.inputs) {
                        input = $data.inputs[key];
                        limit = input.files.length;
                        for (var n = 0; n < limit; n++) {
                            existingFile = input.files[n];
                            if (existingFile.name == file.name && !existingFile.complete) {
                                return true;
                            }
                        }
                    }
                    return false;
                }

                $data.removeExistingFile = function(file) {
                    for (var key in $data.inputs) {
                        input = $data.inputs[key];
                        limit = input.files.length;
                        for (var n = 0; n < limit; n++) {
                            existingFile = input.files[n];
                            if (existingFile.name == file.name && !existingFile.complete) {
                                $data.queue.replaced++;
                                methods.cancel.call($this, existingFile, true);
                            }
                        }
                    }
                }

                if (settings.itemTemplate == false) {
                    $data.queueItem = $('<div class="uploadifive-queue-item">\
                        <a class="close" href="#">X</a>\
                        <div><span class="filename"></span><span class="fileinfo"></span></div>\
                        <div class="progress">\
                            <div class="progress-bar"></div>\
                        </div>\
                    </div>');
                } else {
                    $data.queueItem = $(settings.itemTemplate);
                }

                $data.addQueueItem = function(file) {
                    if ($.inArray('onAddQueueItem', settings.overrideEvents) < 0) {
                        $data.removeExistingFile(file);
                        file.queueItem = $data.queueItem.clone();
                        file.queueItem.attr('id', settings.id + '-file-' + $data.fileID++);
                        file.queueItem.find('.close').bind('click', function() {
                           methods.cancel.call($this, file);
                           return false;
                        });
                        var fileName = file.name;
                        if (fileName.length > settings.truncateLength && settings.truncateLength != 0) {
                            fileName = fileName.substring(0, settings.truncateLength) + '...';
                        }
                        file.queueItem.find('.filename').html(fileName);
                        file.queueItem.data('file', file);
                        $data.queueEl.append(file.queueItem);
                    }
                    if (typeof settings.onAddQueueItem === 'function') {
                        settings.onAddQueueItem.call($this, file);
                    }
                    if (file.size > settings.fileSizeLimit && settings.fileSizeLimit != 0) {
                        $data.error('FILE_SIZE_LIMIT_EXCEEDED', file);
                    } else {
                        $data.queue.queued++;
                        $data.queue.count++;
                    }
                }

                $data.removeQueueItem = function(file, instant, delay) {
                    if (!delay) delay = 0;
                    var fadeTime = instant ? 0 : 500;
                    if (file.queueItem) {
                        if (file.queueItem.find('.fileinfo').html() != ' - Completed') {
                            file.queueItem.find('.fileinfo').html(' - Cancelled');
                        }
                        file.queueItem.find('.progress-bar').width(0);
                        file.queueItem.delay(delay).fadeOut(fadeTime, function() {
                           $(this).remove();
                        });
                        delete file.queueItem;
                        $data.queue.count--;
                    }
                }

                $data.filesToUpload = function() {
                    var filesToUpload = 0;
                    for (var key in $data.inputs) {
                        input = $data.inputs[key];
                        limit = input.files.length;
                        for (var n = 0; n < limit; n++) {
                            file = input.files[n];
                            if (!file.skip && !file.complete) {
                                filesToUpload++;
                            }
                        }
                    }
                    return filesToUpload;
                }

                $data.checkExists = function(file) {
                    if ($.inArray('onCheck', settings.overrideEvents) < 0) {
                        $.ajaxSetup({
                            'async' : false
                        });
                        var checkData = $.extend(settings.formData, {filename: file.name});
                        $.post(settings.checkScript, checkData, function(fileExists) {
                            file.exists = parseInt(fileExists);
                        });
                        if (file.exists) {
                            if (!confirm('A file named ' + file.name + ' already exists in the upload folder.\nWould you like to replace it?')) {
                                methods.cancel.call($this, file);
                                return true;
                            }
                        }
                    }
                    if (typeof settings.onCheck === 'function') {
                        settings.onCheck.call($this, file, file.exists);
                    }
                    return false;
                }

                $data.uploadFile = function(file, uploadAll) {
                    if (!file.skip && !file.complete && !file.uploading) {
                        file.uploading = true;
                        $data.uploads.current++;
                        $data.uploads.attempted++;

                        xhr = file.xhr = new XMLHttpRequest();

                        if (typeof FormData === 'function' || typeof FormData === 'object') {

                            var formData = new FormData();

                            formData.append(settings.fileObjName, file);

                            for (i in settings.formData) {
                                formData.append(i, settings.formData[i]);
                            }

                            xhr.open(settings.method, settings.uploadScript, true);

                            xhr.upload.addEventListener('progress', function(e) {
                                if (e.lengthComputable) {
                                    $data.progress(e, file);
                                }
                            }, false);

                            xhr.addEventListener('load', function(e) {
                                if (this.readyState == 4) {
                                    file.uploading = false;
                                    if (this.status == 200) {
                                        if (file.xhr.responseText !== 'Invalid file type.') {
                                            $data.uploadComplete(e, file, uploadAll);
                                        } else {
                                            $data.error(file.xhr.responseText, file, uploadAll);
                                        }
                                    } else if (this.status == 404) {
                                        $data.error('404_FILE_NOT_FOUND', file, uploadAll);
                                    } else if (this.status == 403) {
                                        $data.error('403_FORBIDDEN', file, uploadAll);
                                    } else {
                                        $data.error('Unknown Error', file, uploadAll);
                                    }
                                }
                            });

                            xhr.send(formData);

                        } else {

                            var reader = new FileReader();
                            reader.onload = function(e) {

                                var boundary = '-------------------------' + (new Date).getTime(),
                                    dashes   = '--',
                                    eol      = '\r\n',
                                    binFile  = '';

                                binFile += dashes + boundary + eol;
                                binFile += 'Content-Disposition: form-data; name="' + settings.fileObjName + '"';
                                if (file.name) {
                                    binFile += '; filename="' + file.name + '"';
                                }
                                binFile += eol;
                                binFile += 'Content-Type: application/octet-stream' + eol + eol;
                                binFile += e.target.result + eol;

                                for (key in settings.formData) {
                                    binFile += dashes + boundary + eol;
                                    binFile += 'Content-Disposition: form-data; name="' + key + '"' + eol + eol;
                                    binFile += settings.formData[key] + eol;
                                }

                                binFile += dashes + boundary + dashes + eol;

                                xhr.upload.addEventListener('progress', function(e) {
                                    $data.progress(e, file);
                                }, false);

                                xhr.addEventListener('load', function(e) {
                                    file.uploading = false;
                                    var status = this.status;
                                    if (status == 404) {
                                        $data.error('404_FILE_NOT_FOUND', file, uploadAll);
                                    } else {
                                        if (file.xhr.responseText != 'Invalid file type.') {    
                                            $data.uploadComplete(e, file, uploadAll);
                                        } else {
                                            $data.error(file.xhr.responseText, file, uploadAll);
                                        } 
                                    }
                                }, false);

                                var url = settings.uploadScript;
                                if (settings.method == 'get') {
                                    var params = $(settings.formData).param();
                                    url += params;
                                }
                                xhr.open(settings.method, settings.uploadScript, true);
                                xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);

                                if (typeof settings.onUploadFile === 'function') {
                                    settings.onUploadFile.call($this, file);
                                }

                                xhr.sendAsBinary(binFile);
                            }
                            reader.readAsBinaryString(file);

                        }
                    }
                }

                $data.progress = function(e, file) {
                    if ($.inArray('onProgress', settings.overrideEvents) < 0) {
                        if (e.lengthComputable) {
                            var percent = Math.round((e.loaded / e.total) * 100);
                        }
                        file.queueItem.find('.fileinfo').html(' - ' + percent + '%');
                        file.queueItem.find('.progress-bar').css('width', percent + '%');
                    }
                    if (typeof settings.onProgress === 'function') {
                        settings.onProgress.call($this, file, e);
                    }
                }

                $data.error = function(errorType, file, uploadAll) {
                    if ($.inArray('onError', settings.overrideEvents) < 0) {
                        switch(errorType) {
                            case '404_FILE_NOT_FOUND':
                                errorMsg = '404 Error';
                                break;
                            case '403_FORBIDDEN':
                                errorMsg = '403 Forbidden';
                                break;
                            case 'FORBIDDEN_FILE_TYPE':
                                errorMsg = 'Forbidden File Type';
                                break;
                            case 'FILE_SIZE_LIMIT_EXCEEDED':
                                errorMsg = 'File Too Large';
                                break;
                            default:
                                errorMsg = 'Unknown Error';
                                break;
                        }

                        file.queueItem.addClass('error')
                        .find('.fileinfo').html(' - ' + errorMsg);
                        file.queueItem.find('.progress').remove();
                    }
                    if (typeof settings.onError === 'function') {
                        settings.onError.call($this, errorType, file);
                    }
                    file.skip = true;
                    if (errorType == '404_FILE_NOT_FOUND') {
                        $data.uploads.errors++;
                    } else {
                        $data.queue.errors++;
                    }
                    if (uploadAll) {
                        methods.upload.call($this, null, true);
                    }
                }

                $data.uploadComplete = function(e, file, uploadAll) {
                    if ($.inArray('onUploadComplete', settings.overrideEvents) < 0) {
                        file.queueItem.find('.progress-bar').css('width', '100%');
                        file.queueItem.find('.fileinfo').html(' - Completed');
                        file.queueItem.find('.progress').slideUp(250);
                        file.queueItem.addClass('complete');
                    }
                    if (typeof settings.onUploadComplete === 'function') {
                        settings.onUploadComplete.call($this, file, file.xhr.responseText);
                    }
                    if (settings.removeCompleted) {
                        setTimeout(function() { methods.cancel.call($this, file); }, 3000);
                    }
                    file.complete = true;
                    $data.uploads.successful++;
                    $data.uploads.count++;
                    $data.uploads.current--;
                    delete file.xhr;
                    if (uploadAll) {
                        methods.upload.call($this, null, true);
                    }
                }

                $data.queueComplete = function() {
                    if (typeof settings.onQueueComplete === 'function') {
                        settings.onQueueComplete.call($this, $data.uploads);
                    }
                }


                if (window.File && window.FileList && window.Blob && (window.FileReader || window.FormData)) {
                    settings.id = 'uploadifive-' + $this.attr('id');

                    $data.button = $('<div id="' + settings.id + '" class="uploadifive-button">' + settings.buttonText + '</div>');
                    if (settings.buttonClass) $data.button.addClass(settings.buttonClass);

                    $data.button.css({
                        'height'      : settings.height,
                        'line-height' : settings.height + 'px', 
                        'overflow'    : 'hidden',
                        'position'    : 'relative',
                        'text-align'  : 'center', 
                        'width'       : settings.width
                    });

                    $this.before($data.button)
                    .appendTo($data.button)
                    .hide();

                    $data.createInput.call($this);

                    if (!settings.queueID) {
                        settings.queueID = settings.id + '-queue';
                        $data.queueEl = $('<div id="' + settings.queueID + '" class="uploadifive-queue" />');
                        $data.button.after($data.queueEl);
                    } else {
                        $data.queueEl = $('#' + settings.queueID);
                    }

                    if (settings.dnd) {
                        var $dropTarget = settings.dropTarget ? $(settings.dropTarget) : $data.queueEl.get(0);
                        $dropTarget.addEventListener('dragleave', function(e) {
                            e.preventDefault();
                            e.stopPropagation();
                        }, false);
                        $dropTarget.addEventListener('dragenter', function(e) {
                            e.preventDefault();
                            e.stopPropagation();
                        }, false);
                        $dropTarget.addEventListener('dragover', function(e) {
                            e.preventDefault();
                            e.stopPropagation();
                        }, false);
                        $dropTarget.addEventListener('drop', $data.drop, false);
                    }

                    if (!XMLHttpRequest.prototype.sendAsBinary) {
                        XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
                            function byteValue(x) {
                                return x.charCodeAt(0) & 0xff;
                            }
                            var ords = Array.prototype.map.call(datastr, byteValue);
                            var ui8a = new Uint8Array(ords);
                            this.send(ui8a.buffer);
                        }
                    }

                    if (typeof settings.onInit === 'function') {
                        settings.onInit.call($this);
                    }

                } else {

                    if (typeof settings.onFallback === 'function') {
                        settings.onFallback.call($this);
                    }
                    return false;

                }

            });

        },


        debug : function() {

            return this.each(function() {

                console.log($(this).data('uploadifive'));

            });

        },

        clearQueue : function() {

            this.each(function() {

                var $this    = $(this),
                    $data    = $this.data('uploadifive'),
                    settings = $data.settings;

                for (var key in $data.inputs) {
                    input = $data.inputs[key];
                    limit = input.files.length;
                    for (i = 0; i < limit; i++) {
                        file = input.files[i];
                        methods.cancel.call($this, file);
                    }
                }
                if (typeof settings.onClearQueue === 'function') {
                    settings.onClearQueue.call($this, $('#' + $data.settings.queueID));
                }

            });

        },

        cancel : function(file, fast) {

            this.each(function() {

                var $this    = $(this),
                    $data    = $this.data('uploadifive'),
                    settings = $data.settings;

                if (typeof file === 'string') {
                    if (!isNaN(file)) {
                        fileID = 'uploadifive-' + $(this).attr('id') + '-file-' + file;
                    }
                    file = $('#' + fileID).data('file');
                }

                file.skip = true;
                $data.filesCancelled++;
                if (file.uploading) {
                    $data.uploads.current--;
                    file.uploading = false;
                    file.xhr.abort();
                    delete file.xhr;
                    methods.upload.call($this);
                }
                if ($.inArray('onCancel', settings.overrideEvents) < 0) {
                    $data.removeQueueItem(file, fast);
                }

                if (typeof settings.onCancel === 'function') {
                    settings.onCancel.call($this, file);
                }
                
            });
            
        },

        upload : function(file, keepVars) {

            this.each(function() {

                var $this    = $(this),
                    $data    = $this.data('uploadifive'),
                    settings = $data.settings;

                if (file) {

                    $data.uploadFile.call($this, file);

                } else {

                    if (($data.uploads.count + $data.uploads.current) < settings.uploadLimit || settings.uploadLimit == 0) {
                        if (!keepVars) {
                            $data.uploads.attempted   = 0;
                            $data.uploads.successsful = 0;
                            $data.uploads.errors      = 0;
                            var filesToUpload = $data.filesToUpload();
                            if (typeof settings.onUpload === 'function') {
                                settings.onUpload.call($this, filesToUpload);
                            }
                        }

                        $('#' + settings.queueID).find('.uploadifive-queue-item').not('.error, .complete').each(function() {
                            _file = $(this).data('file');
                            if (($data.uploads.current >= settings.simUploadLimit && settings.simUploadLimit !== 0) || ($data.uploads.current >= settings.uploadLimit && settings.uploadLimit !== 0) || ($data.uploads.count >= settings.uploadLimit && settings.uploadLimit !== 0)) {
                                return false;
                            }
                            if (settings.checkScript) {
                                _file.checking = true;
                                skipFile = $data.checkExists(_file);
                                _file.checking = false;
                                if (!skipFile) {
                                    $data.uploadFile(_file, true);
                                }
                            } else {
                                $data.uploadFile(_file, true);
                            }
                        });
                        if ($('#' + settings.queueID).find('.uploadifive-queue-item').not('.error, .complete').size() == 0) {
                            $data.queueComplete();
                        }
                    } else {
                        if ($data.uploads.current == 0) {
                            if ($.inArray('onError', settings.overrideEvents) < 0) {
                                if ($data.filesToUpload() > 0 && settings.uploadLimit != 0) {
                                    alert('The maximum upload limit has been reached.');
                                }
                            }
                            if (typeof settings.onError === 'function') {
                                settings.onError.call($this, 'UPLOAD_LIMIT_EXCEEDED', $data.filesToUpload());
                            }
                        }
                    }

                }

            });

        },

        destroy : function() {

            this.each(function() {

                var $this    = $(this),
                    $data    = $this.data('uploadifive'),
                    settings = $data.settings;
            
                methods.clearQueue.call($this);
                if (!settings.queueID) $('#' + settings.queueID).remove();
                $this.siblings('input').remove();
                $this.show()
                .insertBefore($data.button);
                $data.button.remove();
                if (typeof settings.onDestroy === 'function') {
                    settings.onDestroy.call($this);
                }

            });

        }

    }

    $.fn.uploadifive = function(method) {

        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('The method ' + method + ' does not exist in $.uploadify');
        }

    }

})(jQuery);