////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Widget Object
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var DEFAULT_WIDGET_ICON_SRC = "images/icons/widget.png";

// Represents a Webwag Widget
// 
var Widget = Class.create();
Widget.prototype = {

    // constructor
    initialize: function(content, settings, descriptions) {
		
		// Private attributes
		this.isExtended = false;
		
		this._tag = null;
		this._titleSettingName = null;
		this._isDraggable = true;

		// Prefs
        this.prefs = {};
        this._form = "";

		// Content
		this._views 			= View.getDefaultViews();
		this._defaultViewName 	= null;
		this._currentViewName 	= null;
		this.content 			= content;

		// API attributes
        this.unseen 		= 0;
		this.clientPlatform = this._getClientPlatform();
        this.descriptions 	= descriptions;
		this.title 			= null;
		
		
        this._loadSettings(settings);
    },

    // Widget Internal methods
    _loadSettings: function(settings) {
        this.settings = settings;
        this.id = settings.id;
        this.src = settings.src;
        this.availability = settings.availability;
        // reinit prefs to avoid any conflict
        var currentPrefs = this.prefs;
        this.prefs = {};

        // populate prefs
        for (var i in currentPrefs) {
            //alert(pair.key);
            this.prefs[i] = new Pref(i, currentPrefs[i].value, currentPrefs[i].type, currentPrefs[i].label, currentPrefs[i].elems);
        }

        for (var i in settings) {
            //alert(pair.key);
            if (currentPrefs[i]) {
                this.prefs[i] = new Pref(i, settings[i], currentPrefs[i].type, currentPrefs[i].label, currentPrefs[i].elems);
                //this.prefs[i].setValue(settings[i]);
                } else {
                this.prefs[i] = new Pref(i, settings[i], "hidden", "");
            }
        }
        widget = this;
        if (this.id) {
            global.widgets[this.id] = this;
            }
    },
	
	// Gets the current client platform
	// (web, iphone)
	_getClientPlatform: function() {
		var platform = "web";
		if (window.client && client != null && client.platform != null) {
			platform = client.platform;
		}
	return platform;
	},
	
	/*obsolete replaced by _isScreenCompatible*/
	_isOnMobile: function() {
		if (this.settings["_compatibility"] && 
			this.settings["_compatibility"].indexOf("mobile") != -1 &&
			this.availability && 
			this.availability.indexOf("mobile") != -1) {
				
			return true;
		}
		else {
			return false;
		}
	},
	
	_isScreenCompatible : function(type){
		if (this.settings["_compatibility"] && 
			this.settings["_compatibility"].indexOf(type) != -1 &&
			this.availability && 
			this.availability.indexOf(type) != -1) {
			return true;
		}
		else {
			return false;
		}
	},
	
    _getSettings: function() {
        var settings = {};
        for (var i in this.prefs) {
            if (this.prefs[i].key) {
                if (this.prefs[i].value) {
                    settings[this.prefs[i].key] = this.prefs[i].value;
                } else {
                    settings[this.prefs[i].key] = "";
                }
            }
        }
        return settings;
    },

    _extend: function(widgetClass) {
        var instanceSettings = this._getSettings();
        widgetClass.descriptions = this.descriptions;
        Object.extend(this, widgetClass);
        this.isExtended = true;
        this._loadSettings(instanceSettings);
        return this;
    },

    _renderForm: function() {
        this._form = this._evalTemplate(this._form);
        renderer.renderWidgetForm(this);
    },

    _launch: function() {
        var widget = this;
        this.body = $("widget_" + this.id + "_body");
        this._renderForm();
        this.setIcon(this.getIconSrc());
        this.setTitle(this.getTitle());
		renderer.loadViews(this);
		this.showView(this._defaultViewName);
		if ((!this._views || !this._views[this._defaultViewName]) || this._views[this._defaultViewName].type != View.STATIC_TYPE) {
			this.callback("onLoad");
		}
    },
	
    _evalTemplate: function(content) {
        if (content && content != "") {
            content.scan(/(^|.|\r|\n)(#\{(.*?)\})/, function(match) {
                var code = "";
                var value = "undefined";

                var before = match[1];

                // check if there is an escape character otherwise,
                // interpret the content.
                if (before != '\\') {
                    code = match[3];
                    // Get the value
                    eval("try { value = widget.getValue(\"" + code + "\");\n } catch(e) { logger.log(\"widget._evalTemplate: <b>Internal Widget Error. Could not eval " + match[2] + " :</b> \"+e.name+\"\\n\\n\"+e.message, ERROR); }");
					try {
						content = content.replace(match[2], value);
					}
                    catch(e) { 
						logger.log("widget._showContent: <b>Internal Widget Error. Could not eval " + match[2] + " :</b> "+e.name+"\n\n"+e.message, ERROR);
					}
                }

            });
        }
        return content;
    },

    _showContent: function() {
		// We can now load the default content
        var content = "",widget=this;
        if (this.content && this.content.length > 0) {
            content = this.content;
        }

        content = this._evalTemplate(content);

        // Render output

        var output = content.stripScripts();
        renderer.renderWidgetContent(this, output);

        // Eval any javascript in content
        //debugEcho("_showContent: evaled scripts:"+content.extractScripts());
        //content.evalScripts();
        //eval scripts into global scope
        var scrs = content.extractScripts();
        scrs.each(function(s){window.eval(s);});
    },

    _eventCallback: function(event) {
        widget = this;
    },
    _onDragWidget: function(drag, ev) {
        var e,i,found,moved,x,y,ofs,th,tl;
        debugEcho("onDragWidget", drag, ev);

        /*var offset = Position.cumulativeOffset(drag.element);
        x = offset[0] + 10;
        y = offset[1];*/
        x = Event.pointerX(ev);
        y = Event.pointerY(ev) - 10;

        //x =  ev.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft));
        // y =  ev.pageX || (event.clientY +(document.documentElement.scrollTop || document.body.scrollTop));

        // Check over ghost widget
        if (Position.within(Draggable.elGhost, x, y))
            return;

        // Check tab switching
        var elLastTab=user.tabs[user.tabs.length-1].elTab;
        ofs= Position.cumulativeOffset(elLastTab);

        //finding min y & max y positions of the tabs
		if (!user.tabs[0].elTab.parentNode) {
			th = 0;
		}
		else {
			th = user.tabs[0].elTab.parentNode.offsetTop;
		}
        
        tl=ofs[1]+elLastTab.offsetHeight;
        if(y>th && y<tl){// check over tab zone
            debugEcho("tabs switching :)");
            for (i = 0, m = user.tabs.length; i < m;++i) {
                if (Position.within(e = user.tabs[i].elTab, x, y)) {
                    var tabId = i;
                    debugEcho("already selected test ", App.elTabSel, e);
                    user.tabs[i].highlight(1);
                    Draggable.elGhost.style.display = "none";
                    //user.tabs[tabId].show();
                    break;
                }
            }
            //for
            return;
        }
				
				if (Position.within($("webwagCol0"), x, y)||Position.within($("webwagCol1"), x, y) ||Position.within($("webwagCol2"), x, y) || Position.within($("webwagCol3"), x, y) || Position.within($("webwagCol4"), x, y) || Position.within($("webwagCol5"), x, y)) {
				
            dragWidget = drag.element;

            for (index in user.currentTab.widgets) {
                //var index = 0, len = this._widgets.length; index < len; ++index) {
                var w = user.currentTab.widgets[index].elWidgetBox;
                if(typeof user.currentTab.widgets[index] == "function")// fix  javascript enumeration bug 
                	continue;
                // dirty fix
                if(!w)
                	continue;
                // dirty fix ^^
                if(!w.parentNode){
			debugEcho("_onDragWidget: detecting widget not in collection! w.id="+w.id);
			continue;
                }
                if (!w || w == dragWidget || w.parentNode.className != "tabCol") {
                    //debugEcho("Droppable onHover:  continue...  x="+x+" y="+y+" " );
                    continue;
                }
                if (Position.within(w, x, y)) {
                    //debugEcho("Droppable onHover: within widget Position.within(w, x, y)  x=" + x + " y=" + y + "  w=" + (Position.cumulativeOffset(w).inspect()));
                    var overlap = Position.overlap('vertical', w);
                    // Bottom of the widget
                    if (overlap < 0.5) {
                        // Check if the ghost widget is not already below this widget
                        if (w.next() != Draggable.elGhost) {
                            w.parentNode.insertBefore(Draggable.elGhost, w.next());
                            moved = true;
                        }
                    } else {
                        // Top of the widget       
                        // Check if the ghost widget is not already above this widget
                        if (w.previous() != Draggable.elGhost) {
                            w.parentNode.insertBefore(Draggable.elGhost, w);
                            moved = true;
                        }
                    }
                    if (moved){
			Draggable.elGhost.style.display = "";
			if(App.tabSel)
                            	App.tabSel.highlight(0);
                    }
                    found = true;
                    break;
                }
            }


            if (!moved && !found) {
                for (i = 0; i < user.currentTab.nb_columns;++i) {
                var d;
                    e = user.currentTab._getElCol(i);
                    d=Position.cumulativeOffset(e);
                    //debugEcho("seeking for tabCol:  " + "tabCol" + user.currentTab.id + "_" + i + "   ",y>d[1]);

                    if ( x>d[0] && x< (d[0]+e.getWidth()) && y>d[1]  /*e.getElementsByTagName("div")  && Position.within(e, x, y)*/) {
	                    if(App.tabSel){
				App.tabSel.highlight(0);
				delete App.tabSel;
				}
			Draggable.elGhost.style.display = "";
                        e.appendChild(Draggable.elGhost);
                        break;
                    }
                }
            }
        }else{
					debugEcho("onHover not in tab content");
					for (type in global.screens) {
						//debugEcho (global.screens[type].isScreenVisible);
						if (global.screens[type].isScreenVisible && Position.within(global.screens[type].elScreenContainer,x,y)) {
							debugEcho("onHover: under webscreen : "+type);
							WebScreen.dragOver = type;
							Draggable.elGhost.style.display = "none";
							break;
						}
						else {
							WebScreen.dragOver = null;
						}
					}
        }

    },

    onEndDrag: function(draggable, event) {
        if (!draggable.element.id)
            return;
        var i,wid,w,e,tab,tabid,wel = draggable.element,
        ghost = Draggable.elGhost;
        debugEcho("onEnd draggable as " + wel.id);
        if (tab=App.tabSel) {
		tab.highlight(0);
		tab.show();
		e = tab._getElCol(0);//$("tabCol" + tab.id + "_0");
		e.insertBefore(wel, e.firstChild);
		user.getWidgetById(wel.widgetId).setValue("tab",tab.id);
		debugEcho("insert into another tab tabId=" + tab.id, e, "\t1stChild=", e.firstChild);
        } else {
					debugEcho("onEnd draggable as " + wel.id + " returning (no ghost inserted) no tab change");
					if(typeof WebScreen != 'undefined') {
						if(WebScreen.dragOver){
							// over webscreen
							if(!App.elNextWidget){
								debugEcho("onEndDrag: App.elParentContainer.appendChild(wel)",App.elParentContainer,wel);
								App.elParentContainer.appendChild(wel);
							}else{
								debugEcho("onEndDrag:App.elParentContainer.insertBefore(wel,App.elNextWidget)");
								App.elParentContainer.insertBefore(wel,App.elNextWidget);
							}
							debugEcho("onEndDrag: adding to webscreen");
							global.screens[WebScreen.dragOver].addWidget(wel.widgetId);
							//App.mobButton(wel.widgetId);
						}else{
							ghost.parentNode.insertBefore(wel, ghost);
						}
					}else{
						ghost.parentNode.insertBefore(wel, ghost);
					}
//            return;
        }

        wel.style.left = wel.style.top = "0px";
        wel.style.width = "auto";
        ghost.parentNode.removeChild(ghost);
        
        delete Draggable.elGhost;
        var t,tab;
        for (t in user.tabs) {
            tab = user.tabs[t];
            for (i = 0; i < tab.nb_columns; i++) {
                e = tab._getElCol(i);//$("tabCol" + tab.id + "_" + i);
                if (!e)
                    continue;
                w = e.childNodes;
                for (j = 0; j < w.length;++j) {
                   // debugEcho(w[j].id);
                    wid = global.widgets[w[j].widgetId];
                    wid.setValue("position", j);
                    wid.setValue("column", i);
                    //wid.setValue("tab", tab.id);
                }
            }
        }
        dataHandler.saveWidgets(global.widgets, true);
        },

    onStartDrag: function(draggable, event) {
        if (draggable && draggable.element && draggable.element.parentNode) {
			renderer.renderWidgetDrag(draggable);
		}
		else {
			// remove the dragable if the element doesn't exist anymore.
			this._drag.endDrag();
		}
    },

		createBox: function(hidden) {
			renderer.drawWidgetContainer(this,hidden);
		},
    render: function(hidden) {
    	//debugEcho("widget.render:  id="+this.id,this);

		if (((this.settings["_compatibility"]||"web").indexOf("web") != -1 || dataHandler.platform == "iphone") && (!this.settings["_flags"] || (this.settings["_flags"]!="shared" && this.settings["_flags"]!="unshared"))) {
	        if (!this.rendered) {
			this.createBox(hidden);
				
			if (this._isDraggable) {
		            this._drag = new Draggable(this.elWidgetBox, {
		                handle: "draggable",
		                onDrag: this._onDragWidget,
		                onStart: this.onStartDrag,
		                onEnd: this.onEndDrag});
				}
	        this.run();
	        this.rendered = true;
	        }
		}
    },

    run: function() {
        //debugEcho("widget.run: " + this.id + "  widgetClass=" + global.widgetClasses[this.src]);
        var widget = this;
        // If the widget instance is extended, we can launch it directly.
        // Otherwise, we have to extend it from its class.
        if (this.isExtended) {
            this._launch();
        } else {
            // Try to get the widget's class
            var widgetClass = global.widgetClasses[this.src];
            if (widgetClass) {
                this._extend(widgetClass);
                this._launch();
            } else {
                dataHandler.downloadWidget(this);
            }
        }
    },

    select: function() {
        var wel,id = this.id;
        if (App.selectedWidget) {
            App.selectedWidget.unSelect();
        }
        	
        //if widget web is present, go to the tab widget 
        if (this.getValue("_compatibility").indexOf("web")!=-1 && this.getValue("availability").indexOf("web")!=-1) {
		this.show();
		wel = this.elWidgetBox;
        	if(wel){
			wel.addClassName("webWidgetSelection");
		}
        }
        if (this.elMobileWidget){
		this.elMobileWidget.addClassName("mobileWidgetSelection");
	}
	if(this.elWebMobileMenu){
                this.elWebMobileMenu.addClassName("mobileWidgetSelection");
        }
        App.selectedWidget = this;
    },

    unSelect: function() {
        if (App.selectedWidget) {
        	if(App.selectedWidget.elWidgetBox)
			App.selectedWidget.elWidgetBox.removeClassName("webWidgetSelection");
        	if (App.selectedWidget.elMobileWidget){
                	App.selectedWidget.elMobileWidget.removeClassName("mobileWidgetSelection");
                }
                if(App.selectedWidget.elWebMobileMenu){
			App.selectedWidget.elWebMobileMenu.removeClassName("mobileWidgetSelection");
		}
            App.selectedWidget = null;
        }
    },
// switch to the widget's tab
    show: function() {
        user.getTabById(this.prefs.tab.value).show();
    },

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Widget API methods
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	
	// Shows a specific line in the widget form.
    //
    showFormLine: function(prefName) {
		renderer.showFormLine(this, prefName);
    },

	// Hides a specific line in the widget form.
    //
    hideFormLine: function(prefName) {
        renderer.hideFormLine(this, prefName);
    },

	// Block an text area in the widget pref
	blockPref: function(prefName){
		renderer.blockPref(this, prefName);
	},
	
    // Gets the number of un-seen elements within the current widget.
    //
    getUnseen: function() {
        return this.unseen;
    },

    // Sets the number of un-seen elements within the current widget.
    //
    setUnseen: function(nb) {
        var num = parseInt(nb,10);
        if (num != NaN) {
            this.unseen = num;
            renderer.renderUnseenNotification(this);
        }
    },

    // Gets the body element
    //
    getBody: function(id) {
        return $("widget_" + this.id + "_body");
    },

    // Gets a widget in the current environment based on its id
    //
    getWidget: function(id) {
    		// why not return this ????
        return global.widgets[id];
    },

    // Registers a draggable element
    //
    registerDraggable: function(draggable, options) {
        draggable.widgetId = this.id;
        global.draggables[draggable.id] = draggable;
        new Draggable(draggable.id, options);
    },

    // Registers a droppable element
    //
    registerDroppable: function(droppable, options) {
        var widgetId = this.id;
        var handleDrop = function(elem) {
            var draggable = global.draggables[elem.id];
            widget = global.widgets[widgetId];
            widget.onDrop(draggable, droppable);
        };

        Droppables.add(droppable.id, {
            accept: options.accept,
            hoverclass: options.hoverclass,
            onDrop: handleDrop
        });
    },

    // Registers a sortable element for the current widget
    //
    //
    //               STILL NOT TESTED!!!
    //
    //
    registerSortable: function(listName, handleName, type) {
        var sortables = global.sortables[widget.id];
        if (!sortables) {
            sortables = {};
        }
        sortables[listName] = {
            dropOnEmpty: true,
            handle: handleName,
            constraint: false,
            type: type
        };

        // get sortables for this type
        var containment = [];
        var typeSortables = {};
        var j = 0;
        for (var i in sortables) {
            if (sortables[i].type == type) {
                containment[j] = i;
                typeSortables[i] = sortables[i];
                j++;
            }
        }

        // re-register all sortables for this type
        for (var i in typeSortables) {
            Sortable.create(i, {
                dropOnEmpty: typeSortables[i].dropOnEmpty,
                handle: typeSortables[i].handle,
                containment: containment,
                constraint: typeSortables[i].constraint
            });
        }

        global.sortables[widget.id] = sortables;
    },

    // Overrides the onLoad() (called when the widget is launched) method
    // with a specific method.
    //
    setOnLoadFunction: function(method) {
        this.onLoad = method;
        global.widgets[this.id] = this;
    },

    // Gets the date corresponding to the key attribute from the widget's settings.
    //
    getDate: function(key) {
        // var months = {"Jan":0, "Feb":1, "Mar":2, "Apr":3, "May":4, "Jun":5, "Jul":6, "Aug":7, "Sep":8, "Oct":9, "Nov":10, "Dec":11};
        // var dateString = this.getValue(key);
        // var fullDateTime 	= dateString.split(" ");
        // var fullDate 		= fullDateTime[0].split("-");
        // var fullTime 		= fullDateTime[1].split(":");
        // 
        // var date = new Date();
        // date.setFullYear(parseInt(fullDate[2]), months[fullDate[1]], parseInt(fullDate[0]));
        // date.setHours(parseInt(fullTime[0]), parseInt(fullTime[1]), parseInt(fullTime[2]));
        // 
        // return date;

        var dateString = this.getValue(key);
        dateString = dateString.replace(new RegExp("-", 'g'), " ");

        widget.log(dateString);
        return new Date(dateString);
    },

    // Sets a date value in the widget's settings.
    // If the key already exists, the value is just updated, otherwise, a new setting is created.
    // This value can then be retrieved through widget.getValue(key).
    //
    setDate: function(key, date) {
        var get2Digits = function(i) {
            if (i < 10) {
                return "0" + i;
            } else
                return i;
        };
        var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
        var dateString = "";
        dateString += get2Digits(date.getDate()) + "-" + monthNames[date.getMonth()] + "-" + date.getFullYear();
        dateString += " " + get2Digits(date.getHours()) + ":" + get2Digits(date.getMinutes()) + ":" + get2Digits(date.getSeconds());
        this.setValue(key, dateString);
    },

    // Gets the value corresponding to the key attribute from the widget's settings.
    //
    getValue: function(key) {
        //////////////////////////////////////////////
        // Work around to make v1 widgets compatible
        key = key.replace(new RegExp("_?(" + this.id + ")_?", 'g'), "");
        //////////////////////////////////////////////

        if (this.prefs[key]) {
            return this.prefs[key].value;
        } else {
        	// NT: don't have time to explore why this cause trouble on ie 6 (stop js exec without error 
        	// (this case comes  if an access of an inexistant method is called) )
        	// 
            //logger.log("Error in widget.getValue() : Unknown widget setting '" + key + "'.", ERROR);
			return "";
        }
    },

    // Sets a value in the widget's settings.
    // If the key already exists, the value is just updated, otherwise, a new setting is created.
    // This value can then be retrieved through widget.getValue(key).
    //
    setValue: function(key, value, autoSave) {
		
        //////////////////////////////////////////////
        // Work around to make v1 widgets compatible
        key = key.replace(new RegExp("_?(" + this.id + ")_?", 'g'), "");
        //////////////////////////////////////////////

        this.settings[key] = value;
        if (this.prefs[key]) {
            this.prefs[key].setValue(value);
        } else {
            this.prefs[key] = new Pref(key, value, "hidden", "");
            this.prefs[key].changed = true;
        }
        global.widgets[this.id] = this;
        renderer.updateField(this, key, value);
		if (autoSave) {
			this.save(true);
		}
    },
		
		//delete the widget's setting
		deleteValue: function(key) {
			key = key.replace(new RegExp("_?(" + this.id + ")_?", 'g'), "");
			if (this.prefs[key]) {
        this.setValue(key,"!delete");
			}
    },

    // Gets the default widget's title.
    //
    getTitle: function() {
		var title = null;
		
		// Get Shared Title
		if (!title && this.prefs["title_"]) {
			title = this.prefs["title_"].value;
		}
		
		// Look for title from the settings.
		if (this._titleSettingName) {
			if (this.prefs[this._titleSettingName]) {
                title = this.prefs[this._titleSettingName].value;
            }
		}
		
		// if there is no title, look for the dynamic title setting
		// set by the server.
		if (!title && this.prefs["_title"]) {
			title = this.prefs["_title"].value;
		}
		
		// last we try to get the value of the default title.
		if (!title && this.prefs["name"]) {
			title = this.prefs["name"].value;
		}
		if (!title && this.title) {
            title = this.title;
        }
        
		if (!title) {
            return "";
        }
		else {
			return title;
		}
    },

    // Sets the title of the widget.
    // A call to this method triggers the "onUpdateTitle" event.
    // This can be handled by implementing widget.onUpdateTitle() in the widget source.
    //
    setTitle: function(title) {
        renderer.renderWidgetTitle(this, title);
        this.callback("onUpdateTitle");
    },

    // Replaces the html content of the widget with a specific html content.
    // A call to this method triggers the "onUpdateContent" event.
    // This can be handled by implementing widget.onUpdateContent() in the widget source.
    //
    setContent: function(content) {
        this.content = content;
        this._showContent();
        this.callback("onUpdateContent");
    },

    // Adds some html at the bottom of the current html content of the widget.
    // A call to this method triggers the "onUpdateContent" event.
    // This can be handled by implementing widget.onUpdateContent() in the widget source.
    //
    addContent: function(content) {
        this.content += content;
        this._showContent();
        this.callback("onUpdateContent");
    },

    // Sets (in minutes) the refresh interval for a widget
    //
    setRefreshInterval: function(delay) {
        var rndUpdateTime = Math.round(10 * 1000 * Math.random());
        if (this.onRefresh) {
            delay = parseInt(delay) * 1000 * 60;
            // from minutes to milliseconds
            this.setPeriodical('autoRefresh', this.onRefresh, delay + rndUpdateTime);
        }
    },

    // Sets (in milliseconds) the interval used to call a function.
    //
    setPeriodical: function(name, fn, delay, force) {
			if (fn) {
        this.clearPeriodical(name);
        fn = fn.bind(global.widgets[this.id]);

        var widgetId = this.id;
        var handlePeriodical = function() {
            widget = global.widgets[widgetId];
            fn();
        };
        global.periodicals[name + "_" + this.id] = setInterval(handlePeriodical, delay);

        if (force) {
            handlePeriodical();
        }
			}
    },

    // Clears the interval previously set with the specific name.
    //
    clearPeriodical: function(name) {
        if (global.periodicals[name + "_" + this.id]) {
            clearInterval(global.periodicals[name + "_" + this.id]);
        }
    },

    // Sets (in milliseconds) the delay used before calling a function.
    //
    setDelayed: function(name, fn, delay) {
        global.widgets[this.id].clearDelayed(name);
        fn = fn.bind(global.widgets[this.id]);

        var handleDelay = function() {
            widget = global.widgets[widgetId];
            fn();
        };
        global.delays[name + "_" + this.id] = setTimeout(handleDelay, delay);
    },

    // Clears the delayed function previously set with the specific name.
    //
    clearDelayed: function(name) {
        if (global.delays[name + "_" + this.id]) {
            clearTimeout(global.delays[name + "_" + this.id]);
        }
    },

    /////////////////////////////////////////////////////////////////////////////////////////////////////
    // TO BE IMPROVED : 
    // This methods should always generate and handle an interval.
    /////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    clearReloadTime: function() {
        if (global.widgets[this.id].timeout) {
            clearTimeout(global.widgets[this.id].timeout);
        }
    },

    setReloadTime: function(time) {
        if (isNaN(time) || time == undefined) {
            debugEcho("widget.setReloadTime:  Invalid reload value!!!  time=" + time);
            return false;
        }
        this.reloadTime = time;

        this.clearReloadTime();
        var w = this,
        id = this.id;
        this.timeout = setTimeout(function() {
            w.callback("onLoad");
        }, time);
    },

    getReloadTime: function() {
        return global.widgets[this.id].reloadTime;
    },
    /////////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    // Gets the current widget icon source path.
    // 
    getIconSrc: function() {
        if (this.web_icon && this.web_icon.length > 0) {
            return this.web_icon;
        } else {
            if (this.descriptions && this.descriptions.web_icon) {
                return this.descriptions.web_icon;
            } else if(this.descriptions && this.descriptions.icon){
				// case for car
				return this.descriptions.icon;
			}else {
                return DEFAULT_WIDGET_ICON_SRC;
            }
        }
    },

    // Replaces the current widget icon with a new one based on the provided source path.
    // 
    setIcon: function(src) {
        this.web_icon = src;
        renderer.setWidgetIcon(this, src);
    },

    // Replaces the current widget icon with a favicon based on the provided website url.
    // If no favicon has been found, a default icon is displayed.
    // 
    setFavicon: function(url) {
      this.setIcon(WWG_STATIC + "/services/favicon.php?url=" + encodeURIComponent(url));
    },

		setRead: function(channelUrl, itemUrl, callback, cbparm) {
			dataHandler.setRead(channelUrl, itemUrl, callback, cbparm);
		},
		
    /////////////////////////////////////////////////////////////////////////////////////////////////////
    // TO BE IMPROVED : 
    // This method sould be replaced by widget.getElement(elemId).setHtml(html);
    /////////////////////////////////////////////////////////////////////////////////////////////////////
    setElemContent: function(elemId, data) {
        var parent = $("widget_" + this.id + "_body");
        if (!parent) {
            debugEcho("no parent found... this=", this);
            logger.log("Widget.setElemContent: ( id=" + this.id + " - " + this.src + " )no parent found!");
            return;
        }
        var child = findChildById(parent, elemId);
        child.innerHTML = data;
    },

    createElement: function(tagName, elName) {
        return document.createElement(tagName);
    },
    /////////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    // Sends a message to the log handler.
    //
    log: function(message) {
        logger.log(message);
    },

    // Opens a url in a new window.
    //
    openURL: function(url) {
        var newWindow = window.open(url);
    },

    // Registers a function in the callback handler.
    // This function can then be called through widget.callback(name).
    //
    setCallback: function(name, fn) {
        global.callbacks[name + "_" + this.id] = fn;
    },

    // By default this method calls a function on the current object or on the object 
    // defined as the 'bind' parameter:
    //
    // For instance : 
    // 		- Calling widget.calback("myFunction", myArgs) would be the same as calling widget.myFunction(myArgs).
    // 		- Calling widget.calback("myFunction", myArgs, myObject) would be the same as calling myObject.myFunction(myArgs).
    //
    // However if widget.myFunction() doesn't exist, the method will look for a function previously
    // registered with this name in the callback handler and call this function as previously explained.
    //
    //
    // If neither function can be found, this method won't do anything.
    //
    callback: function(name, args, bind) {
        widget = this;

        //debugEcho("widget.callback:   (widget "+this.id+"|"+this.prefs.id.value+" / "+this.prefs.name.value+")     name="+name+", args, bind", args, bind);
        if (args == undefined)
            args = [];

        if (typeof bind == 'undefined') {
            bind = this;
        }
        try {
            if (this[name]) {
                //debugEcho("widget.callback: calling name='"+name+"' \tid= "+this.id);
                this[name].apply(bind, [args]);
            }
            if (global.callbacks[name + "_" + this.id]) {
                global.callbacks[name + "_" + this.id].apply(bind, [args]);
            }
        } catch(e) {
            debugEcho(e);
            logger.log("widget.callback: <b>Internal Widget Error :</b> " + e.name + "\n\n" + e.message + "\n" + e.stack, ERROR);
        }
    },

    // Sends a request and forwards the response through the callback 
    // in an XmlHttpRequest object as the first parameter.
    //
    request: function(url, callback, params, method, authParams) {
        dataHandler.request(this.id, url, callback, params, method, null, authParams);
    },

    // This is to handle v1 compatibility.
    //
    _v1_request: function(url, callback, method, params, authParams) {
        dataHandler.request(this.id, url, callback, params, method, null, authParams);
    },

    // Sends a request and forwards the response through the callback 
    // in a JSON object as the first parameter.
    //
    requestJson: function(url, callback, params, method, authParams) {
        dataHandler.request(this.id, url, callback, params, method, JSON, authParams);
    },

    // Sends a request and forwards the response through the callback 
    // in a XML object as the first parameter.
    //
    requestXml: function(url, callback, params, method, authParams) {
        dataHandler.request(this.id, url, callback, params, method, XML, authParams);
    },

    // Sends a request and forwards the response through the callback 
    // in a string as the first parameter.
    //
    requestText: function(url, callback, params, method, authParams) {
        dataHandler.request(this.id, url, callback, params, method, TEXT, authParams);
    },

    // Sends a request to an RSS feed and forwards the response through the 
	// first parameter of the callback as a JSON object (holding feed's data).
	//
	// The contentType defines how the content of each article is handled.
	// This can be set as : 
	// 			- "original" 
	// or one or more of the following :
	// 			- "small_images, no_image, no_link no_tag"
	//
	// The content type being by default set to "original"
    //
    requestFeed: function(url, callback, lines, contentType, lastSeenId) {
		var requestUrl = encodeURIComponent(url);
		if (lines) {
			requestUrl += "&lines="+lines;
		}
		if (contentType) {
			requestUrl += "&contentType="+contentType;
		}
		if (lastSeenId) {
			requestUrl += "&lastSeenId="+lastSeenId;
		}
		requestUrl=requestUrl.replace(/ /g,"");
		
		//LISTENER SUBSCRIPTION AUTO
		/*
		var subscriptionId=0;
		if(this.getValue("subscriptionId")){
			subscriptionId=this.getValue("subscriptionId");
		}
		var url=document.location+" ";
		var domaine=url.substring(0,url.indexOf("/",7));
		var serviceUrl=domaine+"/services/subscription/rss_subscription.php?url_rss=";
		serviceUrl+=encodeURIComponent(domaine+"/proxy/rss.php?userId="+user.id+"&format=json&url="+requestUrl);
		listener.subscribe(subscriptionId,serviceUrl,callback,"persistant","widget",this.id);*/
		// ----------------------
		 
		//to comment to active RSS auto subscription
		dataHandler.requestFeed(this.id, url, callback, lines, contentType, lastSeenId);
    },

	subscribe: function(serviceUrl,serviceType,callback,subscriptionId){
		if(!subscriptionId){
			var subscriptionId=0;
		}
		listener.subscribe(subscriptionId,serviceUrl,callback,serviceType,"widget",this.id);
	},
	
	messageDelivered: function(message_id){
		listener.updateMessageSate(message_id,"DELIVERED");
	},
	
    // Opens a feed article in a new window and mark it as "seen" for the next
    // thime the widget is loaded.
    //
    readFeedItem: function(position) {
        var channelUrl = this.getValue("url");
        var itemUrl = this.rss.channels[0].items[position].link;
        window.open(itemUrl);
        dataHandler.setRead(channelUrl, itemUrl);
    },
	
	// Displays a widget view.
	// If the specific view, cannot be retrieved in the widget views,
	// this function won't do anything.
	//
	showView: function(viewName, output) {
		if (renderer.showView(this, viewName, output)) {
			this._currentViewName = viewName;
		}
	},
	
	// Get a specific view
	//
	getView: function(viewName){
		if(this._views[viewName]) return this._views[viewName];
	},
	
	// Displays the current status of the widget.
	// By setting the type as "progress", a progression indicator will be shown.
	//
	showStatus: function(message, type) {
		renderer.showStatus(this, message, type);
	},
	
	// Hides the widget's status bar.
	//
	hideStatus: function() {
		renderer.hideStatus(this);
	},
	
	// Shows the settings form
	//
	showForm: function() {
		renderer.showForm(this.id);
	},
	
	// Hides the settings form
	//
	hideForm: function(message, type) {
		renderer.hideForm(this.id);
	},
	
	// Shows a widget output as the widget content
	//
	setLoading: function(message) {
		renderer.setLoading(this, message);
	},
	
    // Saves the current widgets settings.
    // A call to this method triggers the "onSave" event. 
    // This event can be bloacked by setting blockCallback to true.
    // This can be handled by implementing widget.onSave() in the widget source.
    //
    save: function(blockCallback, fullUpdate) {
        widget = this;

        // save to database
        var hash = {};
        hash[this.id] = this;
        dataHandler.saveWidgets(hash, blockCallback, fullUpdate);
    },

    remove: function() {
        if (global.widgets[this.id]) {
			if(listener) {
				listener.removeSubscription("widget",this.id);
			}
			renderer.removeWidget(this.id);
			dataHandler.removeWidget(this.id);
		}
    },

    refresh: function() {		
        this.callback("onRefresh");
    },
	
	share: function() {
		this.setValue("_flags","shared");
		this.save(true,true);
		this.callback("onShare");
    },
	
	unShare: function() {
		this.setValue("_flags","unshared");
		this.save(true,true);
		this.callback("onUnShare");
    },
	
	setTagList: function(tags) {
		this._tags=tags;
		this.save();
		this.callback("onTags");
    },
	
    toXML: function(fullXml) {
        var xml = "";
        for (var i in this.prefs) {
            if ((this.prefs[i].changed || fullXml) && this.prefs[i].key && i != "id" && i != "draggable" && i != "removable" && i!= "list" && (i.substr(0,1)!="_" || i == "_tags"|| i == "_flags")) {
                xml += this.prefs[i].key + "=\"" + String(this.prefs[i].value).encodeXML() + "\" ";
                this.prefs[i].changed = false;
            }
        }
		if(this._flags){
			xml+=" _flags=\"shared\" ";
		}
		
        return xml ? ("<widget id=\"" + this.id + "\" " + xml + "/>\n") : "";
    }
	
};

// Instanciates a new widget for the current user.
// This will add the widget at the top of the current tab.
// isDemo not usable for now...
Widget.add = function(src, prefs, isDemo) {
    dataHandler.addWidget(src, prefs, isDemo);
};

//Share a widget , set settings _flags at "shared" 
Widget.share = function(widgetId) {
	var widgetToShare = global.widgets[widgetId];
    if (widgetToShare && widgetToShare.id) {
		widgetToShare.share();
    } else {
        logger.log("Could not Share Widget " + widgetId + ".\nThe widget may not have been initialized correctly.", ERROR);
    }	
};

Widget.unShare = function(widgetId) {
	var widgetToUnShare = global.widgets[widgetId];
    if (widgetToUnShare && widgetToUnShare.id) {
		widgetToUnShare.unShare();
    } else {
        logger.log("Could not unShare Widget " + widgetId + ".\nThe widget may not have been initialized correctly.", ERROR);
    }	
};

Widget.PopupShareSettings = function(widgetId) {
	var width 	= "450";
	var height 	= "600";
	var title 	= "Settings";
	var content = "";
	popupWindowRss(widgetId, title, content, width, height);
};

//Tag a widget with search tags 
Widget.setTagList = function(widgetId,tags) {
	var widgetToTag = global.widgets[widgetId];
    if (widgetToTag && widgetToTag.id) {
		widgetToTag.setTagList(tags);
    } else {
        logger.log("Could not Tag this Widget " + widgetId + ".\nThe widget may not have been initialized correctly.", ERROR);
    }	
};

Widget.run = function(widgetId) {
    var widgetToRun = global.widgets[widgetId];
	var widget = widgetToRun;
    if (widgetToRun && widgetToRun.id && widgetToRun.src) {

        // If the widget instance is extended, we can launch it directlly.
        // Otherwise, we have to extend it from its class.
        if (widgetToRun.isExtended) {
            widgetToRun._launch();
        } else {
            // Try to get the widget's class
            var widgetClass = global.widgetClasses[widgetToRun.src];
            if (widgetClass) {
                widgetToRun._extend(widgetClass);
                widgetToRun._launch();
            } else {
                dataHandler.downloadWidget(widgetToRun);
            }
        }
    } else {
        logger.log("Could not run Widget " + widgetId + ".\nThe widget may not have been initialized correctly.", ERROR);
    }	
};

Widget.refresh = function(widgetId) {
	debugEcho("static Widget.refresh:");
    var widgetToRefresh = global.widgets[widgetId];
    if (widgetToRefresh && widgetToRefresh.id) {
		widgetToRefresh.refresh();
    } else {
        logger.log("Could not refresh Widget " + widgetId + ".\nThe widget may not have been initialized correctly.", ERROR);
    }
};

// This function will update preferences set in the widget prefs
// form, from the widget represented by the specified id.
// This will not save them to the database.
//
Widget.update = function(widgetId, lastUpdated, blockCallback) {
	debugEcho("static Widget.update: widgetId="+widgetId);
    var widgetToUpdate = global.widgets[widgetId];
    widget = widgetToUpdate;
    if (widgetToUpdate && widgetToUpdate.id) {

        // get form elems
        var formElem = $("prefs_form_" + widget.id);
        var formElems = formElem.getElements();

        formElems.each(function(elem) {
            var value = elem.value;
            if (elem.type == "checkbox") {
                if (elem.checked) {
                    value = "1";
                } else {
                    value = "0";
                }
            }
            if (elem.name && elem.name != "") {
                widgetToUpdate.setValue(elem.name, value);
            } else {
                // Used for the form compatibility with v1 widgets
                widgetToUpdate.setValue(elem.id, value);
            }

        });
        if (!blockCallback) {
            widgetToUpdate.callback("onUpdate");
			widgetToUpdate.callback("onChange", lastUpdated);
        }
    } else {
        logger.log("Could not update Widget " + widgetId + ".\nThe widget may not have been initialized correctly.", ERROR);
    }
};

// This function will save preferences set in the widget prefs
// form, from the widget represented by the specified id.
//
Widget.save = function(widgetId) {
	debugEcho("static Widget.save :");
    var widgetToSave = global.widgets[widgetId];
    widget = widgetToSave;
    if (widgetToSave && widgetToSave.id) {

        Widget.update(widgetId, null, true);
        renderer.hideForm(widgetId);
        widgetToSave.save();
    } else {
        logger.log("Could not save Widget " + widgetId + ".\nThe widget may not have been initialized correctly.", ERROR);
    }
};

Widget.removeRequest = function(widgetId) {
	var widgetToRemove = global.widgets[widgetId];
    if (confirm(_("Are you sure you want to remove this widget?"))) {
        widgetToRemove.remove();
		return true;
    }
	else {
		return false;
	}
	
};

Widget.toggle = function(widgetId) {
	var widgetToToggle = global.widgets[widgetId];
	var reduced = renderer.toggleWidget(widgetToToggle);
	widgetToToggle.setValue("widget_reduced", reduced);
	widgetToToggle.save(true);
};

Widget.addSubscription = function(widgetId, proxyUrl, callback, dataStock, type){
	subscriptions.addSubscription(widgetId, proxyUrl, callback, dataStock, type);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Pref Object
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// represents a Widget's preference field
// 
var Pref = Class.create();
Pref.prototype = {

    // constructor
    initialize: function(key, value, type, label, elems, output) {
        this.key = key;
        this.value = value;
        this.type = type;
        this.label = label;
		this.output = output;
        this.changed = false;
        if (elems) {
            this.elems = elems;
        } else {
            this.elems = {};
        }
    },

    // methods
    setValue: function(value) {
        if (this.value != value) {
        //debugEcho("changed=== this.value="+this.value+"  value="+value);
            this.changed = true;
        }
        return this.value = value;
    },

    getValue: function() {
        return this.value;
    },

    toXMLAttributes: function() {
        if (this.changed) {
            return this.key + "=\"" + String(this.value).escapeHTML() + "\" ";
        }
        return "";
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// View Object
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// represents a Widget's preference field
// 
var View = Class.create();
View.prototype = {

    // constructor
    initialize: function(name, data, type) {
        this.name = name;
        this.data = data;
        this.type = type;
    }
};

// Constants
View.DEFAULT_NAME 	= "_default";
View.LOADING_NAME 	= "_loading";

View.STATIC_TYPE 	= "static";
View.DYNAMIC_TYPE 	= "dynamic";
View.DEFAULT_TYPE 	= "";

// Static Methods
View.getDefaultViews = function() {
	var views = {};
	
	// Loading view
	views[View.LOADING_NAME] = new View(View.LOADING_NAME, "", View.DEFAULT_TYPE);
	
	return views;
};
