/**
 * jQuery plugin for FileAPI v2+
 * @auhtor    RubaXa	<trash@rubaxa.org>
 */

if (typeof FileApiMobile === "undefined") {
    FileApiMobile = false;
}

/*jslint evil: true */
/*global jQuery, FileAPI*/
(function ($, api) {
    "use strict";

    var
        noop = $.noop
        , oldJQ = !$.fn.prop
        , propFn = oldJQ ? 'attr' : 'prop'
        , removePropFn = oldJQ ? 'removeAttr' : 'removeProp'

        , _dataAttr = 'data-fileapi'
        , _dataFileId = 'data-fileapi-id'

        , _slice = [].slice
        , _each = api.each
        , _extend = api.extend

        , _bind = function (ctx, fn) {
            var args = _slice.call(arguments, 2);
            return fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function () {
                return fn.apply(ctx, args.concat(_slice.call(arguments)));
            };
        }

        , _optDataAttr = function (name) {
            return '[' + _dataAttr + '="' + name + '"]';
        }

        , _isID = function (selector) {
            return selector.indexOf('#') === 0;
        }
    ;


    var Plugin = function (el, options) {
        this.$el = el = $(el).on('change.fileapi', 'input[type="file"]', _bind(this, this._onSelect));
        this.el = el[0];

        this._options = {}; // previous options
        this.options = { // current options
            url: 0,
            data: {}, // additional POST-data
            accept: 0, // accept mime types, "*" — unlimited
            multiple: false, // single or multiple mode upload mode
            paramName: 0, // POST-parameter name
            dataType: 'json',
            duplicate: false, // ignore duplicate

            uploadRetry: 0,
            networkDownRetryTimeout: 5000,

            chunkSize: 0, // or chunk size in bytes, eg: .5 * FileAPI.MB
            chunkUploadRetry: 3, // number of retries during upload chunks (html5)
            chunkNetworkDownRetryTimeout: 2000,

            maxSize: 0, // max file size, 0 — unlimited
            maxFiles: 0, // 0 unlimited
            maxNormalTotalSize: 0,       //추가한 변수 max total file size, 0 — unlimited
            maxBigfileTotalSize: 0,      //추가한 변수 max total file size, 0 — unlimited
            imageSize: 0, // { minWidth: 320, minHeight: 240, maxWidth: 3840, maxHeight: 2160 }

            sortFn: 0,
            filterFn: 0,
            autoUpload: true,

            clearOnSelect: void 0,
            clearOnComplete: void 0,

            lang: {
                B: 'bytes'
                , KB: 'KB'
                , MB: 'MB'
                , GB: 'GB'
                , TB: 'TB'
            },
            sizeFormat: '0.0',

            imageOriginal: true,
            imageTransform: 0,
            imageAutoOrientation: !!FileAPI.support.exif,

            elements: {
                ctrl: {
                    upload: _optDataAttr('ctrl.upload'),
                    reset: _optDataAttr('ctrl.reset'),
                    sizedis: _optDataAttr('ctrl.sizedis'),
                    abort: _optDataAttr('ctrl.abort')
                },
                empty: {
                    show: _optDataAttr('empty.show'),
                    hide: _optDataAttr('empty.hide')
                },
                emptyQueue: {
                    show: _optDataAttr('emptyQueue.show'),
                    hide: _optDataAttr('emptyQueue.hide')
                },
                active: {
                    show: _optDataAttr('active.show'),
                    hide: _optDataAttr('active.hide')
                },
                size: _optDataAttr('size'),
                name: _optDataAttr('name'),
                progress: _optDataAttr('progress'),
                list: _optDataAttr('list'),
                file: {
                    tpl: _optDataAttr('file.tpl'),
                    progress: _optDataAttr('file.progress'),
                    active: {
                        show: _optDataAttr('file.active.show'),
                        hide: _optDataAttr('file.active.hide')
                    },
                    preview: {
                        el: 0,
                        get: 0,
                        width: 0,
                        height: 0,
                        processing: 0
                    },
                    abort: _optDataAttr('file.abort'),
                    remove: _optDataAttr('file.remove'),
                    rotate: _optDataAttr('file.rotate')
                },
                dnd: {
                    el: _optDataAttr('dnd'),
                    hover: 'dnd_hover',
                    fallback: _optDataAttr('dnd.fallback')
                }
            },

            onDrop: noop,
            onDropHover: noop,

            onSelect: noop,

            onBeforeUpload: noop,
            onUpload: noop,
            onProgress: noop,
            onComplete: noop,

            onFilePrepare: noop,
            onFileUpload: noop,
            onFileProgress: noop,
            onFileComplete: noop,

            onFileRemove: null,
            onFileRemoveCompleted: null
        };

        $.extend(true, this.options, options); // deep extend
        options = this.options;

        this.option('elements.file.preview.rotate', options.imageAutoOrientation);

        if (!options.url) {
            var url = this.$el.attr('action') || this.$el.find('form').attr('action');
            if (url) {
                options.url = url;
            } else {
                this._throw('url — is not defined');
            }
        }


        this.$files = this.$elem('list');

        this.$fileTpl = this.$elem('file.tpl');
        this.itemTplFn = $.fn.fileapi.tpl($('<div/>').append(this.$elem('file.tpl')).html());


        _each(options, function (value, option) {
            this._setOption(option, value);
        }, this);


        this.$el
            .on('reset.fileapi', _bind(this, this._onReset))
            .on('submit.fileapi', _bind(this, this._onSubmit))
            .on('upload.fileapi progress.fileapi complete.fileapi', _bind(this, this._onUploadEvent))
            .on('fileupload.fileapi fileprogress.fileapi filecomplete.fileapi', _bind(this, this._onFileUploadEvent))
            .on('click', '[' + _dataAttr + ']', _bind(this, this._onActionClick))
        ;


        // Controls
        var ctrl = options.elements.ctrl;
        if (ctrl) {
            this._listen('click', ctrl.reset, _bind(this, this._onReset));

            // 모바일 멀티 업로드에서 전체 삭제 버튼이 활성화되지 않으므로 직접 활성화
            $('.js-reset-manual').on('click', _bind(this, this._onReset));

            this._listen('click', ctrl.upload, _bind(this, this._onSubmit));
            this._listen('click', ctrl.abort, _bind(this, this._onAbort));
            this._listen('click', ctrl.sizedis, _bind(this, this._filesize_display));
        }

        // Drag'n'Drop
        var dnd = FileAPI.support.dnd;
        // [2016-05] Drag'n'Drop 미지원시 레이어 접힘차단
        //this.$elem('dnd.el', true).toggle(dnd);
        //this.$elem('dnd.fallback').toggle(!dnd);

        if (dnd) {
            this.$elem('dnd.el', true).dnd(_bind(this, this._onDropHover), _bind(this, this._onDrop));
        }


        this.$progress = this.$elem('progress');

        if (options.clearOnSelect === void 0) {
            options.clearOnSelect = !options.multiple;
        }

        this.clear();

        if ($.isArray(options.files)) {
            this.files = $.map(options.files, function (file) {
                if ($.type(file) === 'string') {
                    file = {src: file, size: 0};
                }

                file.name = file.name || file.src.split('/').pop();
                file.type = file.type || /\.(jpe?g|png|bmp|gif|tiff?)/i.test(file.src) && 'image/' + file.src.split('.').pop(); // @todo: use FileAPI.getMimeType (v2.1+)
                file.complete = true;

                return file;
            });

            this._redraw();
        }
    };


    Plugin.prototype = {
        constructor: Plugin,

        _throw: function (msg) {
            throw "jquery.fileapi: " + msg;
        },

        // 파일선택 또는 Drag&drop으로 추가된 파일들에 대한 처리
        // 개별 사이즈용량 이나 제한 파일수에 대한 제한은 지원되지만 전체 용량에 체코로직 위치로는 어려울듯
        _getFiles: function (evt, fn) {
            var
                opts = this.options
                , maxSize = opts.maxSize
                , maxBigSize = opts.maxBigSize
                , maxFiles = opts.maxFiles
                , filterFn = opts.filterFn
                , countFiles = this.files.length
                , files = api.getFiles(evt)
                , data = {
                    all: files
                    , files: []
                    , other: []
                    , duplicate: opts.duplicate ? [] : this._extractDuplicateFiles(files)
                }
                , imageSize = opts.imageSize
                , _this = this
            ;

            // 이미지 관련 옵션지정시 작동하는듯 일반파일은 else 부분
            if (imageSize || filterFn) {
                api.filterFiles(files, function (file, info) {
                    if (info && imageSize) {
                        _checkFileByCriteria(file, 'minWidth', imageSize, info);
                        _checkFileByCriteria(file, 'minHeight', imageSize, info);
                        _checkFileByCriteria(file, 'maxWidth', imageSize, info);
                        _checkFileByCriteria(file, 'maxHeight', imageSize, info);
                    }

                    _checkFileByCriteria(file, 'maxSize', maxSize, file.size);

                    return !file.errors && (!filterFn || filterFn(file, info));
                }, function (success, rejected) {
                    _extractFilesOverLimit(maxFiles, countFiles, success, rejected);

                    data.other = rejected;
                    data.files = success;

                    fn.call(_this, data);
                });
            } else {
                /* 파일별로 최댇 용량 상지를 초과여부 체킹*/
                var total_bigfile = 0, total_normal = 0, maxSize_msg = "";
                _each(this.files, function (file, i) {
                    if (file.uptype === "bigfile") {
                        total_bigfile += file.size;
                    } else {
                        total_normal += file.size;
                    }
                });
                total_normal += Number(document.getElementById('mf_attachs_size').value);

                _each(files, function (file, i) {
                    if (file.size < (opts.maxNormalTotalSize - total_normal)) {
//alert("normal: "+file.size+"+"+(opts.maxNormalTotalSize-total_normal));
                        _checkFileByCriteria(file, 'maxTotalSize', opts.maxNormalTotalSize, Number(total_normal + file.size));
                        file.uptype = "normal";
                        total_normal += file.size;
                    } else if (file.size < (opts.maxBigfileTotalSize - total_bigfile)) {
                        if (maxBigSize !== '0' && file.size > maxBigSize) {
                            _checkFileByCriteria(file, 'maxTotalSize', 1, file.size);
                        } else {
//alert("bigfile: "+file.size+"+"+(opts.maxBigfileTotalSize-total_bigfile));
                            _checkFileByCriteria(file, 'maxTotalSize', opts.maxBigfileTotalSize, Number(total_bigfile + file.size));
                            file.uptype = "bigfile";
                            total_bigfile += file.size;
                        }
                    } else {
//alert("else:");
                        _checkFileByCriteria(file, 'maxTotalSize', 1, file.size);
                    }
                    //document.all.txtr.value = JSON.stringify(this.files,null,2);

                    //_checkFileByCriteria(file, 'maxSize', maxSize, file.size);
                    // file.errors가 undefined시 파일 정상 추가되고 error사항이 있을시 object로 리턴됨
                    // {"errors":{"maxSize":0000000}}
                    //data[file.errors ? 'other' : 'files'].push(file);

                    data[file.errors ? 'other' : 'files'].push(file);
                    if (file.errors) {
                        //alert($.stringify(file.errors,null,2));
                        //alert([file.errors][0].length);
                        maxSize_msg += "첨부용량 제한) " + file.name + "\r";
                    }
                });


                // 첨부제한 용량을 초과 파일 내용 출력
                if (maxSize_msg !== "") {
                    alert("[업로드 제한된 파일]\r\r" + maxSize_msg);
                }

                /* 업로드 최대 파일수 제한 체킹 */
                // 정상적인 파일일경우 [{}]을 리턴시킴
                // 제한시킬 파일일경우 []만 리턴시켜 length값이 0
                _extractFilesOverLimit(maxFiles, countFiles, data.files, data.other);
                fn.call(_this, data);
            }
        },

        _extractDuplicateFiles: function (list/**Array*/) {
            var duplicates = [], i = list.length, files = this.files, j;

            while (i--) {
                j = files.length;
                while (j--) {
                    if (this._fileCompare(list[i], files[j])) {
                        duplicates.push(list.splice(i, 1));
                        break;
                    }
                }
            }

            return duplicates;
        },

        _fileCompare: function (A/**File*/, B/**File*/) {
            return (A.size == B.size) && (A.name == B.name);
        },

        _getFormatedSize: function (size) {
            var opts = this.options, postfix = 'B';

            if (size >= api.TB) {
                size /= api[postfix = 'TB'];
            } else if (size >= api.GB) {
                size /= api[postfix = 'GB'];
            } else if (size >= api.MB) {
                size /= api[postfix = 'MB'];
            } else if (size >= api.KB) {
                size /= api[postfix = 'KB'];
            }

            return opts.sizeFormat.replace(/^\d+([^\d]+)(\d*)/, function (_, separator, fix) {
                size = (parseFloat(size) || 0).toFixed(fix.length);
                return (size + '').replace('.', separator) + ' ' + opts.lang[postfix];
            });
        },

        /* 파일 선택 및 drop시 처리*/
        _onSelect: function (evt) {
            if (this.options.clearOnSelect) {
                this.queue = [];
                this.files = [];
            }

            this._getFiles(evt, _bind(this, function (data) {
                if (data.all.length && this.emit('select', data) !== false) {
                    // console.log('_onSelect() data.files', data.files);

                    this.add(data.files);
                }

                // Reset input
                FileAPI.reset(evt.target);
            }));
        },

        _onActionClick: function (evt) {
            var
                el = evt.currentTarget
                , act = $.attr(el, _dataAttr)
                , uid = $(el).closest('[' + _dataFileId + ']', this.$el).attr(_dataFileId)
                , file = this._getFile(uid)
                , prevent = true
            ;

            if (!this.$file(uid).attr('disabled')) {
                if ('file.remove' == act) {
                    if (file && this.emit('fileRemove' + (file.complete ? 'Completed' : ''), file)) {
                        this.remove(uid);
                    }
                } else if (/^file\.rotate/.test(act)) {
                    //this.rotate(uid, (/ccw/.test(act) ? '-=90' : '+=90'));
                } else if ('file.uptype' == act) {
                    // closest
                    // 현재 자신의 요소부터 Document Root까지 위로 올라가며 검색을 한다.
                    //  parents와 차이점은 가장 가까운 부모요소 한개의 결과값을 도출할 수 있다.
                    // L버튼 클릭시 처리문
                    //$(el).closest('['+_dataFileId+']', this.$el).attr('style', 'color:red');

                    var this_uid = api.uid(file);
                    if (file.uptype === "normal") {
                        var total_bigfile = 0;
                        _each(this.files, function (file, i) {
                            // 아래 라인에서 file값이 입력됨 "fileapi1441873172428": "fileapi14418731724283"
                            var uid = api.uid(file);

                            if (file.uptype == "bigfile") {
                                total_bigfile += file.size;
                            }
                        });

                        if ((total_bigfile + file.size) > this.options.maxBigfileTotalSize) {
                            alert("대용량 첨부 가능한 전체크기[" + this._getFormatedSize(this.options.maxBigfileTotalSize) + "]를 초과하였습니다.");
                        } else {
                            file.uptype = "bigfile";
                            //$(el).attr('style', 'color:blue');
                            $(el).parent().find('.b-thumb__uptype').text(((FileApiMobile) ? "대용량" : "대용량첨부"));
                        }
                    } else if (file.uptype === "bigfile") {
                        var total_normal = 0;
                        _each(this.files, function (file, i) {
                            // 아래 라인에서 file값이 입력됨 "fileapi1441873172428": "fileapi14418731724283"
                            var uid = api.uid(file);

                            if (file.uptype == "normal") {
                                total_normal += file.size;
                            }
                        });

                        if ((total_normal + file.size) > this.options.maxNormalTotalSize) {
                            alert("일반 첨부 가능한 전체크기[" + this._getFormatedSize(this.options.maxNormalTotalSize) + "]를 초과 하였습니다.");
                        } else {
                            file.uptype = "normal";
                            //$(el).attr('style', 'color:red');
                            $(el).parent().find('.b-thumb__uptype').text(((FileApiMobile) ? "일반" : "일반첨부"));
                        }
                    }
                    this._filesize_display();
                } else {
                    prevent = false;
                }
            }

            if (prevent) {
                evt.preventDefault();
            }
        },

        _listen: function (name, selector, fn) {
            selector && _each($.trim(selector).split(','), function (selector) {
                selector = $.trim(selector);

                if (_isID(selector)) {
                    $(selector).on(name + '.fileapi', fn);
                } else {
                    this.$el.on(name + '.fileapi', selector, fn);
                }
            }, this);
        },

        _onSubmit: function (evt) {
            evt.preventDefault();
            this.upload();
        },

        _onReset: function (evt) {
            evt.preventDefault();
            this.clear(true);
        },

        _onAbort: function (evt) {
            evt.preventDefault();
            this.abort();
        },

        _onDrop: function (files) {
            this._getFiles(files, function (data) {
                if (this.emit('drop', data) !== false) {
                    this.add(data.files);
                }
            });
        },

        _onDropHover: function (state, evt) {
            if (this.emit('dropHover', {state: state, event: evt}) !== false) {
                var hover = this.option('elements.dnd.hover');
                if (hover) {
                    $(evt.currentTarget).toggleClass(hover, state);
                }
            }
        },

        _getFile: function (uid) {
            return api.filter(this.files, function (file) {
                return api.uid(file) == uid;
            })[0];
        },

        _getUploadEvent: function (xhr, extra) {
            xhr = this.xhr || xhr;

            var evt = {
                xhr: xhr
                , file: this.xhr.currentFile
                , files: this.xhr.files
                , widget: this
            };

            return _extend(evt, extra);
        },

        _emitUploadEvent: function (prefix, file, xhr) {
            var evt = this._getUploadEvent(xhr);
            this.emit(prefix + 'Upload', evt);
        },

        _emitProgressEvent: function (prefix, event, file, xhr) {
            var evt = this._getUploadEvent(xhr, event);
            this.emit(prefix + 'Progress', evt);
        },

        _emitCompleteEvent: function (prefix, err, xhr, file) {
            var evt = this._getUploadEvent(xhr, {
                error: err
                , status: xhr.status
                , statusText: xhr.statusText
                , result: xhr.responseText
            }),
            error = null;

            if (xhr.status !== 200) {
                error = "파일 업로드가 실패하였습니다.\n\n"
                    + "서버 응답 : " + xhr.status + ' ' + xhr.statusText + '\n'
                    + "오류 내용 : " + xhr.responseText
                evt.error = error;

                // 서버 응답 완료후 _emitCompleteEvent이 한번 더 실행되므로, 두번째 요청에서 경고
                if (this.__fileId === api.uid(file)) {
                    alert(error);
                }

                file.complete = false;
                this.remove(this.__fileId);
            } else {
                if (prefix == 'file') {
                    file.complete = true;
                }

                if (this.options.dataType == 'json') {
                    try {
                        var data = $.parseJSON(evt.result);

                        if (data.hasOwnProperty('_errors') && data._errors !== '') {
                            error = "파일 업로드가 실패하였습니다." + data._errors.replace(/\\n/g, '\n')
                            evt.error = error;

                            // 서버 응답 완료후 _emitCompleteEvent이 한번 더 실행되므로, 두번째 요청에서 경고
                            if (this.__fileId === api.uid(file)) {
                                alert(error);
                            }

                            file.complete = false;
                            this.remove(this.__fileId);
                        } else {
                            // 전송완료시 리턴값 추가나 수정가능한 위치
                            if (data.hasOwnProperty('_datas') && data._datas !== '') {
                                var fileData = data._datas.split("|");
                                file.temp_name = fileData[3];
                            } else {
                                error = "파일 업로드가 실패하였습니다.\n\n서버 응답 결과에서 _datas 항목이 없습니다.";
                                evt.error = error;
                                alert(error);
                            }
                        }
                    } catch (err) {
                        evt.error = err;
                    }
                }
            }

            // console.log('  this.files', this.files);

            this.emit(prefix + 'Complete', evt);
        },

        _onUploadEvent: function (evt, ui) {
            var _this = this, $progress = _this.$progress, type = evt.type;

            if (type == 'progress') {
                $progress.stop().animate({width: ui.loaded / ui.total * 100 + '%'}, 300);
            } else if (type == 'upload') {
                // Начало загрузки
                $progress.width(0);
            } else {
                // Завершение загрузки
                var fn = function () {
                    $progress.dequeue();
                    _this[_this.options.clearOnComplete ? 'clear' : 'dequeue']();
                };

                this.xhr = null;
                this.active = false;

                if ($progress.length) {
                    $progress.queue(fn);
                } else {
                    fn();
                }
            }
        },

        _onFileUploadPrepare: function (file, opts) {
            var
                uid = api.uid(file)
//				, deg	= this._rotate[uid]
//				, crop	= this._crop[uid]
//				, resize = this._resize[uid]
                , evt = this._getUploadEvent(this.xhr)
            ;
            /*
             if( deg || crop || resize ){
             var trans = $.extend(true, {}, opts.imageTransform || {});
             deg = (deg != null) ? deg : (this.options.imageAutoOrientation ? 'auto' : void 0);

             if( $.isEmptyObject(trans) || _isOriginTransform(trans) ){
             _extend(trans, resize);

             trans.crop		= crop;
             trans.rotate	= deg;
             }
             else {
             _each(trans, function (opts){
             _extend(opts, resize);

             opts.crop	= crop;
             opts.rotate	= deg;
             });
             }

             opts.imageTransform = trans;
             }
             */
            evt.file = file;
            evt.options = opts;

            this.emit('filePrepare', evt);
        },

        _onFileUploadEvent: function (evt, ui) {
            var
                _this = this
                , type = evt.type.substr(4)
                , uid = api.uid(ui.file)
                , $file = this.$file(uid)
                , $progress = this._$fileprogress
                , opts = this.options
            ;

            //console.log('_onFileUploadEvent()', type, this.__fileId, uid, (this.__fileId !== uid));

            if (this.__fileId !== uid) {
                this.__fileId = uid;
                this._$fileprogress = $progress = this.$elem('file.progress', $file);
            }

            // [2016-05] 업로드 진행시 progressbar 위치에 맞게 포커스주기
            $progress.focus();

            if (type == 'progress') {
                $progress.stop().animate({width: ui.loaded / ui.total * 100 + '%'}, 300);
            } else if (type == 'upload' || type == 'complete') {
                var fn = function () {
                    var
                        elem = 'file.' + type
                        , $remove = _this.$elem('file.remove', $file)
                    ;

                    if (type == 'upload') {
                        // $remove.hide();
                        $progress.width(0);
                    } else if (opts.onFileRemoveCompleted) {
                        // $remove.show();
                    }

                    $progress.dequeue();

                    _this.$elem(elem + '.show', $file).show();
                    _this.$elem(elem + '.hide', $file).hide();
                };

                if ($progress.length) {
                    $progress.queue(fn);
                } else {
                    fn();
                }

                if (type == 'complete') {
                    this.uploaded.push(ui.file);
                    //delete this._rotate[uid];
                }
            }
        },

        _redraw: function (clearFiles/**Boolean*/, clearPreview/**Boolean*/) {
            var
                files = this.files
                , active = !!this.active
                , empty = !files.length && !active
                , emptyQueue = !this.queue.length && !active
                , name = []
                , size = 0
                , $files = this.$files
                , offset = $files.children().length
                , preview = this.option('elements.preview')
                , filePreview = this.option('elements.file.preview')
            ;

            if (clearFiles) {
                this.$files.empty();
            }

            if (clearPreview && preview && preview.el && !this.queue.length) {
                this.$(preview.el).empty();
            }

            // 파일 업로드 안내 문구 전환
            if (this.files.length > 0 ) {
                $(".b-upload__guide_dnd").hide();
                $(".b-upload__guide_add").hide();
            } else {
                if (FileAPI.support.dnd) {
                    $(".b-upload__guide_add").hide();
                    $(".b-upload__guide_dnd").show();
                } else {
                    $(".b-upload__guide_dnd").hide();
                    $(".b-upload__guide_add").show();
                }
            }

            var total_normal = 0, total_bigfile = 0;
            _each(files, function (file, i) {
                if (file.uptype == "normal") {
                    total_normal += file.size;
                } else if (file.uptype == "bigfile") {
                    total_bigfile += file.size;
                }
            }, this);

            _each(files, function (file, i) {
                // 아래 라인에서 file값이 입력됨 "fileapi1441873172428": "fileapi14418731724283"
                var uid = api.uid(file);

                name.push(file.name);
                if (!file.uptype) {

                    if (file.size < (this.options.maxNormalTotalSize - total_normal)) {
                        //alert("normal: " + file.size+"+"+(this.options.maxNormalTotalSize-total_normal));
                        file.uptype = "normal";
                    } else if (file.size < (this.options.maxBigfileTotalSize - total_bigfile)) {
                        //alert("bigfile: " + file.size+"+"+(this.options.maxNormalTotalSize-total_bigfile));
                        file.uptype = "bigfile";
                    }
                }
                size += file.complete ? 0 : file.size;

                if (preview && preview.el) {
                    this._makeFilePreview(uid, file, preview, true);
                } else if ($files.length && !this.$file(uid).length) {
                    // 아래 객체배열에 값을 추가하면 템플릿에 값이 변경됨....
                    var
                        html = this.itemTplFn({
                            $idx: offset + i
                            , uid: uid
                            , name: file.name
                            , temp_name: ""
                            , type: file.type
                            , size: file.size
                            , complete: !!file.complete
                            , sizeText: this._getFormatedSize(file.size)
                            , uptype: file.uptype == "bigfile"
                                ? ((FileApiMobile) ? "대용량" : "대용량첨부")
                                : ((FileApiMobile) ? "일반" : "일반첨부")
                        });
                    //html변수에 파일템플릿에 값이 매치된 문자열이 담김
                    var $file = $(html).attr(_dataFileId, uid);

                    file.$el = $file;
                    $files.append($file);

                    if (file.complete) {
                        this.$elem('file.upload.hide', $file).hide();
                        this.$elem('file.complete.hide', $file).hide();
                    }

                    //if( filePreview.el ){
                    //	this._makeFilePreview(uid, file, filePreview);
                    //}
                }
            }, this);
            this._filesize_display();

            this.$elem('name').text(name.join(', '));
            this.$elem('size').text(!emptyQueue ? this._getFormatedSize(size) : '');


            this.$elem('empty.show').toggle(empty);
            this.$elem('empty.hide').toggle(!empty);


            this.$elem('emptyQueue.show').toggle(emptyQueue);
            this.$elem('emptyQueue.hide').toggle(!emptyQueue);


            this.$elem('active.show').toggle(active);
            this.$elem('active.hide').toggle(!active);


            this.$('.js-fileapi-wrapper,:file')
                [active ? 'attr' : 'removeAttr']('aria-disabled', active)
                [propFn]('disabled', active)
            ;

            // Upload control
            this._disableElem('ctrl.upload', emptyQueue || active);

            // Reset control
            this._disableElem('ctrl.reset', emptyQueue || active);

            // Abort control
            this._disableElem('ctrl.abort', !active);
        },

        _disableElem: function (name, state) {
            this.$elem(name)
                [state ? 'attr' : 'removeAttr']('aria-disabled', 'disabled')
                [propFn]('disabled', state)
            ;
        },

        _filesize_display: function () {
            var
                size = 0
                , total_normal = 0
                , total_bigfile = 0
                , total_bigfile_count = 0
                , mf_attach_names = ""
            ;
            _each(this.files, function (file, i) {
                // 아래 라인에서 file값이 입력됨 "fileapi1441873172428": "fileapi14418731724283"
                var uid = api.uid(file);

                /**
                 * 추가된 파일과 일반첨부 용량최과시 대용량첨부 구분값입력 처리
                 */
                if (file.uptype === "bigfile") {
                    total_bigfile += file.size;
                    total_bigfile_count++;
                } else {
                    total_normal += file.size;
                }
                size += file.size;
                mf_attach_names += "||" + file.name + "|" + file.temp_name + "|" + (file.uptype === "bigfile" ? "1" : "0").toString();
            });
            total_normal += Number(document.getElementById('mf_attachs_size').value);

            $('#mf_normal_size').val(total_normal);
            $('#mf_attach_names').val(mf_attach_names);
            $('#mf_attach_datas').val(mf_attach_names);
            $('#sUploadSize').text(this._getFormatedSize(total_normal));
            $('#bUploadSize').text(this._getFormatedSize(total_bigfile));
            $('#sMaxSize').text(this._getFormatedSize(this.options.maxNormalTotalSize));
            $('#bMaxSize').text(this._getFormatedSize(this.options.maxBigfileTotalSize));

            // 대용량 첨부 안내 전환
            if (total_bigfile_count > 0) {
                $(".b-upload__bigfile_guide").show();
            } else {
                $(".b-upload__bigfile_guide").hide();
            }
        },

        emit: function (name, arg) {
            var opts = this.options, evt = $.Event(name.toLowerCase()), res;
            evt.widget = this;
            name = $.camelCase('on-' + name);
            if ($.isFunction(opts[name])) {
                res = opts[name].call(this.el, evt, arg);
            }
            this.$el.triggerHandler(evt, arg);
            return (res !== false) && !evt.isDefaultPrevented();
        },

        /**
         * Add files to queue
         * @param  {Array}    files
         * @param  {Boolean}  [clear]
         */
        add: function (files, clear) {
            files = [].concat(files);

            if (files.length) {
                var
                    opts = this.options
                    , sortFn = opts.sortFn
                ;

                if (sortFn) {
                    files.sort(sortFn);
                }

                if (this.xhr) {
                    this.xhr.append(files);
                }

                this.queue = clear ? files : this.queue.concat(files);
                this.files = clear ? files : this.files.concat(files);

                if (this.active) {
                    this.xhr.append(files);
                    this._redraw(clear);
                } else {
                    this._redraw(clear);
                    if (this.options.autoUpload) {
                        this.upload();
                    }
                }
            }
        },

        /**
         * Find element
         * @param    {String}    sel
         * @param    {jQuery}    [ctx]
         * @return    {jQuery}
         */
        $: function (sel, ctx) {
            if (typeof sel === 'string') {
                sel = /^#/.test(sel) ? sel : (ctx ? $(ctx) : this.$el).find(sel);
            }
            return $(sel);
        },

        /**
         * @param  {String}   name
         * @param  {Boolean|jQuery}  [up]
         * @param  {jQuery}   [ctx]
         * @return {jQuery}
         */
        $elem: function (name, up, ctx) {
            if (up && up.jquery) {
                ctx = up;
                up = false;
            }

            var sel = this.option('elements.' + name);
            if (sel === void 0 && up) {
                sel = this.option('elements.' + name.substr(0, name.lastIndexOf('.')));
            }

            return this.$($.type(sel) != 'string' && $.isEmptyObject(sel) ? [] : sel, ctx);
        },

        /**
         * @param  {String}  uid
         * @return {jQuery}
         */
        $file: function (uid) {
            return this.$('[' + _dataFileId + '="' + uid + '"]');
        },

        /**
         * Get/set options
         * @param {String} name
         * @param {*} [value]
         * @return {*}
         */
        option: function (name, value) {
            if (value !== void 0 && $.isPlainObject(value)) {
                _each(value, function (val, key) {
                    this.option(name + '.' + key, val);
                }, this);

                return this;
            }

            var opts = this.options, val = opts[name], i = 0, len, part;

            if (name.indexOf('.') != -1) {
                val = opts;
                name = name.split('.');
                len = name.length;

                for (; i < len; i++) {
                    part = name[i];

                    if ((value !== void 0) && (len - i === 1)) {
                        val[part] = value;
                        break;
                    } else if (!val[part]) {
                        val[part] = {};
                    }

                    val = val[part];
                }
            } else if (value !== void 0) {
                opts[name] = value;
            }

            if (value !== void 0) {
                this._setOption(name, value, this._options[name]);
                this._options[name] = value;
            }

            return value !== void 0 ? value : val;
        },

        _setOption: function (name, nVal) {
            switch (name) {
                case 'accept':
                case 'multiple':
                    this.$(':file')[nVal ? propFn : removePropFn](name, nVal);
                    break;


                case 'paramName':
                    nVal && this.$(':file')[propFn]('name', nVal);
                    break;
            }
        },

        serialize: function () {
            var obj = {}, val;

            this.$el.find(':input').each(function (name, node) {
                if (
                    (name = node.name) && !node.disabled
                    && (node.checked || /select|textarea|input/i.test(node.nodeName) && !/checkbox|radio|file/i.test(node.type))
                ) {
                    val = $(node).val();
                    if (obj[name] !== void 0) {
                        if (!obj[name].push) {
                            obj[name] = [obj[name]];
                        }

                        obj[name].push(val);
                    } else {
                        obj[name] = val;
                    }
                }
            });

            return obj;
        },

        upload: function () {
            if (!this.active && this.emit('beforeUpload', {widget: this, files: this.queue})) {
                this.active = true;

                var
                    $el = this.$el
                    , opts = this.options
                    , files = {}
                    , uploadOpts = {
                        url: opts.url
                        , data: _extend({}, this.serialize(), opts.data)
                        , headers: opts.headers
                        , files: files

                        , uploadRetry: opts.uploadRetry
                        , networkDownRetryTimeout: opts.networkDownRetryTimeout
                        , chunkSize: opts.chunkSize
                        , chunkUploadRetry: opts.chunkUploadRetry
                        , chunkNetworkDownRetryTimeout: opts.chunkNetworkDownRetryTimeout

                        , prepare: _bind(this, this._onFileUploadPrepare)
                        , imageOriginal: opts.imageOriginal
                        , imageTransform: opts.imageTransform
                        , imageAutoOrientation: opts.imageAutoOrientation
                    }
                ;

                // Set files
                files[$el.find(':file').attr('name') || 'files[]'] = this.queue;

                // Add event listeners
                _each(['Upload', 'Progress', 'Complete'], function (name) {
                    var lowerName = name.toLowerCase();
                    uploadOpts[lowerName] = _bind(this, this['_emit' + name + 'Event'], '');
                    uploadOpts['file' + lowerName] = _bind(this, this['_emit' + name + 'Event'], 'file');
                }, this);

                // Start uploading
                this.xhr = api.upload(uploadOpts);
                this._redraw();
            }
        },

        abort: function (text) {
            if (this.active && this.xhr) {
                this.xhr.abort(text);
            }
        },

        remove: function (file) {
            var uid = typeof file == 'object' ? api.uid(file) : file;

            // 파일정보
            var fileInfo = this._getFile(file);

            //console.log('remove()', fileInfo);

            if (typeof fileInfo !== 'undefined' && fileInfo.name !== null && fileInfo.complete === true) {
                // 서버에서 파일 삭제
                $.ajax({
                    url: nmpath + '/mail_write_multiupload_del.php',
                    type: 'GET',
                    data: {
                        'file_name': fileInfo.name,
                        'mf_add_attach_names': $('#mf_attach_names').val(),
                        'type': 'json',
                    },
                    dataType: "json",
                    success: function (html, textStatus, xhr) {
                        fileapi_ajax_error_check("파일 삭제가 실패하였습니다.", html, textStatus, xhr, $);
                    }
                });
            }

            this.$file(uid).remove();

            this.queue = api.filter(this.queue, function (file) {
                return api.uid(file) != uid;
            });
            this.files = api.filter(this.files, function (file) {
                return api.uid(file) != uid;
            });

            this._redraw();
        },

        clear: function (all) {
//			this._crop		= {};
//			this._resize	= {};
//			this._rotate	= {}; // rotate deg

            if ($('#mf_attach_names').val() !== '') {
                // 여기에 서버의 파일 삭제 추가
                $.ajax({
                    url: nmpath + '/mail_write_multiupload_del.php',
                    type: 'GET',
                    data: {
                        'file_name': 'ALL',
                        'mf_add_attach_names': $('#mf_attach_names').val(),
                        'type': 'json',
                    },
                    dataType: "json",
                    success: function (html, textStatus, xhr) {
                    }
                });
            }

            this.queue = [];
            this.files = []; // all files
            this.uploaded = []; // uploaded files

            all = all === void 0 ? true : all;
            this._redraw(all, all);
        },

        dequeue: function () {
            this.queue = [];
            this._redraw();
        },

        widget: function () {
            return this;
        },

        toString: function () {
            return '[jQuery.FileAPI object]';
        },

        destroy: function () {
            this.$files.empty().append(this.$fileTpl);

            this.$el
                .off('.fileapi')
                .removeData('fileapi')
            ;

            _each(this.options.elements.ctrl, function (selector) {
                _isID(selector) && $(selector).off('click.fileapi');
            });
        }
    };


    function _isOriginTransform(trans) {
        var key;
        for (key in trans) {
            if (trans.hasOwnProperty(key)) {
                if (!(trans[key] instanceof Object || key === 'overlay')) {
                    return true;
                }
            }
        }
        return false;
    }


    function _checkFileByCriteria(file, name, imageSize, info) {
        if (imageSize && info) {
            var excepted = imageSize > 0 ? imageSize : imageSize[name],
                actual = info > 0 ? info : info[name.substr(3).toLowerCase()],
                val = (excepted - actual),
                isMax = /max/.test(name)
            ;

            if ((isMax && val < 0) || (!isMax && val > 0)) {
                if (!file.errors) {
                    file.errors = {};
                }
                file.errors[name] = Math.abs(val);
            }
        }
    }


    function _extractFilesOverLimit(limit, countFiles, files, other) {
        if (limit) {
            var delta = files.length - (limit - countFiles);
            if (delta > 0) {
                _each(files.splice(0, delta), function (file, i) {
                    _checkFileByCriteria(file, 'maxFiles', -1, i);
                    other.push(file);
                });
            }
        }
    }


    /**
     * @export
     * @param    {Object}    options
     * @param    {String}    [value]
     */
    $.fn.fileapi = function (options, value) {
        var plugin = this.data('fileapi');

        if (plugin) {
            if (options === 'widget') {
                return plugin;
            }

            if (typeof options == 'string') {
                var fn = plugin[options], res;

                if ($.isFunction(fn)) {
                    res = fn.apply(plugin, _slice.call(arguments, 1));
                } else if (fn === void 0) {
                    res = plugin.option(options, value);
                } else if (options === 'files') {
                    res = fn;
                }

                return res === void 0 ? this : res;
            }
        } else if (options == null || typeof options == 'object') {
            this.data('fileapi', new Plugin(this, options));
        }

        return this;
    };


    $.fn.fileapi.version = '0.4.11';
    $.fn.fileapi.tpl = function (text) {
        var index = 0;
        var source = "__b+='";

        text.replace(/(?:&lt;|<)%([-=])?([\s\S]+?)%(?:&gt;|>)|$/g, function (match, mode, expr, offset) {
            source += text.slice(index, offset).replace(/[\r\n"']/g, function (match) {
                return '\\' + match;
            });

            if (expr) {
                if (mode) {
                    source += "'+\n((__x=(" + expr + "))==null?'':" + (mode == "-" ? "__esc(__x)" : "__x") + ")\n+'";
                } else {
                    source += "';\n" + expr + "\n__b+='";
                }
            }

            index = offset + match.length;
            // 이 블럭내에서 테플릿상에 정의된 매칭문자열을 조회하는듯
            //alert(match); // <%=uid%>, <%-name%>, <%-sizeText%>, <%=uptype%>
            return match;
        });

        return new Function("ctx", "var __x,__b=''," +
            "__esc=function(val){return typeof val=='string'?val.replace(/</g,'&lt;').replace(/\"/g,'&quot;'):val;};" +
            "with(ctx||{}){\n" + source + "';\n}return __b;");
    };
})(jQuery, FileAPI);
