var Uploader = new Class({
    Implements:[Options],
    
    form: null,
    uploading: null,
    swfu: null,
    overlay: null,
    loaded: false,
    url: null,
    
    options:{
        maxfiles: 0,
        target: null,
        extensions: ["jpg","jpeg","gif","png","bmp"],
        maxsize: null,
        debug: true
    },

    /**
     * initialize uploader
     * 
     * @param Element form    form to handle
     * @param object  options
     */
    initialize: function(form,options){
        if(!window.console) options.debug = false;
        if(!options.maxsize){
            options.maxsize = $(document.getElementsByName("MAX_FILE_SIZE")[0]).get("value").toInt().round(1) + " B";
        }
        this.form = $(form);
        this.setOptions(options);
        
        this.url = this.form.get("action");
        //alert(this.url);
        this.form.addEvent("submit",function(event){if(!this.onSubmit()){event.preventDefault();}}.bindWithEvent(this));
        var shown = false;
        this.form.getElements("input[type=file]").each(function(input){
            input.addEvent("change",function(event){
                this.onFileChange($(event.target));
            }.bindWithEvent(this));
            if(input.get("value") != ""){
                this.onFileAdd(input);
            }else{
                if(!shown){                                                     // show only first empty input field
                    shown = true;
                    input.addClass("fileInput");
                }else{                                                          // hide all others
                    input.addClass("fileEmpty");
                }
            }
        }.bind(this));
        
        if(!shown){                                                             // no remaining empty input field remaining
            this.addInput();
        }
        
        this.overlay = new Overlay(new Element("div",{
            "class":"upload"
        }).adopt(new Element("h1",{
            "text": "Uploading"
        })).adopt(new Element("p",{
            "text": "Uploading images, please wait..."
        })).adopt(new Element("table",{"class":"filelist"}).adopt(new Element("tbody")/*.adopt(new Element("tr").adopt(new Element("td",{text:1})).adopt(new Element("td",{text:2})))*/)),{
            "onEscape": $empty,
            "onClick": $empty
        });
        this.overlay.add = function(file){
            return new Element("tr",{"id":file.id}).adopt(new Element("td",{"text":file.name})).adopt(new Element("td",{"class":"status"})).inject(this.overlay.canvas.getElement("tbody"),"bottom");
        }.bind(this);
        this.overlay.get = function(file){
            return this.overlay.canvas.getElement("table").getElement("#"+file.id);
        }.bind(this);
        
        this.swfu = new SWFUpload({
            flash_url : "/src/swfupload.swf",
            upload_url: this.url,
            post_params:{
                "mode":    "flash",
                "session": Cookie.read("session") || ""
            },
            file_size_limit : this.options.maxsize || 0,
            file_types : (this.options.extensions ? this.options.extensions.map(function(v){ return "*."+v;}).join(";") : "*.*"),
            file_types_description : "All Image Files",
            //file_upload_limit : 5,
            file_queue_limit : this.options.maxfiles || 0,

            // Button settings,
            button_width: 204,
            button_height: 64,
            button_placeholder: new Element("div").inject(new Element("div",{"id":"flashupload","class":"hidden"}).inject(this.form,"before"),"top"),
            button_cursor: SWFUpload.CURSOR.HAND,
            button_window_mode: SWFUpload.WINDOW_MODE.TRANSPARENT,
            
            swfupload_loaded_handler : function(){
                if(this.options.debug) console.debug("swfupload_loaded");
                if(!this.loaded){
                    this.loaded = true;
                    if(!Browser.Platform.linux){ // flashuploader is extremely unreliable for linux
                        this.form.addClass("hidden");
                        $("flashupload").removeClass("hidden");
                    }
                    $("uploadinfo").empty().grab(new Element("a",{"text":"[click to toggle between classic/flash uploader]","href":"#"}).addEvent("click",function(){
                        this.form.toggleClass("hidden");
                        $("flashupload").toggleClass("hidden");
                        window.fireEvent("resize");
                        return false;
                    }.bindWithEvent(this)));
                    window.fireEvent("resize");
                }
            }.bind(this),
            
            file_dialog_complete_handler: function(selected,d2,queued){
                if(this.options.debug) console.debug("file_dialog_complete_handler",selected,d2,queued);
                if(queued){
                    var num = this.overlay.canvas.getElement("table").getElements("tr").length;
                    this.overlay.show().canvas.getElement("p").set("text","Uploading "+(num == 1 ? "image" : (num + " images"))+", please wait...");
                    this.swfu.startUpload();
                }else if(selected){ // files selected but nothing queued => just queue errors
                    if(selected > this.options.maxfiles && this.options.maxfiles){
                        new Overlay.Error("You may only upload up to "+this.options.maxfiles+" images at a time");
                    }else{
                        this.onComplete();
                    }
                }
            }.bind(this),
            
            file_queued_handler : function(file){
                if(this.options.debug) console.debug("file_queued_handler",file);
                this.overlay.add(file);
            }.bind(this),
            
            file_queue_error_handler : function(file,code,msg){
                if(this.options.debug) console.debug("file_queue_error_handler",file,code,msg);
                var reason = "Error uploading file\n";
                if(code == SWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT){
                    var size = (file.size / (1024 * 1024)).round(1);
                    var max  = (this.options.maxsize.toInt() / (1024 * 1024)).round(1);
                    reason += "File size of "+size+" MiB exceeds limit of "+max+" MiB";
                }else if(code == SWFUpload.QUEUE_ERROR.INVALID_FILETYPE){
                    reason += "File extension doesn't look like a valid image.\nAllowed image extensions are: "+this.options.extensions.join(", ");
                }else if(code == SWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE){
                    reason += "Empty file or bigger than 4 GiB";
                    if(Browser.Platform.win){
                        reason += "(This may also happen for Windows shortcuts)";
                    }
                }else if(code == SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED){
                    reason += "Maximum number of files exceeded";
                    if(!file) return; // workaround: swfupload supplies a NULL file :-/
                }else{
                    reason += "Unknown: " + msg + "(" + code + ")";
                }
                this.overlay.add(file).addClass("error").getLast().set("title",reason);
            }.bind(this),
            
            upload_start_handler : function(file){
                if(this.options.debug) console.debug("upload_start_handler",file);
                this.overlay.get(file).addClass("loading").getLast().set("title","Uploading...");
            }.bind(this),
            
            upload_progress_handler : function(file,completed,total){ 
                //if(this.options.debug) console.debug("upload_progress_handler",file,completed,total);
                
                var percent = (completed * 100 / total).round();
                if(percent >= 100 && completed < total){
                    percent = 99;
                }
                this.overlay.get(file).getLast().set("text",percent + "%");
            }.bind(this),
            
            upload_error_handler : function(file,code,msg){
                if(this.options.debug) console.debug("upload_error_handler",file,code,msg);
                var reason = "Error uploading file\n";
                if(code == -1){
                    
                }else{
                    reason += msg;
                }
                this.onFileComplete(this.overlay.get(file).removeClass("loading"),reason);
            }.bind(this),
            
            upload_success_handler : function(file,response){
                if(this.options.debug) console.debug("upload_success_handler",file,response);
                this.onFileComplete(this.overlay.get(file),response);
            }.bind(this),
            
            upload_complete_handler : function(){
                if(this.swfu.getStats().files_queued == 0){
                    this.onComplete();
                }
            }.bind(this)
        });
        
        $("uploadinfo").set("text","Your browser does not support Flash, you're missing the multi-uploader");
    },
    
    /**
     * called when all files finished uploading (will also be called when only invalid files were skipped)
     */
    onComplete: function(){
        if(this.options.debug) console.debug("onComplete");
        var errors = this.overlay.canvas.getElement("table").getElements("tr.error").length;
        var oks    = this.overlay.canvas.getElement("table").getElements("tr.done").length;
        if(oks && !errors){
            this.overlay.show().canvas.getElement("p").empty()
                        .grab(new Element("span",{"text":"Finished uploading, redirecting... "}))
                        .grab(new Element("a",{"href":this.url,"text":"(Click here if automatic redirection doesn't work for you)"}).addClass("small"));
            if(!this.options.debug) window.location.href = this.url;
        }else if(oks && errors){
            this.overlay.show().canvas.getElement("p").empty()
                        .grab(new Element("span",{"text":(errors == 1 ? "One image" : "Some images") + " failed, please see the following list for details. Once finished, "}))
                        .grab(new Element("a",{"href":this.url,"text":"click here to proceed"}))
                        .grab(new Element("span",{"text":" to your image" + (oks == 1 ? "" : "s") + " (failed images will be discarded)."}));
        }else if(!oks && errors){
            this.overlay.show().canvas.getElement("p").empty()
                        .grab(new Element("span",{"text":(errors == 1 ? "The image" : "All images") + " you've selected failed, please see the following list for details. Once finished, "}))
                        .grab(new Element("a",{"href":"#","text":"click here"}).addEvent("click",function(){
                            this.overlay.canvas.getElement("tbody").empty();
                            this.overlay.hide();
                            return false;
                        }.bindWithEvent(this)))
                        .grab(new Element("span",{"text":" to discard the list of failed images."}));
        }
        this.overlay.position();
    },
    
    /**
     * called when a single file finished uploading (will also be called when it finishes with an error, see response)
     * 
     * @param Element       elem
     * @param string|object response message to be parsed as json or parsed response object
     */
    onFileComplete: function(elem,response){
        if(this.options.debug) console.debug("onFileComplete",response);
        elem.removeClass("loading").getLast().empty();
        var json = response;
        if($type(response) != "object"){
            if(response.length < 2 || response[0] != "{" || response[response.length-1] != "}"){
                this.onFileError(elem,"Invalid response "+response);
                return;
            }
            json = JSON.decode(response,true);
        }
        if(!json){
            this.onFileError(elem,"Unparsable response "+response);
            return;
        }
        if(!json.status || json.status != "ok"){
            this.onFileError(elem,json.message ? json.message : "No error message given!?");
            return;
        }
        if(json.url){
            this.url = json.url;
            if(this.options.debug) console.debug("new upload target url",this.url);
            try{this.swfu.setUploadURL(this.url)}catch(ex){ };
        }
        elem.addClass("done").getLast().set("title",json.message ? json.message : "Successfully uploaded");
    },
    
    /**
     * called when a single file has an error
     * 
     * @param Element elem
     * @param string  msg
     */
    onFileError: function(elem,msg){
        if(this.options.debug) console.debug("onFileError",msg);
        elem.addClass("error").getLast().set("title",msg);
        
        //new Overlay.Error(msg);
    },
    
    /**
     * add new input field or warning when the limit is reached
     */
    addInput: function(){
        var fields = this.form.getElements("input[type=file]").length;
        if(!this.options.maxfiles || fields < this.options.maxfiles){
            new Element("input",{
                "type":  "file",
                "class": "input fileInput",
                "name":  "file"+(fields+1)
            }).inject(this.form.getElements("input[type=file]").getLast(),"after").addEvent("change",function(event){ this.onFileChange($(event.target));}.bindWithEvent(this));
        }else{
            new Element("input",{
                "type":     "file",
                "disabled": true,
                "class":    "input fileInput",
                "name":     "file0"
            }).inject(this.form.getElement("input[type=file]"),"before");
            
            new Element("div",{
                "class": "info"
            }).grab(new Element("img",{
                "src": "/src/button_info.png",
                "alt": "[info]"
            })).grab(new Element("span",{
                "text": "Maximum number of "+fields+" images reached"
            })).inject(this.options.target,"bottom");
        }
    },
    
    /**
     * new file selected
     * 
     * @param Element input           file input
     * @param boolean ignoreduplicate whether to ignore duplicate files
     */
    onFileChange: function(input,ignoreduplicate){
        var filename = this.getFilename(input);
        var p = filename.lastIndexOf(".");
        if(p == -1){
            this.fileClear(input);
            new Overlay.Error("File doesn't have an extension at all!\nAllowed image extensions are: "+this.options.extensions.join(", "));
            return;
        }else{
            var ext = filename.substring(p+1,filename.length).toLowerCase();
            if(!this.options.extensions.contains(ext)){
                this.fileClear(input);
                new Overlay.Error("File extension \"" + ext + "\" doesn't look like a valid image.\nAllowed image extensions are: "+this.options.extensions.join(", "));
                return;
            }
        }
        
        if(!ignoreduplicate){
            var unique = true;
            this.form.getElements(".fileSelected").each(function(elem){
                if(unique && input.get("value") == elem.get("value")){
                    unique = false;
                }
            });
            if(!unique){
                new Overlay.Confirm("You've already selected a file with the same name!\nAdd again anyway?",{
                    title: "Warning",
                    onAnswer: function(confirmed,overlay){
                        if(parseInt(confirmed)){
                            this.onFileChange(input,true);
                        }else{
                            this.fileClear(input);
                        }
                        overlay.hide();
                    }.bind(this)
                });
                return;
            }
        }
        
        this.onFileAdd(input);
        
        var unused = this.form.getElement(".fileEmpty");
        if(unused){
            unused.removeClass("fileEmpty").addClass("fileInput");              // show next empty file input
        }else{                                                                  // no empty file input remaining
            this.addInput();
        }
        window.fireEvent("resize");
    },
    
    /**
     * add given file to list and hide input field
     * 
     * @param Element input file input
     */
    onFileAdd: function(input){
        input.removeClass("fileInput").addClass("fileSelected");
        
        new Element("div",{
            "class": "file"
        }).grab(new Element("a",{
            "href": "#"
        }).grab(new Element("img",{
            "src": "/src/button_remove.png",
            "alt": "[remove]",
            "title": "Remove "+ this.getFilename(input) + " from list"
        })).addEvent("click",function(event,input){
            this.onFileRemove(input,$(event.target).getParent("div"));
            event.preventDefault();
        }.bindWithEvent(this,input))).grab(new Element("span",{
            "text": this.getFilename(input)
        })).inject(this.options.target,"bottom");
    },
    
    /**
     * remove given file from list and display at least one input field
     * 
     * @param Element input file input
     * @param Element div   list div element
     */
    onFileRemove: function(input,div){
        input = this.fileClear(input);
        
        var dummy = input.getParent().getElement("[name=file0]");               // check for dummy input field
        if(dummy){                                                              // dummy found => no empty field remains
            dummy.destroy();
            div.getParent().getElement(".info").destroy();
            input.addClass("fileInput");                                        // show this input
        }else{
            input.addClass("fileEmpty");                                        // there's already another input, so hide this one
        }
        
        input.removeClass("fileSelected");
        div.destroy();
        
        window.fireEvent("resize");
    },
    
    /**
     * submit form
     * 
     * @return boolean false=cancel submit action
     */
    onSubmit: function(){
        var ok = this.form.getElements(".fileSelected").length;
        
        if(!ok){
            new Overlay.Error("No images selected!?");
            return false;
        }
        
        var tbody = this.overlay.show().canvas.getElement("tbody");
        this.form.getElements(".fileSelected").addClass("toupload").each(function(elem){
            var row = new Element("tr").adopt(new Element("td",{"text":this.getFilename(elem)})).adopt(new Element("td",{"class":"status"}));
            elem.store("filelist",row);
            tbody.adopt(row);
        }.bind(this));
        
        var num = tbody.getElements("tr").length;
        this.overlay.canvas.getElement("p").set("text","Uploading "+(num == 1 ? "image" : (num + " images"))+", please wait...");     
        this.overlay.position();
        
        this.send(this.form.getElement("input.toupload"));
        
        return false;
    },
    
    /**
     * called when given input finished uploading using hidden iframe
     * 
     * @param Element input
     * @param object  json
     */
    onReceive: function(input,json){
        if(this.options.debug) console.debug("onReceive",input,json);
        input.removeClass("toupload").replaces(input.retrieve("placeholder"));  // move back input to previous position
        this.onFileComplete(input.retrieve("filelist"),json);
        this.uploading.destroy();
        this.uploading = null;
        
        input = input.getNext("input.toupload");
        if(input){                                                              // another file in the queue
            this.send.delay(100,this,input);
        }else{                                                                  // queue empty => finished
            this.onComplete();
        }
    },
    
    /**
     * submit given input field using a hidden iframe
     * 
     * @param Element input input field to submit
     */
    send: function(input){
        if(this.options.debug) console.debug("send",input);
        input.store("placeholder",new Element("span").inject(input,"after"))    // remember current position
             .retrieve("filelist").addClass("loading");                         // add loading indicator
        var iframe = new IFrame({"name":"uploaded","events":{"load":function(){
            if(this.options.debug) console.debug("IFrame finished",iframe);
            var doc  = iframe.contentWindow.document;
            var json = $(doc.lastChild.lastChild).get("text");                  // get contents of document.html.body
            this.onReceive.delay(10,this,[input,json]);                         // delay onReceive event to avoid removing element too early
        }.bindWithEvent(this)}});
        this.uploading = this.form.clone(false)
                         .set("target",iframe.get("name"))
                         .addClass("hidden")
                         .adopt(input,iframe,new Element("input",{"type":"hidden","name":"mode","value":"javascript"}));
        if(this.url){
            this.uploading.set("action",this.url);
        }
        $(document.body).adopt(this.uploading);
        this.uploading.submit();
    },
    
    /**
     * clear file input path
     * 
     * @param Element input file input
     * @return Element
     */
    fileClear: function(input){
        if(Browser.Engine.trident){ // IE needs some special care => clone element and remove old
            var clone = input.clone().cloneEvents(input).inject(input,"before");
            input.destroy();
            input = clone;
        }else{
            input.value = "";
        }
        return input;
    },
    
    /**
     * get filename component of given file input or full filepath string
     * 
     * @param Element|string input file input or full filepath
     * @return string
     */
    getFilename: function(input){
        var filename = input.get("value");
        if(filename.lastIndexOf("/") != -1){                                    // clear path component
            filename = filename.substring(filename.lastIndexOf("/")+1,filename.length);
        }else if(filename.lastIndexOf("\\") != -1){
            filename = filename.substring(filename.lastIndexOf("\\")+1,filename.length);
        }
        return filename;
    }
});
