/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) Open-Xchange Inc., 2006-2011
 * Mail: info@open-xchange.com 
 * 
 * @author Stefan Preuss <stefan.preuss@open-xchange.com>
 * 
 */

function AutoComplete(oText, oDiv, nMaxSize, minChars, timeout) {    
    this.oText = oText;
    this.oDiv = oDiv;
    // minium characters a user has to enter before search will be executed
    this.minChars = minChars && minChars >= 2 ? minChars : 2;
    // maximum numbers of results which will be displayed
    this.nMaxSize = nMaxSize || 20;
    // timeout after the search results will be displayed
    this.timeout = timeout || 500;
    this.searchPattern = new Object();
    this.cancelRequest = false;
    
    /* to work with teamview (i.e. other use cases than new mail) */
    this.autoAdjustHeight = true;
    this.showDisplayNamesOnly = false;
    this.showSystemUsersOnly = false;
    this.includeResources = false;
    this.nameIndex = {};
    
    /*-- private variables --*/
    this.tt;
    this.cache = new AutoCompleteCache();
    this.currSelected = 0;
    this.currEntries = 0;
            
    // attach handlers to the text-box
    oText.AutoComplete = this;
    oText.onkeydown = AutoComplete.prototype.onTextChange;
    oText.onblur = AutoComplete.prototype.onTextBlur;
}

AutoComplete.prototype.onTextBlur = function() {
   this.AutoComplete.cancelRequest=true;
    this.AutoComplete.clear();
    // remove last ending , if exists
    var value = trimStr(this.AutoComplete.oText.value);
    if (value.charAt(value.length-1) == ",") {
       this.AutoComplete.oText.value = value.substring(0, value.length-1);
    }
};

AutoComplete.prototype.onTextChange = function(evt) {
    var parent = this;
    if (!evt) evt = event;
    a = evt.keyCode;    
   this.AutoComplete.cancelRequest = false;
    switch (a) {
        case 27: //ESC
            parent.AutoComplete.clear();
            stopEvent(evt); 
            return false;
        case 38:    // UP
            parent.AutoComplete.keyUpDown(false);
            break;
        case 40:    // DOWN
            parent.AutoComplete.keyUpDown(true);
            break;
        case 16: case 17: case 18: case 35: case 36: case 37: case 39: 
           // ignore: SHIFT / CTRL / ALT / POS1 / END / LEFT / RIGHT;
            return true;
        case 13: case 9: // ENTER
            return parent.AutoComplete.keyEnter(a);
        default:
            setTimeout(function() {
                    var st = getCurrStr(parent.AutoComplete.oText.value, parent.AutoComplete.oText);
                    if (getSearchPatternLength(st) >= parent.AutoComplete.minChars) {
                        parent.AutoComplete.search(trimStr(st));
                    } else {
                        parent.AutoComplete.clear();
                    }
                },50);
            return;
    }
    if (this.AutoComplete.visible) stopEvent(evt);
};

AutoComplete.prototype.onDivMouseDown = function() {
    var st = this.AutoComplete.oText.scrollTop;
    this.AutoComplete.onSelect(this, st);
    this.AutoComplete.clear();
    return true;
};

AutoComplete.prototype.onDivMouseOver = function() {
    this.className = "PopupEntryDivActive border-color-PMG-selection-elements";
    this.AutoComplete.oText.currSelected = this.getAttribute("ono");
};

AutoComplete.prototype.onDivMouseOut = function() {
    this.className = "PopupEntryDiv";
};

AutoComplete.prototype.keyUpDown = function (up) {
    // top or bottom has been reached, so nothing to do    
    if ((up && this.currSelected >= this.currEntries) || (!up && this.currSelected == 0)) {
        return;
    }

    // disable old selection
    document.getElementById("ac_" + this.currSelected).className = "PopupEntryDiv";
    if (up)
        this.currSelected++;
    else
        this.currSelected--;
    
    // select new row
    var elem = document.getElementById("ac_" + this.currSelected);
    if (elem) {
        elem.className = "PopupEntryDivActive border-color-PMG-selection-elements";
        if (this.oDiv.scrollTop + this.oDiv.clientHeight
            < elem.offsetTop + elem.offsetHeight)
        {
            this.oDiv.scrollTop = elem.offsetTop + elem.offsetHeight
                - this.oDiv.clientHeight;
        }
        if (this.oDiv.scrollTop > elem.offsetTop)
            this.oDiv.scrollTop = elem.offsetTop;
    }
};

AutoComplete.prototype.keyEnter = function () {
    var _rb = true;
    var elem = document.getElementById("ac_" + this.currSelected);
    if (elem) {
        var st = this.oText.scrollTop;
        this.onSelect(elem, st);
        _rb = false;
    }
    this.clear();
    return _rb;
};

AutoComplete.prototype.onSelect = function (elem, st) {
    this.oText.value=replaceCurrStr(this.oText.value, this.oText, elem.getAttribute("oval"));
    setSelRange(this.oText, this.oText.value.length, this.oText.value.length, st);
};

AutoComplete.prototype.clear = function () {
    removeChildNodes(this.oDiv);
    if (this.autoAdjustHeight) {
        this.oDiv.style.height = "";
    }
    this.oDiv.style.visibility = "hidden";    
    this.currSelected = 0;
    this.currEntries = 0;
    this.visible = false;
};

AutoComplete.prototype.enableDiv = function() {
    // positioning the div and enable it
    var oTop = getAbsolutePositionTop(this.oText) + this.oText.offsetHeight;
    var oLeft = getAbsolutePositionLeft(this.oText);
    this.oDiv.style.top = oTop + "px";
    this.oDiv.style.left =  oLeft + "px";
    this.oDiv.scrollTop = "0";
    this.oDiv.style.visibility = "visible";
    this.visible = true;
};

AutoComplete.prototype.onchange = function(txt) {    
    // get all the matching strings from the AutoCompleteCache
    var aStr = new Array();
    this.cache.getResults(txt, aStr);
    this.currSelected = 0;

    // count the number of strings that match the text-box value
    var nCount = aStr.length > this.nMaxSize ? this.nMaxSize : aStr.length;
    this.currEntries = nCount-1;
            
    // if a suitable number then show the popup-div
    if (!this.cancelRequest && this.nMaxSize != -1 && nCount > 0) {
        //!(nCount == 1 && trimStr(txt) == aStr[0])
        
        // clear the popup-div.
        while ( this.oDiv.hasChildNodes() )
            this.oDiv.removeChild(this.oDiv.firstChild);
        
        for (var i = 0; i < nCount; i++ ) {
            var nobj = aStr[i];
            for (var ab in nobj) {                
                var myObj = nobj[ab];
                
                // add each string to the popup-div
                var nDiv = newnode("div", 
                    { overflow: "hidden", whiteSpace: "nowrap" }, null);
                getParsedNode(txt, ab, nDiv);
                nDiv.id = "ac_" + i;
                nDiv.setAttribute("oval", myObj.join(","));
                nDiv.setAttribute("ono", ab);
                nDiv.onmousedown = AutoComplete.prototype.onDivMouseDown;
                nDiv.onmouseover = AutoComplete.prototype.onDivMouseOver;
                nDiv.onmouseout = AutoComplete.prototype.onDivMouseOut;
                if (this.currSelected == i)
                    nDiv.className = "PopupEntryDivActive border-color-PMG-selection-elements";
                else 
                    nDiv.className = "PopupEntryDiv";
                nDiv.AutoComplete = this;
                this.oDiv.appendChild(nDiv);    
            }
        }
        //this.oDiv.style.height = "20em";
        if (this.autoAdjustHeight && this.oDiv.offsetHeight > 160) {
            this.oDiv.style.height = "160px";
        }
        this.enableDiv();
    } else {
        this.clear();
    }
};

AutoComplete.prototype.search = function(str) {
    var parent = this;
    
    // already in cache?
    if (this.cache.contains(trimStr(str)))
       return;
    
    for (var e in this.searchPattern) {
        var sp = new RegExp("^" + escapeRegExp(e));
        if (sp && str.match(sp)) {
            this.onchange(str);
            return;
        }
    }    
        
    // clear timer if already set
    if (this.tt) 
        window.clearTimeout(this.tt);
        
    if (this.oDiv.firstChild) 
        this.oDiv.removeChild(this.oDiv.firstChild);

    this.oDiv.appendChild(
        newnode("div", { textAlign: "center", width: "100%" }, null, [ 
            newnode("img",null,{src: getFullImgSrc("img/ox_animated_withoutbg.gif")},[])
        ])
    );        
        
    // set new timer with given timout
    this.tt = window.setTimeout(function() {
        
        if (parent.cancelRequest) return; // request aborted, so leave    
        parent.enableDiv();
        
        // fire-up search
        var pattern = str;
        if (pattern.charAt(pattern.length - 1) != "*") pattern += "*";
        
        ox.api.folder.exists({
            folder: "6",
            success: function (GAL) {
            
                // in which folder should be searched for autocomplete
                var allFolder = GAL ? [6] : [];
                allFolder.push(configGetKey("folder.contacts"));
                if (configGetKey("modules.mail.contactCollectFolder")) {
                    allFolder.push(configGetKey("modules.mail.contactCollectFolder"));
                }
                
                // default folder
                var defaultSearchFolder = configGetKey("folder.contacts") || 6;
                
                // create multiple request
                var multiple = [{
                    // search contacts
                    "action": "search",
                    "module": "contacts",
                    "columns": "1,500,555,556,557,524,602,592,20,524",
                    "sort": "609",
                    "data": {
                        display_name: pattern, email1: pattern,
                        email2: pattern, email3: pattern, last_name: pattern,
                        first_name: pattern, folder: allFolder, orSearch: true,
                        emailAutoComplete: true
                    }
                }];
                
                if (configGetKey("language") == "ja_JP") {
                    multiple[0].data.yomiFirstName = pattern;
                    multiple[0].data.yomiLastName = pattern;
                }
                
                if (configGetKey("modules.contacts.allFoldersForAutoComplete") == true) {
                    delete(multiple[0].data.folder);
                }
                
                // search for resource?
                if (parent.includeResources) {
                    multiple.push({
                        "action": "search",
                        "module": "resource",
                        "data": {
                            "pattern": pattern,
                            "folder": defaultSearchFolder
                        }
                    });
                }
    
                (new JSON()).put(AjaxRoot + "/multiple?session=" + session, multiple, null, function(cb) {
                    // no response?
                    if (!cb || !cb.length) {
                        parent.clear();
                        return;
                    }
    
                    // process contacts
                    if (cb[0].error) {
                        newServerError(4, cb[0]);
                    } else {
                        var response = cb[0];
                        for (var i = 0; i < response.data.length; i++) {
                            var data = response.data[i];
                            // system users only? (folder_id=6)
                            if (parent.showSystemUsersOnly && data[8] != 6) continue;
                            // address prefix
                            var pn = data[1] || "";
                            // fill name cache (format: folder.id)
                            var name = quotePersonalAddr(pn);
                            var objectData = {
                                    type: 1, // user
                                    id: data[9], // internal user id
                                    folder: data[8],
                                    display_name: name
                            };
                            // names only?
                            if (parent.showDisplayNamesOnly) {
                                // add
                                var mailAddresses = [];
                                if (data[2]) mailAddresses.push("<"+data[2]+">");
                                if (data[3]) mailAddresses.push("<"+data[3]+">");
                                if (data[4]) mailAddresses.push("<"+data[4]+">");
                                var key = name + " " + mailAddresses.join(", ");
                                parent.cache.add(key, [key]);
                                parent.nameIndex[key] = objectData;
                            }
                            // distribution list?
                            else if (data[6] && data[7]) {
                                var addresses = [];
                                for (var ib in data[7]) {
                                    addresses.push(quotePersonalAddr(
                                        data[7][ib].display_name) +
                                        " <" + data[7][ib].mail + ">");
                                }
                                parent.cache.add(pn, addresses);
                            }
                            // normal users
                            else {
                                var em1 = data[2]; // email1
                                if (em1) {
                                    var key = quotePersonalAddr(pn) + " <" + em1 + ">";
                                    parent.cache.add(key, [key]);
                                    parent.nameIndex[key] = objectData;
                                }
                                var em2 = data[3]; // email2
                                if (em2) {
                                    var key = quotePersonalAddr(pn) + " <" + em2 + ">";
                                    parent.cache.add(key, [key]);
                                    parent.nameIndex[key] = objectData;
                                }
                                var em3 = data[4]; // email3
                                if (em3) {
                                    var key = quotePersonalAddr(pn) + " <" + em3 + ">";
                                    parent.cache.add(key, [key]);
                                    parent.nameIndex[key] = objectData;
                                }
                            }
                        }
                    }
    
                    // process resources
                    if (cb.length > 1 && !cb[1].error) {
                        var response = cb[1];
                        for (var i = 0; i < response.data.length; i++) {
                            var data = response.data[i];
                            var objectData = {
                                    type: 3, // resource
                                    id: data.id,
                                    folder: 0,
                                    display_name: data.display_name
                            };
                            var key = data.display_name + " <" + data.mailaddress + ">";
                            parent.cache.add(key, [key]);
                            parent.nameIndex[key] = objectData;
                        }
                    } else {
                        newServerError(4, cb[1]);
                    }
                    
                    // add search pattern
                    if (!parent.searchPattern[str]) {
                        parent.searchPattern[str] = str;
                    }
                    parent.onchange(str);
                });
            }
        });
    }, this.timeout);
};

function quotePersonalAddr(addr) {    
    if (addr.match(new RegExp("[.,:;<>]"))) {
        return "\"" + addr + "\"";
    } else {
        return addr; 
    }
}

function AutoCompleteCache() {
    this.aStr = new Object();
}

AutoCompleteCache.prototype.add = function(key, addresses) {
    if ( !this.aStr[key] ) 
        this.aStr[key] = new AutoCompleteCache();
    this.aStr[key] = addresses;
};

AutoCompleteCache.prototype.getResults = function(str, outStr) {
    for (var i in this.aStr) {
        // quote string for regex but fix wilcards
        // this reflects what the server does: the search pattern must start
        // at the begin or after whitespaces or "<" (email addresses)
        // additionally, display names might be quoted
        var resultRegex = "(^\"?|.*(\\s|<))"+escapeRegExp(str).replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
        var re = new RegExp(resultRegex, "i");
        var obj = {};
        obj[i] = this.aStr[i];
        if (i.match(re)) outStr.push(obj);
    }
};

AutoCompleteCache.prototype.contains = function(str) {
    var tA = new Array();
    this.getResults(str, tA);
    return (tA && tA.length == 1 && tA[0] == str);
};

/******************* UTILS **********************/

/**
 * Constructs a single row with and mark matching text colored
 * @param str  The search String
 * @param oStr The original String (the address in this case)
 * @param oDiv The original div where the row will be appended
 */
var tmp_span = newnode("span", { margin:0 }, { className:"font-style-headline" }, [document.createTextNode("\xa0")]);
function getParsedNode (str, oStr, oDiv) {
    var regexp = new RegExp(escapeRegExp(str), "gi");
    var mpos, lIndex = 0;
    while (str != "" && (mpos = regexp.exec(oStr))) {        
        // everything before
       oDiv.appendChild(document.createTextNode(oStr.substring(lIndex,mpos.index)));
       lIndex = mpos.index + str.length;
       // match
       var mn = tmp_span.cloneNode(true);
       
       mn.firstChild.nodeValue = oStr.substring(mpos.index, lIndex)
       oDiv.appendChild(mn);
    }
    // everything else
    oDiv.appendChild(document.createTextNode(oStr.substring(lIndex, oStr.length)));
}

/**
 * Gets the current String within the carret position
 * @param str  The search String
 * @param obj The div
 */
function getCurrStr(str, obj) {
    var caretStart = getCaretStart(obj);
    var iLength = 0;
    var io=splitMails(str);
    for (var a = 0; a < io.length; a++) {
        iLength += io[a].length + 1;
        if (iLength > caretStart) {
            return io[a];
        }
    }
    return null;
}

function getSearchPatternLength(str) {
    // remove wildcards and whitespaces and return the number of characters
    return (str || "").replace(/[*?\s]/g, '').length;
}

function replaceCurrStr(oldstr,obj,newstr) {    
    var caretStart = getCaretStart(obj);
    var io = splitMails(oldstr);
    var iLength = 0;
    var stringnotfound=true;
    for (var a = 0; a < io.length; a++) {
        iLength += io[a].length + 1;
        if (iLength > caretStart && stringnotfound) {
            io[a]=newstr;
            stringnotfound=false;
        }
    }
    // rebuild string
    var nStr = "";
    for (var a = 0; a < io.length; a++) {
        if (trimStr(io[a]).length != 0) nStr += trimStr(io[a]) + ", ";
    }
     return nStr;
}

function getCaretStart(obj){
    if (typeof obj.selectionStart != "undefined") {
        return obj.selectionStart;
    } else if (document.selection && document.selection.createRange){
        var O = document.selection.createRange();
        try {
            var am = O.duplicate();
            am.moveToElementText(obj);
        } catch(e) {
            var am = obj.createTextRange();
        }
        am.setEndPoint("EndToStart", O);
        var rb=am.text.length;
        if (rb > obj.value.length) {
            return -1;
        }
        return rb;
    }
}

function setSelRange(obj, selStart, selEnd, sc) {
   if (obj.setSelectionRange) {
      obj.focus(); 
      obj.setSelectionRange(selStart, selEnd);
   } else if (obj.createTextRange) { 
      var range = obj.createTextRange(); 
      range.collapse(true); 
      range.moveEnd('character', selEnd); 
      range.moveStart('character', selStart); 
      range.select(); 
   }   
   setTimeout(function() { 
            obj.focus(); 
            obj.scrollTop = sc + 2 + pxPerEm;           
       }, 0);
}

function splitMails(str) {
    var quoted=str.split("\"");
    var noquotes=new Array();
    var holes=new Array();
    var removequotes="";
    var count=0;
    if(quoted.length) {
        for(var i=0;i<quoted.length;i++) {
            if(i%2==0) {
                noquotes.push(quoted[i]);
                count=count+quoted[i].length;
            } else {
                holes.push({count : count , size : quoted[i].length+1 , content : quoted[i] })
            }    
        }
        removequotes=noquotes.join("");
    } else {
        removequotes=str;
    }
    var mysplit=removequotes.split(new RegExp(";|,","g"));
    var newquote=new Array();
    var tmpcount=-1;
    for (var i=0; i<mysplit.length;i++) {
        tmpcount++;
        var tmpInt=mysplit[i].length+tmpcount;
        var tmpText=mysplit[i];
        for(var i2=0;i2<holes.length; i2++) {
            if(holes[i2].count>tmpInt)  { break;}
            if(holes[i2].count<tmpcount) { continue;}
            else {
                tmpText=tmpText.substring(0,holes[i2].count-tmpcount)+"\""+holes[i2].content+"\""+tmpText.substring(holes[i2].count-tmpcount,tmpText.length);
            }
        }
        tmpcount=tmpcount+mysplit[i].length;
        mysplit[i]=tmpText;
    }
    return mysplit;
}
