/* Caching based on:
Copyright (c) 2007 Monsur Hossain (http://www.monsur.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/

console.debug("Loading grapevine.js");

var dojo, dijit, maxDay, minDay,demographicPopup; //so JSlint stops complaining about globals

var cache = {
    items: {},
    count : 0,
    maxSize: -1,
    fillFactor: 0.75,
    purgeSize: Math.round(this.maxSize * this.fillFactor),
    stats : {hits: 0, misses: 0},

    init : function (size) {
        this.maxSize = size;
        this.purgeSize = Math.round(this.maxSize * this.fillFactor);
    },
    
    get: function(key) {
        // retrieve the item from the cache
        var item = this.items[key], returnVal = null;
        
        if ((item !== undefined) && (item !== null)) {
            // update its last accessed date
            item.lastAccessed = new Date().getTime();
            returnVal = item.value;
            this.stats.hits = this.stats.hits+1;
        } else {
            this.stats.misses = this.stats.misses +1;
        }
        return returnVal;
    },
    put: function(k,v){
    
        if ((k === undefined ) ||(k ===  null) || (k === '')){
            throw new Error("key cannot be null or empty");
        }
        var obj = {key:k,value:v,lastAccessed: new Date().getTime()};

       
        // add a new cache item to the cache
        if ((this.items[k]!==undefined) && (this.items[k] !== null)){
            delete this.items[k];
        } else {
            this.count = this.count+1;
        }
        this.items[k] = obj;
    
        // if the cache is full, purge it
        if ((this.maxSize > 0) && (this.count > this.maxSize)) {
            this.purge();
        }
    },
    purge : function() {
    
        var tmparray = [],key;
        
        for (key in this.items) {
            if(this.items.hasOwnProperty(key)){
                tmparray.push(this.items[key]);
            }
        }
        
        if (tmparray.length > this.purgeSize) {
    
            // sort this array based on last accessed date
            tmparray = tmparray.sort(function(a, b) { 
                return b.lastAccessed - a.lastAccessed;
            });
            
            // remove items from the end of the array
            while (tmparray.length > this.purgeSize) {
                delete this.items[tmparray.pop().ritem.key];
                this.count = this.count -1;
            }
        }
    },
    
    toHtmlString : function() {
        var returnStr = this.count + " item(s) in cache<br /><ul>", key, item;
        for (key in this.items) {
            if(this.items.hasOwnProperty(key)){
                item = this.items[key];
                returnStr = returnStr + "<li>" + item.key.toString() + " = " + item.value.toString() + "</li>";
            }
        }
        returnStr = returnStr + "</ul>";
        return returnStr;
    }
};


var util = {
    unescapeHTML : function (html) {
        //TODO: test in other browsers. Used inline in entityAutoComplete js
        var htmlNode = document.createElement("div");
        htmlNode.innerHTML = html;
        if(htmlNode.innerText !== undefined){
            return htmlNode.innerText; // IE
        }
        return htmlNode.textContent; // FF
    },
    addExtra : function(collection, toAdd){
        var x, i;
        o: for(x=0; x< toAdd.length; x=x+1){
            for(i = 0; i < collection.length; i=i+1){
                if(toAdd[x]===collection[i]){
                    continue o;
                }
            }
            collection.push(toAdd[x]);
        }
    },
    adjustEnts : function(entities,toAdd,toRemove){
        var comps = entities.split(","),i, ent;
        if(toRemove !== undefined){
            if((""+toRemove).substring(0,4) === "upto"){
                toRemove = parseInt((""+toRemove).substring(4),10)-(toAdd=== undefined? 0 : 1);
                if((toRemove !== undefined)&&(toRemove < comps.length)){
                    //console.debug("Invocation: splice("+toRemove+","+(comps.length - toRemove));
                    comps.splice(toRemove,comps.length - toRemove);
                    //console.debug("Removed from "+entities + "+" + toAdd + ", result is "+comps );
                    
                }
                toRemove = undefined;
            } else {
                toRemove = parseInt(toRemove,10);
            }
        }
        if(toAdd !== undefined){
            toAdd = parseInt(toAdd,10);
        }
        entities = "";
        for(i in comps){
            if(comps.hasOwnProperty(i)){
                ent = parseInt(comps[i],10);
                if(toAdd === ent){
                    toAdd = undefined;
                }
                if(toRemove === ent){
                    toRemove = undefined;
                } else if(entities.length === 0){
                    entities = ent;
                } else {
                    entities = entities + ","+ent;
                }
            }
        }
        if( toAdd !==undefined){
            if(entities.length === 0){
                entities = toAdd;
            } else {
                entities = entities + ","+toAdd;
            }
        }
        return entities;
    },
    hashToString : function(hash){
        var a = [], b, result, i;
        for( b in hash){
            if(hash.hasOwnProperty(b)){
                a.push(encodeURIComponent(b)+"="+encodeURIComponent(hash[b]));
            }
        }
        a= a.sort();
        result ="";
        for(i in a){
            if(a.hasOwnProperty(i)){
                result = result + "&"+a[i];
            }
        }
        if(a.length > 0){
            return result.substring(1);
        }
        return "";
    },
    alert : function(message){
        //TODO
        console.log(message);
    },
    makeLoad : function(nodeName){
        if(nodeName === undefined){
            return;
        }
        var node = dojo.byId(nodeName),
        loading = "<img src=\"images/loading.gif\" title=\"Loading...\" style=\"height:20px; width: 20px; float: right;\" />";

        if(node.innerHTML.search(loading)===-1){
            node.innerHTML= loading+node.innerHTML;
        }
    },
    
    nonces : {},
    charset : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",
    nonce : function() {
        while (true){
            var string_length = 8, randomstring = '',i, rnum;
            for (i=0; i<string_length; i=i+1) {
                rnum = Math.floor(Math.random() * (util.charset.length-1));
                randomstring += util.charset.substring(rnum,rnum+1);
            }
            if(util.nonces[randomstring]===undefined){
                util.nonces[randomstring]= true;
                return randomstring;
            }
        }
    },
    trimString : function(str, maxLen, hoverFull){
        if((str === undefined)||(str === null)){
            return "";
        }
        if(maxLen === undefined){
            maxLen = 30;
        }
        if(str.length > maxLen){
            if(hoverFull === true){
                hoverFull = "<a href=\"#\" onclick=\"return false;\" title=\""+str+"\">...</a>";
            } else {
                hoverFull = "...";
            }
            str = str.substring(0,maxLen -3);
            //beware trailing escape characters
            var slash = "\u005c";//this is a \ - jslint chokes on it otherwise
            if((str.charAt(str.length-1) === slash ) &&
                    (str.length > 1) &&
                    (str.charAt(str.length - 2) !== slash)) {
                str = str.substring(0,str.length-1);
            }
            str = str + hoverFull;
        }
        return str;
    },
    monthStrings : ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
    dayIdOffset : 0,
    dayIdToDate : function(dayId){
        dayId = parseInt(dayId,10);
        var d=new Date();
        d.setTime((dayId+this.dayIdOffset)*86400000);
        d.setTime(d.getTime()+d.getTimezoneOffset()*60000);
        return d;
    },
    dateToDateString : function(date){
        return date.getDate()+" "+this.monthStrings[date.getMonth()]+"'" +(""+date.getFullYear()).substring(2);
    },
    dayIdToDateString : function(dayId){
        return this.dateToDateString(this.dayIdToDate(dayId));
    },
    dateToDayId : function(date){
        var firstGuess = (date.getTime()-(date.getTimezoneOffset()*60000))/86400000 - this.dayIdOffset,
        i, dateStr = this.dateToDateString(date);
        for(i=0;i<30;i=i+1){
            if(this.dayIdToDateString(firstGuess+i)=== dateStr){
                return firstGuess+i;
            }
            if(this.dayIdToDateString(firstGuess-i)=== dateStr){
                return firstGuess-i;
            }
        }
        console.error("Could not convert date to dayid");
        return null;
    },
    init : function(){
        var dayId = 14000, date = "30 Apr'08",i;
        if(this.dayIdToDateString(dayId)!==date){
            for(i=0;i<30;i=i+1){
                this.dayIdOffset=i;
                if(this.dayIdToDateString(dayId)===date){
                    return;
                }
                this.dayIdOffset=-i;
                if(this.dayIdToDateString(dayId)===date){
                    return;
                }
            }
            console.error("Could not calibrate dayIdToDateString!");
            this.dayIdOffset=0;
        }
    },
    formatImage : function(image,width,height){
        if(width === undefined){
            width = "32";
        }
        if(height === undefined){
            height = "32";
        }
        if(image === undefined){
            return "images/noimage"+width+"_"+height+".png";
        }
        return "http://www.freebase.com/api/trans/image_thumb" + 
            image +"?maxwidth=" + width + "&maxheight=" + height;
    }
};

util.init();

var queryParams = {
        lastQuery : {    
            country: "Any",
            state: "Any",
            city: "Any",
            industry: "Any",
            age: "Any",
            gender: "Any",
            period:0,
            entities : "",
            moreEnts : "",
            category: "Thing",
            topic : ""
        },
        subscribers : [], //an array of callback functions, for when values change
                        //callback functions are of the form callback(attributesChanged,queryParameters)
                        //where attributesChanged is a hash of all values that changed, and queryParameters
                        //is a hash of all values
        subscribe : function(callback){
            this.subscribers.push(callback);
        },
        subscribeFirst : function(callback){
            this.subscribers.splice(0,0,callback);
        },
        initAllListeners : function(){
            for(var callback in this.subscribers){
                if(this.subscribers.hasOwnProperty(callback)){
                    this.subscribers[callback](this.lastQuery,this.lastQuery);
                }
            }
            
        },
        setValues: function(arg){
            console.debug("Setting values");
            console.debug(arg);
            var attrsChanged = {} , what, s;
            for(what in arg){
                if(arg.hasOwnProperty(what)){
                    if((arg[what] !==undefined)&&(arg[what] !== this.lastQuery[what])){
                        this.lastQuery[what] = arg[what];
        
                        //update location information, if applicable
                        //if(what === "country"){
                        //} else if (what === "state"){
                        //}

                        attrsChanged[what]=arg[what];
                    }
                }
            }
            console.debug("Attributes changed: ");
            console.debug(attrsChanged);
            for(s in this.subscribers){
                if(this.subscribers.hasOwnProperty(s)){
                    this.subscribers[s](attrsChanged,this.lastQuery);
                }
            }
        }
};

var dispatcher = {
        doQuery: function(qParams,callbackFn){
            if((qParams === undefined)||(qParams.type === undefined)){
                console.debug("Cannot run query, nothing provided");
                console.debug(qParams);
            }
            var query = {inGrapevine : true},
                type = qParams.type,
                attr,
                key,
                cached;
            for(attr in qParams){
                if(qParams.hasOwnProperty(attr)){
                    query[attr] = qParams[attr];
                }
            }
            
            //check if object in cache
            key = util.hashToString(query);
            cached = cache.get(key);
            if(cached !== null){
                console.debug("Using cached result for query");
                console.debug(query);
                callbackFn(cached);
            } else {
                console.debug("Running query");
                console.debug(query);
                dojo.xhrPost({
                    url: "q",
                    handleAs: "json-comment-filtered",
                    content :query,
                    handle: function(response){
                        if(response instanceof Error){
                            util.alert("Server communication error");
                            console.debug(response);
                        } else {
                            console.debug("Got response for "+type);
                            cache.put(key,response);
                            callbackFn(response);
                        }
                    }
                });
            }
    }        
};

var titlelimits = {
    maxPostTitle: 90,
    maxSnippet: 40,
    maxPageTitle: 80,
    maxPageTitleExplore: 50,
    maxPageEntities: 100,
    maxEntityName: 20,
    maxEntityNameExplore: 40,
    maxVideoTitle: 23
};

var standardWidgets = {};

function BaseWidget(divId, queryType, conditionFn, drawingCallback){

    //drawingCallback is function(response, self), where
    //self is the widget object, and
    //response is {results : [result1,result2,...]}
    //    and result_i can have attributes: name, hover, demographics,
    //  url, snippet and entities, moreEnts (the last two only if it's an
    //  entrypoint widget)
    

    //register widget
    this.nonce = util.nonce();
    standardWidgets[this.nonce] = this;

    this.extraParams = {};

    this.overrideParams = {};

    this.currentPage = 0;
    this.resultsPerPage = 5;
    
    this.compactFormat = true;
    
    this.showDemographic = true;
    this.demographicLabel = "Most engaged demographic:";
    
    var self = this;

    this.drawCallback = function(response){
        drawingCallback(response, self);
    };

    this.page = function(increment){
        var oldPage = this.currentPage, newPage = this.currentPage+ increment, pars;
        this.currentPage = newPage;
        pars = this.copyParams(queryParams.lastQuery);
        this.currentPage = oldPage;
        
        dispatcher.doQuery(pars, function(response){
            self.currentPage = newPage;
            self.drawCallback(response);
        });
    };
    
    this.copyParams = function(params){
        var copy = {}, i;
        for(i in params){
            if(conditionFn(i)){
                copy[i]=params[i];
            }
        }
        for(i in this.overrideParams){
            if(this.overrideParams.hasOwnProperty(i)){
                copy[i]= this.overrideParams[i];
            }
        }

        util.makeLoad(divId);
        copy.type = queryType;
        copy[queryType+"_k"]=self.resultsPerPage;
        copy[queryType+"_startResult"] = self.currentPage*self.resultsPerPage;
        for(i in self.extraParams){
            if(self.extraParams.hasOwnProperty(i)){
                copy[queryType+"_"+i] =self.extraParams[i];
            }
        }
        return copy;
    };
    
    queryParams.subscribe(function(diffs, params){
        var j;
        if(conditionFn()){
            dispatcher.doQuery(self.copyParams(params),self.drawCallback);
            return;
        }
        for(j in diffs){
            if(conditionFn(j)){
                dispatcher.doQuery(self.copyParams(params),self.drawCallback);
                break;
            }
        }
    });
}

var newStandardWidget = function(divId, queryType, conditionFn){
    return new BaseWidget(divId,queryType,conditionFn, function(response, self){
        var result = "", results, i, curr, c,d, describe,n;
        
        if(self.currentPage > 0){
            result = "<div class=\"nav_button_area\"><a href=\"#\" onclick=\"standardWidgets['"+self.nonce+"'].page(-1); return false;\">" +
                    "<img src=\"images/uparrow.png\" height=\"12px;\" width=\"12px\" />" +
                    "&nbsp;&nbsp;&nbsp;&nbsp;prev&nbsp;&nbsp;&nbsp;&nbsp;" +
                    "<img src=\"images/uparrow.png\" height=\"12px;\" width=\"12px\" /></a></div>";
        }
        
        if((response !== undefined )&&(response.results !==undefined) && (response.results.length > 0)){
            results = response.results;
            if(response.current !==undefined){
                n = self.nonce+"-tooltip";
                result = result + "<div class=\"comments\">Content unavailable for selected period, showing current. (<span id=\""+n+"\">what's this?</span>)</div><br />";
            }
            for(i in results){
                if(results.hasOwnProperty(i)){
                    curr = results[i];
                    if(curr.name === undefined){
                        curr.name = "";
                    }
                    if(self.compactFormat){
                        result= result+"<div class=\"res_g_cont_frame\"";
                    } else {
                        result= result +"<div class=\"res_g_cont_frame\"";
                    }
                    if(curr.snippet === undefined){
                        result = result + "style=\"margin: 0px;\" ";
                    }
                    result = result + " onmouseover=\"changeClass(this, 'res_g_cont_frame_active')\""+
                        " onmouseout=\"changeClass(this, 'res_g_cont_frame')\">";
                    if(self.compactFormat){
                        result = result + "<div class=\"res_g_cont_frame_extra\">";
                    } else {
                        result = result + "<div class=\"result_box\">"+
                            "<div class=\"res_g_cont_fr_active_top_right_ic\">";
                    }
                    result= result+ "<div class=\"res_g_cont_frame_title\"><a href=\"";
                    if((curr.entities === undefined) &&(curr.url !== undefined)){
                        result = result + curr.url+"\" target=\"_blank";
                    } else {
                        result = result + "#";
                    }
                    result = result + "\" title=\"";
                    if(curr.name.length > titlelimits.maxPostTitle){
                        result = result + curr.name.replace(/"/g,"'");
                        if(curr.hover !==undefined){
                            result = result +" : "+ curr.hover;
                        }
                    } else if(curr.hover !==undefined){
                        result = result + curr.hover;
                    }
    
                    result = result + "\" onclick=\"";
                    if(curr.entities !== undefined){
                        result = result + "queryParams.setValues({entities: "+
                                curr.entities;
                        if(curr.moreEnts !==undefined){
                            result = result + ", moreEnts: "+curr.moreEnts;
                        }
                        if(curr.url !== undefined){
                            result = result + ", showUrl: '"+curr.url.replace(/'/g, "\\u0027").replace(/"/g,"\\u0022") +"'";
                        }
                        result = result + ", showTitle: '" + curr.name.replace(/'/g, "\\u0027").replace(/"/g,"\\u022") +"'}); ";

                        result = result +"return false; ";

                    } else if(curr.url === undefined){
                         result = result +"return false; ";
                    }

                    result = result + "\">"+ util.trimString(curr.name,titlelimits.maxPostTitle).replace(/"/g,"&#34;")+"</a></div>";
    
                    if(!self.compactFormat){
                        result = result + "</div>";
                    }
                    
                    if((self.showDemographic === true ) && (curr.demographics !== undefined)){
                        result = result + "<div class=\"demo_icons\">";
                        describe = self.demographicLabel+" ";
                        for(d in curr.demographics){
                            if(curr.demographics.hasOwnProperty(d)){
                                c = curr.demographics[d];
                                if( d > 0 ){
                                    describe = describe + ", ";
                                }
                                describe = describe + c.description;
                            }
                        }
                        for(d in curr.demographics){
                            if(curr.demographics.hasOwnProperty(d)){
                                c = curr.demographics[d];
                                if( d > 0 ){
                                    result = result + "&nbsp;";
                                }
                                result = result + "<img src=\"images/"+c.url+"\" title=\""+describe+"\"/>";
                            }
                        }
                        result = result + "</div>";
                    }
                    
                    if(curr.snippet !== undefined){
                        result = result + "<div class=\"res_g_cont_frame_active_general_text_box";
                        if(self.compactFormat){
                            result = result + " res_g_cont_frame_active_general_text_box_extra\">";
                        } else {
                            result = result + "\">";
                        }
                        result = result + util.trimString(curr.snippet,titlelimits.maxSnippet,true) + "</div>";
                    }
                
                    result = result + "</div></div>";
                }
            }
            
            if(results.length === self.resultsPerPage){
                result = result+ "<div class=\"nav_button_area\"><a href=\"#\" onclick=\"standardWidgets['"+self.nonce+"'].page(1); return false;\">" +
                "<img src=\"images/downarrow.png\" height=\"12px;\" width=\"12px\" />" +
                "&nbsp;&nbsp;&nbsp;&nbsp;more&nbsp;&nbsp;&nbsp;&nbsp;" +
                "<img src=\"images/downarrow.png\" height=\"12px;\" width=\"12px\" /></a></div>";
            }
        } else {
            if(self.currentPage > 0){
                result = result + "No further results";
            } else {
                result = result + "No results found";
            }
        }
        dojo.byId(divId).innerHTML = result;
        if(n!==undefined){
            n = new dijit.Tooltip({label:"Due to API limitations, we could not retrieve content for the period you are examining; current content is displayed instead.", connectId:[n]});
        }
    });
};

var popularityUtils = {
    colors : ["004586","ff420e","579d1c","7e0021","0084d1","c5000b"],
    baseUrl : "http://chart.apis.google.com/chart?cht=ls&chs=30x10&chd=s:A0F9&chls=2,1,0&chco="
};

var newEntityWidget = function(divId){
    var newWidget = new BaseWidget(divId,"storyCloud",
            function(attrName){
                return (attrName !== "topic");
            }, function(response, self){
        var results,currEntsIndex,currEnts,i, ent,
            selectedExtra, unselectedCore,
            manyEnts,rendered;

        if((response !== undefined )&&(response.results !==undefined) && (response.results.length > 0)){
            results = response.results;

            currEnts = (""+queryParams.lastQuery.entities).split(",");

            currEntsIndex = {};
            for(i in currEnts){
                if(currEnts.hasOwnProperty(i)){
                    ent = parseInt(currEnts[i],10);
                    currEntsIndex[ent]= true;
                    if(self.actualEntIndex[ent] === undefined){
                        selectedExtra = ent;
                        break;
                    }
                }
            }

            for(ent in self.actualEntIndex){
                if(self.actualEntIndex.hasOwnProperty(ent)){
                    if(currEntsIndex[ent] === undefined){
                        unselectedCore = parseInt(ent,10);
                        break;
                    }
                }
            }

            manyEnts = (self.overrideParams.entities.split(",").length>1)&&(currEnts.length > 1);

            rendered = self.drawMain(self,results,selectedExtra,unselectedCore, manyEnts);
             
            dojo.byId(divId).innerHTML = rendered[0];
            dojo.byId(divId+"_entities_node").innerHTML = util.trimString(rendered[1],titlelimits.maxPageEntities,true);
            if((response.title !== undefined) && 
                    ( (dojo.byId(divId+"_title_node").innerHTML.length === 0) || (!self.honorOldTitle)) ){
                if (response.titleUrl === undefined){
                    dojo.byId(divId+"_title_node").innerHTML =
                        util.trimString(response.title,titlelimits.maxPageTitle, true);
                } else if(response.title.length > titlelimits.maxPageTitle){
                    dojo.byId(divId+"_title_node").innerHTML ="<a class=\"titlelink\" href=\""+
                        response.titleUrl.replace(/'/g, "\\'").replace(/"/g,"\\u0022")+"\" target=\"_blank\" title=\""+
                        response.title+ "\">"+util.trimString(response.title,titlelimits.maxPageTitle) +"</a>";
                } else {
                    dojo.byId(divId+"_title_node").innerHTML = "<a class=\"titlelink\" href=\""+
                        response.titleUrl.replace(/'/g, "\\u0027").replace(/"/g,"\\u0022")+"\" target=\"_blank\">"+
                        response.title+"</a>";
                }
            }
            
        } else {
            dojo.byId(divId).innerHTML = "";
            util.alert("Server communication error - no key players found in story.");
        }
    });
    
    newWidget.imageSize = 32;
    newWidget.honorOldTitle = true;

    newWidget.rebuildEntIndex = function(){
        var i, tmp;
        newWidget.overrideParams.entities = queryParams.lastQuery.entities;
        newWidget.overrideParams.moreEnts = queryParams.lastQuery.moreEnts;
        newWidget.actualEntIndex = {};
        tmp = (""+queryParams.lastQuery.entities).split(",");
        for( i in tmp){
            if(tmp.hasOwnProperty(i)){
                newWidget.actualEntIndex[parseInt(tmp[i],10)]= true;
            }
        }
    };
    
    newWidget.drawMain = function(self,results, selectedExtra, unselectedCore, manyEnts){
        var result = "<div class=\"left_entity_separator\">&nbsp;</div>", i, ent,
        extraName, currEnt, currSelected, prevSelected, name,
        storyEntities="";

        prevSelected = false;
        
        result = result + "<div class=\"left_ents_comments_container\">"+
            "<div class=\"contentHeading\">Key entities involved</div>";
        if(results[0].length > 1){
            result = result + "<div class=\"comments\">You can unselect/select a key entity to broaden/refine the focus of the displayed content.</div>";
        }
        result = result + "</div>";
        
        //draw main cluster. Take unselectedCore into account
        for(ent in results[0]){
            if(results[0].hasOwnProperty(ent)){
                currEnt = results[0][ent];
                currSelected = (currEnt.id !== unselectedCore);
                
                if(currSelected && (!prevSelected)){
                    result = result + "<div class=\"first_selected_entity\"><div class=\"first_selected_entity_corner\">&nbsp;</div></div>";
                }
                
                if(prevSelected && (!currSelected)){
                    result = result + "<div class=\"last_selected_entity\"><div class=\"last_selected_entity_corner\">&nbsp;</div></div><br />";
                }
                
                result = result + "<div class=\"left_entity";
                if(currSelected){
                    result = result + " selected_entity";
                }
                result = result + "\"><div class=\"u_entity\" onmouseout=\"changeClass(this,'u_entity')\" onmouseover=\"changeClass(this,'h_entity')\">"+
                    "<a href=\"#\" onclick=\"";
                
                if(currSelected){
                    if(manyEnts){
                        //new ents are: self.overrideParams.entities - currEnt
                        result = result + "queryParams.setValues({entities: '"+
                            util.adjustEnts(self.overrideParams.entities,undefined,currEnt.id)+
                            "' }); return false;";
                    } else {
                        result = result + "return false;";
                    }
                } else {
                    //new ents are: self.overrideParams.entities
                    result = result + "queryParams.setValues({entities: '"+
                        self.overrideParams.entities+
                        "' }); return false;";
                }
                
                result = result + "\" title=\"";

                name = currEnt.name.replace(/'/g, "\\'").replace(/"/g,"\\\"");
                
                if(storyEntities.length > 0 ){
                    storyEntities = storyEntities + ", ";
                } else {
                    storyEntities = "about ";
                }
                storyEntities = storyEntities + name;

                if(currSelected){
                    result = result + "Broaden content; ignore "+name;
                } else {
                    result = result + "Refine content; include "+name;
                    extraName = name;
                }
                
                result = result +"\"><img src=\"" + util.formatImage(currEnt.image,self.imageSize,self.imageSize) + "\" class=\"entityIcon\" />&nbsp;"+
                    util.trimString(name,titlelimits.maxEntityName)+"</a>";
                
                if((!currSelected)&& (selectedExtra === undefined)){
                    result = result + "<a href=\"#\" style=\"margin-left:40px;\" onclick=\""+

                    "standardWidgets['"+self.nonce+"'].rebuildEntIndex(); " +
                    "dojo.byId('"+divId+"_title_node').innerHTML='';"+
                    "dispatcher.doQuery(standardWidgets['"+self.nonce+"'].copyParams(queryParams.lastQuery),standardWidgets['"+self.nonce+"'].drawCallback); " +
                    "return false;"+
                    
                    "\" title=\"Only explore discussions NOT involving "+ extraName+
                    "\"><img src=\"images/zoommin.png\" class=\"entityIcon\" /></a>";
                }
                
                result = result + "</div></div><br />";

                prevSelected = currSelected;
            }
        }
        
        if(prevSelected){
            result = result + "<div class=\"last_selected_entity\"><div class=\"last_selected_entity_corner\">&nbsp;</div></div><br />";
        }
        
        result = result +
        "<div class=\"left_entity_separator\">&nbsp;</div>"+
        "<div class=\"left_ents_comments_container\"><div class=\"contentHeading\">Also involving</div><div class=\"comments\">You can select/unselect a related entity to refine/broaden the focus of the displayed content.</div></div>"+
        "<div class=\"left_entity_separator\">&nbsp;</div>";

        for(i in results){
            if(i > 0){
                
                if( i > 1){
                    result = result + "<div class=\"left_entity_separator\"><hr class=\"left_entity_hr\" /></div>";
                }
                
                //draw additional clusters. Take selectedExtra into account
                for(ent in results[i]){
                    if(results[i].hasOwnProperty(ent)){    
                        currEnt = results[i][ent];
                        currSelected = (currEnt.id === selectedExtra);
                        if(currSelected){
                            result = result + "<div class=\"first_selected_entity\"><div class=\"first_selected_entity_corner\">&nbsp;</div></div>"; 
                        }
                        result = result + "<div class=\"left_entity";
                        if(currSelected){
                            result = result + " selected_entity";
                        }
                        result = result + "\"><div class=\"u_entity\" onmouseout=\"changeClass(this,'u_entity')\" onmouseover=\"changeClass(this,'h_entity')\">"+
                            "<a href=\"#\" onclick=\"";
                        
                        if(currSelected){
                            //new ents are self.overrideParams.entities - unselectedCore (if applicable) 
                            result = result + "queryParams.setValues({entities: '"+
                                util.adjustEnts(self.overrideParams.entities,undefined, unselectedCore)+
                                "' }); return false;";
                        } else {
                            //new ents are self.overrideParams.entities +currEnt (up to 4 entities)
                            result = result + "queryParams.setValues({entities: '"+
                                util.adjustEnts(self.overrideParams.entities,currEnt.id, "upto4")+
                                "' }); return false;";
                        }
                    
                        
                        result = result + "\" title=\"";
                        
                        name = currEnt.name.replace(/'/g, "\\'").replace(/"/g,"\\\"");

                        if(currSelected){
                            result = result + "Broaden content; ignore "+name;
                            extraName = name;
                        } else {
                            result = result + "Refine content; include "+name;
                        }
                        
                        result = result+"\"><img src=\"" + util.formatImage(currEnt.image,self.imageSize,self.imageSize) + "\" class=\"entityIcon\" />&nbsp;"+
                            util.trimString(name,titlelimits.maxEntityName)+"</a>";
                        
                        if(currSelected){
                            result = result + "<a href=\"#\" style=\"margin-left:40px;\" onclick=\""+
                            
                            "standardWidgets['"+self.nonce+"'].rebuildEntIndex(); " +
                            "dojo.byId('"+divId+"_title_node').innerHTML='';"+
                            "dispatcher.doQuery(standardWidgets['"+self.nonce+"'].copyParams(queryParams.lastQuery),standardWidgets['"+self.nonce+"'].drawCallback); " +
                            "return false;"+
                            
                            "\" title=\"Only explore discussions involving "+ extraName +
                            "\"><img src=\"images/zoomplus.png\" class=\"entityIcon\" /></a>";
                        }
                        
                        result = result + "</div></div><br />";

                        if(currSelected){
                            result = result + "<div class=\"last_selected_entity\"><div class=\"last_selected_entity_corner\">&nbsp;</div></div><br />";
                        }
                    }
                }
            }
        }
        
        result = result + "<div class=\"left_entity_separator\">&nbsp;</div>";
        
        return [ result, storyEntities];
    };
    
    newWidget.rebuildEntIndex();
    
    return newWidget;
};

var newExplorationWidget = function(divId){
    
    var newWidget = new BaseWidget(divId,"storyCloud",
            function(attrName){
                return (attrName !== "topic");
            }, function(response, self){
        var results, copy = {type:"allEntPop"},i,manyEnts;

        if((response !== undefined )&&(response.results !==undefined) && (response.results.length > 0)){
            results = response.results;

            manyEnts = (queryParams.lastQuery.entities.split(",").length>1);

            dojo.byId(divId).innerHTML = self.drawMain(self,results, manyEnts);
            
            //request popularity curve:
           
            //send the request for a popcurve
            for(i in queryParams.lastQuery){
                if(queryParams.lastQuery.hasOwnProperty(i)){
                    copy[i]=queryParams.lastQuery[i];
                }
            }
            util.makeLoad(self.popCurveNonce);
            dispatcher.doQuery(copy,function(response){
                if(response.url !== undefined){
                    dojo.byId(self.popCurveNonce).innerHTML = "<img src=\""+response.url+"\" />";
                }
            });

            if((response.title !== undefined) && 
                    ( (dojo.byId(divId+"_title_node").innerHTML.length === 0) || (!self.honorOldTitle)) ){
                if (response.titleUrl === undefined){
                    dojo.byId(divId+"_title_node").innerHTML =
                        util.trimString(response.title,titlelimits.maxPageTitleExplore, true);
                } else if(response.title.length > titlelimits.maxPageTitleExplore){
                    dojo.byId(divId+"_title_node").innerHTML ="<a class=\"titlelink\" href=\""+
                        response.titleUrl.replace(/'/g, "\\u0027").replace(/"/g,"\\u0022")+"\" target=\"_blank\" title=\""+
                        response.title+ "\">"+util.trimString(response.title,titlelimits.maxPageTitleExplore) +"</a>";
                } else {
                    dojo.byId(divId+"_title_node").innerHTML = "<a class=\"titlelink\" href=\""+
                        response.titleUrl.replace(/'/g, "\\u0027").replace(/"/g,"\\u0022")+"\" target=\"_blank\">"+
                        response.title+"</a>";
                }
            }
            
        } else {
            dojo.byId(divId).innerHTML = "";
            util.alert("Server communication error - no key players found in story.");
        }
    });
    
    newWidget.popCurveNonce = util.nonce();
    
    newWidget.honorOldTitle = false;
    
    newWidget.imageSize = 64;
    
    newWidget.drawMain = function(self,results, manyEnts){
        var result = "<div style=\"width:37%; float:left;\">"+
        "<div class=\"contentHeading\">Key entities involved</div><div class=\"dashboard_box\">", ent, currEnt,i,name;
        
        //draw main cluster
        for(ent in results[0]){
            if(results[0].hasOwnProperty(ent)){
                currEnt = results[0][ent];

                name = currEnt.name.replace(/'/g, "\\'").replace(/"/g,"\\\"");
                result = result +"<div class=\"storyEvolutionKeyPlayer\"><a href=\"#\" style=\"color:#"+
                    popularityUtils.colors[ent]+";\" onclick=\"";
                if(manyEnts){
                    //new ents are: lastentities - ent
                    result = result + "queryParams.setValues({entities: '"+
                        util.adjustEnts(queryParams.lastQuery.entities,undefined,currEnt.id)+
                        "' }); return false;";
                } else {
                    result = result + "return false;";
                }

                result = result + "\" title=\"Broaden content; ignore "+currEnt.name.replace(/'/g, "\\'").replace(/"/g,"\\u0022")+
                    "\"><img src=\"" + util.formatImage(currEnt.image,self.imageSize,self.imageSize) + "\" class=\"largeEntityIcon\" /><br/>"+
                    "<img src=\""+popularityUtils.baseUrl+popularityUtils.colors[ent]+"\" />"+util.trimString(name,titlelimits.maxEntityNameExplore,true)+"</a></div>";
            }

        
        }
        
        result = result +
        "</div></div>"+
        "<div style=\"width:3%; float:left;\">&nbsp;</div>"+
        "<div style=\"width:60%; float:left;\">"+
        "<div class=\"contentHeading\">Mentions in social media</div>"+
        "<div id=\""+self.popCurveNonce+"\">&nbsp;</div>"+
        "</div>"+
        "<br style=\"clear:both;\"/>"+
        "<div>"+
        "<div class=\"contentHeading\">Related entities</div>"+
        "<div class=\"dashboard_box\">";

        for(i in results){
            if(i > 0){
                result = result + "<div class=\"cluster_all cluster_";
                if( i % 2 === 0){
                    result = result + "even\">";
                } else {
                    result = result + "odd\">";
                }
                //draw additional clusters
                for(ent in results[i]){
                    if(results[i].hasOwnProperty(ent)){
                        currEnt = results[i][ent];
                        name = currEnt.name.replace(/'/g, "\\'").replace(/"/g,"\\\"");

                        if((ent > 0)&&(ent % 5 === 0)){
                            result = result + "</div><div class=\"cluster_all cluster_";
                            if( i % 2 === 0){
                                result = result + "even\">";
                            } else {
                                result = result + "odd\">";
                            }
                        }
                        //new ents are self.overrideParams.entities +currEnt (up to 4 entities)
                        result = result + "<div class=\"storyEvolutionKeyPlayer\"><a href=\"#\" onclick=\"queryParams.setValues({entities: '"+
                                util.adjustEnts(queryParams.lastQuery.entities,currEnt.id, "upto4")+
                                "' }); return false;\" title=\"Refine content; include "+
                                currEnt.name.replace(/'/g, "\\'").replace(/"/g,"\\u0022")+
                                "\"><img src=\"" + util.formatImage(currEnt.image,self.imageSize,self.imageSize) + 
                                "\" class=\"largeEntityIcon\" /><br />"+
                            util.trimString(name,titlelimits.maxEntityNameExplore)+"</a></div>";

                    }
                }
                result = result + "</div>";
            }
        }
        result = result + "</div>";
        return result;
    };
    
    return newWidget;
};

var newVideoWidget = function(divId, queryType, conditionFn){
    return new BaseWidget(divId,queryType,conditionFn, function(response, self){
        var result = "", results, i, curr;
        
        if(self.currentPage > 0){
            result = "<div class=\"nav_button_area_vert\"><a href=\"#\" onclick=\"standardWidgets['"+self.nonce+"'].page(-1); return false;\">" +
                    "<br /><img src=\"images/leftarrow.png\" height=\"12px;\" width=\"12px\" />" +
                    "<br />p<br />r<br />e<br />v<br />" +
                    "<img src=\"images/leftarrow.png\" height=\"12px;\" width=\"12px\" /></a></div>";
        }
        
        if((response !== undefined )&&(response.results !==undefined) && (response.results.length > 0)){
            results = response.results;
            for(i in results){
                if(results.hasOwnProperty(i)){    
                    curr = results[i];
                    result = result+"<div class=\"youtube_video_container\" >"+
                        "<a href=\""+
                        curr.url+
                        "\" class=\"youtube_videothumb\" target=\"_blank\" title=\"" +
                        curr.hover+
                        "\">"+
                        "<img src=\""+
                        curr.image+
                        "\" class=\"youtube_video_image\" />"+
                        "<span></span></a>";
                    if(curr.name !== undefined){
                        result = result + "<div class=\"youtube_video_title\">"+
                                util.trimString(curr.name,titlelimits.maxVideoTitle,true)+"</div>";
                    }
                    result = result +"</div>";
                }
            }
            
            if(results.length === self.resultsPerPage){
                result = result+ "<div class=\"nav_button_area_vert\"><a href=\"#\" onclick=\"standardWidgets['"+self.nonce+"'].page(1); return false;\">" +
                "<br /><img src=\"images/rightarrow.png\" height=\"12px;\" width=\"12px\" />" +
                "<br />m<br />o<br />r<br />e<br />" +
                "<img src=\"images/rightarrow.png\" height=\"12px;\" width=\"12px\" /></a></div>";
            }
        } else {
            if(self.currentPage > 0){
                result = result + "No further videos found";
            } else {
                result = result + "No videos found";
            }
        }
        dojo.byId(divId).innerHTML = result;
    });
};

var newDescribeDemographicsWidget = function(divId){
    var self = {};
    self.drawCallback = function(response){
        var result = "", source = response.results,c,d,describe;
        
        if((source !== undefined)&&(source.length !== undefined)&&(source.length > 0)){
            result = "<span>Currently focusing on:<br />";
            describe = "";
            for(d in source){
                if(source.hasOwnProperty(d)){
                    c = source[d];
                    if( d > 0 ){
                        describe = describe + ", ";
                    }
                    describe = describe + c.description;
                }
            }
            for(d in source){
                if(source.hasOwnProperty(d)){
                    c = source[d];
                    if( d > 0 ){
                        result = result + "&nbsp;";
                    }
                    result = result + "<img src=\"images/"+c.url+"\" title=\"Currently focusing on "+describe+"\"/>";
                }
            }
            result = result +"</span>&nbsp;<a href=\"#\" onclick=\"queryParams.setValues(" +
                    "{country:'Any',state:'Any',city:'Any',industry:'Any',age:'Any',gender:'Any'});" +
                    " return false;\" title=\"see what all people are saying \">" +
                    "see all</a>,";
        }

        dojo.byId(divId).innerHTML = result;
    };
    self.conditionFn =  function(attrName){
        return ((attrName === "country")||(attrName === "industry") || (attrName==="gender")||(attrName==="age"))&&
            ((queryParams.lastQuery.country !== "Any")||(queryParams.lastQuery.industry !== "Any")||
                (queryParams.lastQuery.gender !== "Any")||(queryParams.lastQuery.age !== "Any"));
        //when demographic is null, hand over to Demographics Widget
    };
    
    self.copyParams = function(params){
        var copy = {}, i;
        for(i in params){
            if(params.hasOwnProperty(i)){
                copy[i]=params[i];
            }
        }

        util.makeLoad(divId);
        copy.type = "describeDem";
        //copy[queryType+"_k"]=3;
        return copy;
    };

    queryParams.subscribe(function(diffs, params){
        if(self.conditionFn()){
            dispatcher.doQuery(self.copyParams(params),self.drawCallback);
            return;
        }
        var j;
        for(j in diffs){
            if(self.conditionFn(j)){
                dispatcher.doQuery(self.copyParams(params),self.drawCallback);
                break;
            }
        }
    });
    
    return self;
};

var newTopicWidget = function(divId){
	var self = {};
    self.drawCallback = function(response){
        var result = "", source = response.results,c,d, addExtra, currentEchoed=false;
        
        addExtra = queryParams.lastQuery.topic;
        if((addExtra === "")||(addExtra === "Any")){
            addExtra = undefined;
        }
        
        if((source !== undefined)&&(source.length !== undefined)&&(source.length > 0)){
            //move current topic to front
            if(addExtra !== undefined){
                for(d in source){
                    if(source.hasOwnProperty(d)){
                        c = source[d];
                        if(c.value === addExtra){
                            result = "Showing content in "+ c.name + "&nbsp; (switch to ";
                            currentEchoed = true;
                            break;
                        }
                    }
                }
                if(!currentEchoed){
                    //echo current topic
                     result = "Showing content in "+ addExtra + "&nbsp; (switch to ";
                }
                result = result + "<a href=\"#\" onclick=\"queryParams.setValues("+
                    "{topic:''}); return false;\" title=\"See all content\" " +
                    "style=\"text-decoration:none;\" class=\"uh_link\" onmouseover=\"changeClass(this,'hl_link')\"" +
                    "onmouseout=\"changeClass(this,'uh_link')\">All content</a>&nbsp;";
            } else {
                //echo "All topics"
                result = result + "Showing all content &nbsp; (switch to ";
            }
            
            for(d in source){
                if(source.hasOwnProperty(d)){
                    c = source[d];
                    if( d > 0 ){
                        result = result + "&nbsp;";
                    }
                    if(c.value !== addExtra){
                        result = result + "<a href=\"#\" onclick=\"queryParams.setValues("+
                            "{topic:'"+c.value+"'}); return false;\" " +
                            "class=\"uh_link\" style=\"text-decoration:none;\" onmouseover=\"changeClass(this,'hl_link')\"" +
                            "onmouseout=\"changeClass(this,'uh_link')\""+
                            " title=\"See content in "+c.name+"\">"+ c.name+"</a>";
                    }
                }
            }
            result = result + ")";

        } else if(addExtra !== undefined){
            //echo current topic
            result = "Showing content in "+ addExtra + "&nbsp; (switch to "+
                "<a href=\"#\" onclick=\"queryParams.setValues("+
                "{topic:''}); return false;\" title=\"See all content\"" +
                "style=\"text-decoration:none;\" class=\"uh_link\" onmouseover=\"changeClass(this,'hl_link')\"" +
                "onmouseout=\"changeClass(this,'uh_link')\">All content</a> )";
        }
        dojo.byId(divId).innerHTML = result;
    };
    
    self.copyParams = function(params){
        var copy = {}, i;
        for(i in params){
            if(params.hasOwnProperty(i)){
                copy[i]=params[i];
            }
        }

        util.makeLoad(divId);
        copy.type = "topics";
        //copy[queryType+"_k"]=3;
        return copy;
    };

    queryParams.subscribe(function(diffs, params){
        dispatcher.doQuery(self.copyParams(params),self.drawCallback);
    });
    
    return self;
};

function DemographicsWidget(divId,type){
    
    var self = this,
        adjective = (type === "activeDem")?"active":"engaged",
        describe;
    
    this.drawCallback = function(response){
        var result = "Most "+adjective+" demographics:<br />", source = response.results,c,d;
        //TODO "These groups of people were most active today on social media; click to find out what each group was talking about."
        //TODO "These groups of people were most engaged with this story; click to find out what each group was talking about.
        
        if((source === undefined)||(source.length === undefined)||(source.length === 0)){
            result = result + "<span><img src=\"images/demog/all.png\" title=\"No specific demographic is particularly "+
                        adjective+"\"/></span>";
            source = undefined;
        } else {
            result = result + "<span>";
            describe = "Most "+adjective+" demographics: ";
            for(d in source){
                if(source.hasOwnProperty(d)){
                    c = source[d];
                    if( d > 0 ){
                        describe = describe + ", ";
                    }
                    describe = describe + c.description;
                }
            }
            for(d in source){
                if(source.hasOwnProperty(d)){
                    c = source[d];
                    if( d > 0 ){
                        result = result + "&nbsp;";
                    }
                    result = result + "<img src=\"images/"+c.url+"\" title=\""+describe+"\"/>";
                }
            }
            result = result + "</span>";
        }

        dojo.byId(divId).innerHTML = result;

        self.drawPopup(source);
    };
    
    this.drawPopup = function(results){
        if(type === "activeDem"){
            demographicPopup.titleNode.innerHTML="What are they talking about ...";
        } else if(type === "engagedDem"){
            demographicPopup.titleNode.innerHTML="What are they saying about this story...";
        }

        var result = "",c,revers,d;
        
        if(results !== undefined){
            for(d in results){
                if(results.hasOwnProperty(d)){
                    c = results[d];
                    revers = dojo.fromJson(c.reverse);
                    if(revers.country === undefined){
                        revers.country = "Any";
                    }
                    if(revers.age === undefined){
                        revers.age = "Any";
                    }
                    if(revers.gender === undefined){
                        revers.gender = "Any";
                    }
                    result = result + "<div><a href=\"#\" style=\"text-decoration:none;\" onclick=\"queryParams.setValues("+
                        dojo.toJson(revers).replace(/'/g,"@@@").replace(/"/g,"'").replace(/@@@/g,"\\\'")+
                            "); demographicPopup.hide(); return false;\" ><img src=\"images/"+
                        c.url+"\" title=\""+c.description+"\"/>&nbsp;&nbsp;"+c.description+"</a></div>";
                }
            }
        }
        
        result = result + "<div><a href=\"#\" style=\"text-decoration:none;\" onclick=\"queryParams.setValues("+
            "{country:'Any',state:'Any',city:'Any',industry:'Any',age:'Any',gender:'Any'}); demographicPopup.hide(); return false;\" >" +
            "<img src=\"images/demog/all.png\" title=\"Everybody\"/>&nbsp;&nbsp;Everybody</a></div>";
    
        dojo.byId("demographicPopupTop").innerHTML = result; 
    };
    
    this.conditionFn =     function(attrName){
        return ((attrName === "period")||
                (attrName === "country")||(attrName === "industry")||
                (attrName === "gender")||(attrName === "age")||
            ((type === "engagedDem")&&((attrName === "entities") || (attrName==="moreEnts"))))&&
            ((queryParams.lastQuery.country === "Any")&&(queryParams.lastQuery.industry === "Any")&&
                    (queryParams.lastQuery.gender === "Any")&&(queryParams.lastQuery.age === "Any"));
        //when a demographic is set, hand over to describe demographic widget
    };
    
    this.copyParams = function(params){
        var copy = {}, i;
        for(i in params){
            if((i === "period")||
                ((type === "engagedDem")&&((i === "entities") || (i==="moreEnts")))){
                copy[i]=params[i];
            }
        }

        util.makeLoad(divId);
        copy.type = type;
        //copy[queryType+"_k"]=3;
        return copy;
    };

    queryParams.subscribe(function(diffs, params){
        if(self.conditionFn()){
            dispatcher.doQuery(self.copyParams(params),self.drawCallback);
            return;
        }
        var j;
        for(j in diffs){
            if(self.conditionFn(j)){
                dispatcher.doQuery(self.copyParams(params),self.drawCallback);
                break;
            }
        }
    });
}

var createAutocomplete = function(divId,url,callback,drawParams,drawItem,itemValue,moreParams){

    var tmpNode = document.createElement("div");
    if(moreParams === undefined){
        moreParams = {demographics: function() { return dojo.toJson(queryParams.lastQuery); } };
    }
    
    if(drawItem === undefined){
        drawItem = function(value){
            var v = value.split("@");
            return "<span style=\"width:40px;height:40px;\"><img src=\""+v[0]+"\" title=\""+v[1]+"\"></span><span style=\"font-size:17px;\">"+v[1]+"</span>";
        };
    }
    
    if(itemValue === undefined){
        itemValue = function(value){
            var html = value.split("@")[1];
            //TODO: test unescaping of html in other browsers 
            tmpNode.innerHTML = html;
            if(tmpNode.innerText !== undefined){
                return tmpNode.innerText; // IE
            }
            return tmpNode.textContent; // FF
        };
    }
    
    if(drawParams === undefined){
        drawParams = {};
    }
    if(drawParams.width === undefined){
        drawParams.width = 150;
    }
    if(drawParams.max === undefined){
        drawParams.max = 7;
    }
    if(drawParams.scrollHeight === undefined){
        drawParams.scrollHeight = 300;
    }
    
    $().ready(function() {
        $("#"+divId).autocomplete(url, {
                width: drawParams.width,
                max: drawParams.max,
                highlight: false,
                scroll: true,
                scrollHeight: drawParams.scrollHeight,
                formatItem: function(data, i, n, value) {
                    return drawItem(value);
                },
                formatResult: function(data, value) {
                    return itemValue(value);
                },
                extraParams: moreParams
        });

        
    
        $("#"+divId).result(
                function (event, data, formatted) {
                    callback(formatted);
                }
        ).next().click(function() {
            $(this).prev().search();
        });
    });
};

console.debug("Loaded grapevine.js");