String._format = function (format, values, useLocale) {
    if (!String._formatRE) {
        String._formatRE = /(\{[^\}^\{]+\})/g;
    }

    return format.replace(String._formatRE,
                          function(str, m) {
                              var index = parseInt(m.substr(1));
                              var value = values[index + 1];
                              if (value === null || value === undefined) {
                                  return '';
                              }
                              return value.toString();
                          });
}

String.format = function (format, params) {
/// <summary>
/// Creates a string using the provided format and values.
/// </summary>
/// <param name="format" optional="false" type="String">The formatted string</param>
/// <param name="params" optional="true" parameterArray="true">The formatted string</param>
/// <returns type="String">A formatted String.</returns>
    return String._format(format, arguments, /* useLocale */ false);
}

Array.prototype.add = function (item) {
/// <summary>
/// Adds an item into an array
/// </summary>
/// <param name="item" type="Object">Item to Add.</param>
    this[this.length] = item;
}

Array.prototype.indexOf = function (item) {
/// <summary>
/// Returns the index of an element in this array.
/// </summary>
/// <param name="item">Item to search for.</param>
/// <returns returnType="Number" elementInteger="true">Index of the item in this array.  If not found then -1 is returned.</returns>
    var length = this.length;
    if (length) {
        for (var index = 0; index < length; index++) {
            if (this[index] === item) {
                return index;
            }
        }
    }
    return -1;
}

Array.prototype.contains = function (item) {
/// <summary>
/// Tells you if an item exists in this array or not
/// </summary>
/// <param name="item" type="Object">item to search for in the array.</param>
    var index = this.indexOf(item);
    return (index >= 0);
}

function $(id) { 
/// <summary>
/// Returns a DOM element based on its ID.
/// </summary>
/// <param name="id" type="String" mayBeNull="false">ID of the element being searched.</param>
/// <returns elementDomElement="true" elementMayBeNull="false">A dom element.</returns>
     
    return document.getElementById(id); 
}

function $tag(elt, tagName) 
{ 
/// <summary>
/// Returns an array of DOM element based on a tagname.
/// </summary>
/// <param name="elt" domElement="true" mayBeNull="true">Root element to start at.  If null then will be replaced with the document object.</param>
/// <param name="tagName" type="String" mayBeNull="false">Tag name to look for.</param>
/// <returns elementDomElement="true" elementType="Array" elementMayBeNull="false">A dom element.</returns>
     
    return (elt || document).getElementsByTagName(tagName); 
}

if(Function.wrapMethod === undefined)
{
    Function.wrapMethod = Function.wrapTopLevelMethod = function (className, methodName, method) { return method; }
    Function.isSpecialMember = Function.needsWrapping = function () { return false; }
}

window.__classes = {};
/// <reference path="Kernel.js"/>
/// <reference path="Debug.js"/>

function Delegate() {}

Delegate.Null = function() 
{ 
/// <summary>
/// Represents an Empty function.
/// </summary>
}


Delegate._create = function (targets) {
    var delegate = function() {
        // If the delegate only has a single instance and
        // callback on it (usually happens 80% of the time.)
        // then just optimize for that one case and invoke
        // that method.
        if (targets.length == 2) 
        {
            // If we're dealing with only a single delegate
            // then you can have a return value.
            return targets[1].apply(targets[0], arguments);
        }
        else 
        {
            // Otherwise iterate over every delegate instance
            // and callback and invoke it.
            for (var i = 0; i < targets.length; i += 2) 
            {
                targets[i + 1].apply(targets[i], arguments);
            }
            
            // a colleciton of delegates cant have a return value
            // because each one could return something different
            // and that makes no sense.  So instead return null.
            return null;
        }
    };
    delegate.invoke = delegate;
    delegate._targets = targets;

    return delegate;
}


Delegate.create = function (object, method) 
{
/// <summary>
/// Creates an instance of a delegate.  If used, this method will return a 
/// function that when called will invoke the provided method but the "this"
/// instance of the callback will be set by the value of "object".
///
/// Equivalent to "new EventHandler(this, method)" in C#.
/// </summary>
/// <param name="object" type="Object">Object instance.</param>
/// <param name="method" type="Function">Function that will be invoked.</param>
/// <return type="Function">Function that if invoked will call the method but the 
/// "this" parameter will be whatever the value of "object"</return>
    
    // If no object instance was provided then we're just dealing with a
    // normal function.  There is no need to create a delegate so just
    // return it.
    if (!object) {
        method.invoke = method;
        return method;
    }
    return Delegate._create([object, method]);
}


Delegate.combine = function (delegate1, delegate2) 
{
/// <summary>
/// Takes two functions or two delegates or one of each and combines them and
/// returns a single new delegate that when called will call each method/delegate
/// successively.
///
/// Equivalent to += for events in C#.
/// </summary>
/// <param name="delegate1" type="Function">Function or delegate</param>
/// <param name="delegate2" type="Function">Function or delegate.</param>
/// <return type="Function">New delegate that is the aggregate of delegate1 and delegate2</return>
    if (!delegate1) {
        if (!delegate2._targets) {
            return Delegate.create(null, delegate2);
        }
        return delegate2;
    }
    if (!delegate2) {
        if (!delegate1._targets) {
            return Delegate.create(null, delegate1);
        }
        return delegate1;
    }

    var targets1 = delegate1._targets ? delegate1._targets : [null, delegate1];
    var targets2 = delegate2._targets ? delegate2._targets : [null, delegate2];

    return Delegate._create(targets1.concat(targets2));
}


Delegate.remove = function (delegate1, delegate2) {
/// <summary>
/// Takes two functions or two delegates or one of each and combines them and
/// returns a new delegate that has all callbacks in delegate1 but without 
/// any instances of delegate2.
///
/// Equivalent to -= for events in C#.
/// </summary>
/// <param name="delegate1" type="Function">Function or delegate</param>
/// <param name="delegate2" type="Function">Function or delegate.</param>
/// <return type="Function">New delegate that is equivalent to delegate1 but without delegate2</return>
    if (!delegate1 || (delegate1 === delegate2)) 
    {
        return null;
    }
    if (!delegate2) 
    {
        return delegate1;
    }

    var targets = delegate1._targets;
    var object = null;
    var method;
    if (delegate2._targets) {
        object = delegate2._targets[0];
        method = delegate2._targets[1];
    }
    else {
        method = delegate2;
    }

    // Look for an instance of delegate 2 inside of
    // Delegate1.  If its found then remove it and
    // return that as a new delegate.  Otherwise,
    // just quit out and return delegate1.
    for (var i = 0; i < targets.length; i += 2) 
    {
        if ((targets[i] === object) && (targets[i + 1] === method)) 
        {
            if (targets.length == 2) 
            {
                return null;
            }
            targets.splice(i, 2);
            return Delegate._create(targets);
        }
    }
    
    return delegate1;
}

var Events = {}

Events.init = function (instance)
{
/// <summary>
/// Initializes the members of "this" instance of the object to allow
/// the attaching and detaching of events.
/// </summary>
/// <param name="type" type="Object">use "this" as the value</param>
    
    
    instance._events = {};
}

Events._attachEvent = function (eventName, handler)
{
/// <summary>
/// Attaches a handler to a particular event that the object exposes.
/// </summary>
/// <param name="eventName" type="String">Event name being attached to</param>
/// <param name="handler" type="Delegate">Delegate or function to be called when this event is fired.</param>
    
    
    
    
    
    this._events[eventName] = Delegate.combine(this._events[eventName], handler);
    
    return this._events[eventName];
}

Events._detachEvent = function (eventName, handler)
{
/// <summary>
/// Detaches a handler from the specified event that the object exposes. If
/// the object does not have any handlers or does not expose this event then
/// this method will do nothing.
/// </summary>
/// <param name="eventName" type="String">Event name being detached from.</param>
/// <param name="handler" type="Delegate">Delegate or function to be removed from the list of callbacks.</param>
    
    
    
    
    
    if(!this._events[eventName])
    {
        
        return;
    }
    
    this._events[eventName] = Delegate.remove(this._events[eventName], handler);
    
    if(!this._events[eventName])
        delete this._events[eventName];
}

Events._fireEvent = function (eventName, arg)
{
/// <summary>
/// Fires an event and any and all listeners will receive it.
/// </summary>
/// <param name="eventName" type="String">Event being fired.</param>
/// <param name="arg" type="Delegate">Delegate or function to be removed from the list of callbacks.</param>
    
    
    
    
    if(!this._events[eventName])
        return;
    
    this._events[eventName].apply(this, arg);
}

Events.addEventing = function (type)
{
/// <summary>
/// Adds eventing support to a given object.  (Adds the attachEvent, detachEvent and fireEvent)
/// methods to the prototype of the provided type.
/// </summary>
/// <param name="type" type="Function">Type</param>
    var definition = type.prototype;
    if(definition)
    {
        definition._events     = null;
        definition.attachEvent = Events._attachEvent;
        definition.detachEvent = Events._detachEvent
        definition.fireEvent   = Events._fireEvent;
    }
    
    type._events = {};
    type.attachEvent = Events._attachEvent;
    type.detachEvent = Events._detachEvent
    type.fireEvent   = Events._fireEvent;
}
/// <reference path="Kernel.js" />
/// <reference path="Debug.js" />

var WLHMType = Function;

WLHMType.registerNamespace = function (name) 
{
/// <summary>
/// Creates a namespace.
/// </summary>
/// <param name="name" optional="false" type="String">Period separated namespace.</param>

    // If we've already created this namespace, dont bother
    // trying to create it again.  (Useful mainly for writers of shared components)
    if(window.__namespaces[name])
    {
        return;
    }
    
    var ns = window;
    var nameParts = name.split('.');
    var len=nameParts.length;
    
    for (var i = 0; i < len; i++) 
    {
        var part = nameParts[i];
        var nso = ns[part];
        
        if (!nso)
        {
            ns[part] = nso = {};
        }
        
        ns = nso;
    }
    
    window.__namespaces[name] = ns;
}

window.__namespaces = {};

WLHMType.getType = function (typeName)
{
/// <summary>
/// Returns a class based on a fully qualified string.
/// </summary>
/// <param name="name" optional="false" type="String">Period separated namespace.</param>

    // This method is leveraged mainly by Control.js.
    return window.__classes[typeName];
}

WLHMType.prototype.createClass = function (className, baseType)
{
/// <summary>
/// Adds runtime type information to a class. If a baseType is specified, 
/// then the class inherits all the members of the base.
/// </summary>
/// <param name="className" optional="false" type="String">Fully qualified name of the class.</param>
/// <param name="baseType" optional="true" type="Type">(optional) Base type to derive off of.</param>
    
    // The class we're creating is not a static class but we need to do the identical
    // book keeping on it as we would with a static class.  The book keeping that is 
    // needed is adding a typeName to the class definition and adding this class into
    // the collection of known classes.
    // 
    // Rather than duplicate the code, the Type.createStaticClass method is being 
    // called directly.
    WLHMType.createStaticClass(this, className);
    
    this.prototype.constructor = this;
    
    if(baseType) this.extendFrom(baseType);
}

WLHMType.createStaticClass = function (classDef, className)
{
/// <summary>
/// Creates a static class that will enable the use of Control.invokeStatic and rich
/// stack traces in watson.
/// </summary>
/// <param name="classDef" optional="false" type="Object">Definition of the object.</param>
/// <param name="className" optional="false" type="String">Fully qualified name of the class.</param>

    classDef.__typeName = className;
    
    // Keep a collection of all the classes that we've 
    // created so far (leveraged mainly by Control.js
    // and watson.js).
    window.__classes[className] = classDef;
}

__classes["Type"] = WLHMType;
/// <reference path="Kernel.js"/>
/// <reference path="Debug.js"/>
/// <reference path="Types.js"/>

var Control = {};

Control.onPreInvoke = Control.onPostInvoke = Control.onError = function() {};

Control.create = function (instance, elt)
{
/// <summary>
/// Makes the element usable by the Control helper class.
/// </summary>
/// <param name="instance" type="Object">Pass in "this" (without quotes).</param>
/// <param name="elt" domElement="true">Element reference to turn into a control.</param>
    
    
    
    
    
    instance.elt = elt;
    elt["__" + instance.constructor.__typeName] = instance;
}

Control.destroy = function (instance)
{
/// <summary>
/// Cleans up an object initialized by Control.create();
/// </summary>
/// <param name="instance" type="Object">Pass in "this" (without quotes).</param>
    

    var elt = instance.elt;
    elt["__" + instance.constructor.__typeName] = instance.elt = null;
    elt = null;
}

Control.findControl = function (startAt, typeName)
{
/// <summary>
/// Starts at a provided dom element and walks up the hierarchy until a dom element that has
/// an expando property that starts with "_" followed by the typeName associated with type.
/// </summary>
/// <param name="startAt">The element to start at. (Note: this element must exist in the DOM)</param>
/// <param name="typeName" type="String">Class to look for.  (The class must have had createClass() called on it)</param>
/// <returns>Returns a DOM element that has a control instance.  Otherwise null.</returns>
    
    
    
    
    
    var expando = "__" + WLHMType.getType(typeName).__typeName;
    
    while(startAt != document.body)
    {
        if(startAt[expando])
            return startAt[expando];
        
        startAt = startAt.parentNode;
    }
    
    
    
    return null;
}

Control._invokeImpl = function (typeName, methodName, event, args, isInstance)
{
    
    
    
    
    if(window.Event && window.Event.prototype)
        window.event = event;
    
    Control.onPreInvoke(typeName, methodName, args);
    
    var eventReturn = null;
    
    if(isInstance)
    {
        // Find the instance of the class type in the DOM starting at the event.srcElement.
        var control = Control.findControl(event.srcElement, typeName);
        
        
        
        
        if(!control || !control[methodName])
        {
            Control.onError({method : "Control.invoke", message : String.format("Unable to invoke {0}.{1}. (Control instance or method not found or does not exist)", typeName, methodName)});
            return;
        }
        
        // Fire the method
        eventReturn = control[methodName](args);
    }
    else
    {
        // Check to see if the specified type exists.
        var type = WLHMType.getType(typeName);
        
        
        
        
        if(!type || !type[methodName])
        {
            Control.onError({method : "Control.invokeStatic", message : String.format("Unable to invoke {0}.{1}. (Type or static method not found or does not exist)", typeName, methodName)});
            return;
        }
        
        // Fire the method
        eventReturn = type[methodName](args);
    }
    
    if(window.Event && window.Event.prototype)
    {
        eventReturn = event.returnValue;
    }
    
    Control.onPostInvoke();
    
    return eventReturn;
}

Control.invoke = function (typeName, methodName, event, args)
{
/// <summary>
/// Walks up the dom starting at event.srcElement to see if it can find an
/// element that has an expando with a member that is of the provided 
/// type and then invokes a method on it specified by methodName.
/// </summary>
/// <param name="typeName" type="String">Fully qualified classname.</param>
/// <param name="methodName" type="String">Name of the method to call.</param>
/// <param name="event">This must always be the browser event object.  Pass in "event" without quotes.</param>
/// <param name="args">(optional)Any arguments that you would like to have passed into the method.</param>
    
    
    
    
    
    
    return Control._invokeImpl(typeName, methodName, event, args, true);
}

Control.invokeStatic = function (typeName, methodName, event, args)
{
/// <summary>
/// Invokes a static method.
/// </summary>
/// <param name="typeName" type="String">Fully qualified classname.</param>
/// <param name="methodName" type="String">Name of the method to call.</param>
/// <param name="event">This must always be the browser event object.  Pass in "event" without quotes.</param>
/// <param name="args">(optional)Any arguments that you would like to have passed into the method.</param>
    
    
    

    
    
    return Control._invokeImpl(typeName, methodName, event, args, false);
}

Control.getAncestorByAttr = function (elt, att)
{
/// <summary>
/// Searches the current element and ancestors looking for the specified attribute.
/// If the attribute is found on the current DOM element or an ancestor, the value
/// of the attribute is returned. If not, null is returned.
///
/// This method specifically ignores the document body tag.  The reason being is 
/// because if an attribute is on the body, it is expected to be referenced directly
/// since it is a well known element and is guaranteed to be there on all pages.
/// </summary>
/// <param name="elt" domElement="true">Dom element to begin attribute search</param>
/// <param name="att" type="String">Attribute being queried</param>
/// <returns elementDomElement="true" elementMayBeNull="true">A dom element.</returns>
    
    
    
    // We're stopping on document.body for 2 reasons:
    // 1.  Performance (so you dont walk up higher than you realistically have to.
    // 2.  In Firefox, if you keep walking the DOM, elt will eventually return
    //     the document object and getAttribute does not exist on it.
    while(elt && elt != document.body && !elt.getAttribute(att))
    {
        elt = elt.parentElement;
    }
    
    return (elt != document.body) ? elt : null;
}

WLHMType.createStaticClass(Control, "Control");
function $callFocus(elt)          { setTimeout(function() { elt.focus(); }, 0); }
function $containsClass(elt, className)
{
/// <summary>
/// Returns true/false if a given element contains a classname
/// </summary>
/// <param name="elt" domElement="true" elementMayBeNull="false">Dom element to add the class name to.</param>
/// <param name="className" type="String" elementMayBeNull="false">Classname to test for.</param>
/// <return type="Boolean">True if the classname is applied, false otherwise.</return>
    
    
    
    var classes = elt.className.split(' ');
    
    return classes.contains(className);
}

function $addClass(elt, className)
{
/// <summary>
/// Adds a CSS class to the given dom element.  If the classname is already applied then no change will occur.
/// </summary>
/// <param name="elt" domElement="true">Dom element to add the class name to.</param>
/// <return type="Boolean">True if the classname was applied, false otherwise.</return>
    
    
    
    if(!elt.className.split(' ').contains(className))
    {
        elt.className += ' ' + className;
        return true;
    }
    
    return false;
}

function $removeClass(elt, className)
{
/// <summary>
/// Removes a CSS class to the given dom element.  If the classname does not exist then no change will occur.
/// </summary>
/// <param name="elt" domElement="true">Dom element to add the class name to.</param>
/// <return type="Boolean">True if the classname was applied, false otherwise.</return>
    
    
    
    var classes = elt.className.split(' ');
    
    if(classes.contains(className))
    {
        var index = classes.indexOf(className);
        if (index >= 0) 
        {
            classes.splice(index, 1);
        }        
        elt.className = classes.join(' ');
        
        return true;
    }
    
    return false;
}

//// Constants //////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
var ContactPickerConstants = {};
ContactPickerConstants.KeyCodes = { 
    NONE_SELECTED   : -1,
    BACK_BUTTON     : 8,
    TAB             : 9,
    RETURN          : 13,
    SHIFT           : 16,
    ESC             : 27,
    KEY_LEFT        : 37,
    KEY_UP          : 38,
    KEY_RIGHT       : 39,
    KEY_DOWN        : 40,
    FF_SEMICOLON    : 59, // this is lame, firefox return the charCode instead of the keyCode for semicolol
    SEMICOLON       : 186,
    COMMA           : 188,
    DELETE          : 46,
    SPACE          : 32
};

ContactPickerConstants.Constants = {
    NONE                        : -1, 
    IE                          : 1,
    FIREFOX                     : 2,
    FIREFOX3                    : 5,
    SAFARI                      : 3,
    OTHER_BROWSER               : 4,
    ROWS_PER_CONTAINER          : 10,
    PEOPLE                      : 1,
    CATEGORIES                  : 2,
    FAVORITES                   : 3,
    ADD                         : 1,
    REMOVE                      : 2,
    GLOBAL_ARRAY_EMAIL_INDEX    : 7,
    SAFARI_INPUT_WIDTH          : 18,
    PERSIST_SERVER_SIDE_ID      : 0,
    PERSIST_FULL_NAME           : 1,
    PERSIST_EMAIL               : 2    
};

ContactPickerConstants.Instrumentation = {
    PeopleTab                   : 0,
    CategoryTab                 : 1,
    FavoritesTab                : 2,
    ExpandCategory              : 3
};

//// AbchElement ////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
// Client-side AbchElement object backed by data from global array
var AbchElement = function (abchClientID, isLightLoad, emailIndex)
{
    var data = ContactPickerCore.contactData;
    if( data )
    {
        //allow 'parameterless constructor'
        if(abchClientID == null)
        {
            this.email = "";
            this.fullName = "";
            this._isNew = true;
            this.isCategory = false;
            this.isGroup = false;
            this.isFavorite = false;
            this.isSpacesFriend = false;
            return;
        }
        
        // full name is always at index 2
        this.fullName = data[abchClientID][2];
        this._clientID = abchClientID;
        if(!isLightLoad)
            this.serverSideID = data[abchClientID][1];
            
        //contact/group
        if(abchClientID < ContactPickerCore.contactCount)
        {   
            // return primary email unless emailIndex (optional parameter) was included as a parameter
            // in the constructor
            if(!emailIndex)
                emailIndex = 0; 
            // keep track of the email index, this is needed for syncing between controls
            this.emailIndex = emailIndex;
               
            if(data[abchClientID][6])
            {                 
                if(data[abchClientID][6].length > emailIndex)
                    this.email = data[abchClientID][6][emailIndex];                                            
                this._emailArray = data[abchClientID][6];                
            }
            this.isSpacesFriend = (data[abchClientID][5] == 0 || data[abchClientID][5] == "" ? false : true);
            if(!isLightLoad)
            {
                this.isGroup = (data[abchClientID][3] == 0 || data[abchClientID][3] == "" ? false : true);
                this.isFavorite = (data[abchClientID][4] == 0 || data[abchClientID][4] == "" ? false : true);                
                this.isCategory = false;                
            }
        }
        else
        {
            //category
            this.isFavorite = false;
            this.isSpacesFriend = false;
            this.isGroup = false;
            this.users = data[abchClientID][3];
            this.isCategory = true;
        }
    }
}
AbchElement.prototype =
{
    // private properties
    _clientID : ContactPickerConstants.Constants.NONE,
    _isNew : false,
    _uniqueSelectedElementID : 0,
    _emailArray : null,
    email : null,











    isValid : true,
    getClientId : function ()
    {
        return this._clientID;
    },
    
    getEmailArray : function ()
    {
        return this._emailArray;
    },
    
    setUniqueId : function (id)
    {
        this._uniqueSelectedElementID = id;
    },
    
    getUniqueId : function ()
    {
        return this._uniqueSelectedElementID;
    }
    
}

AbchElement.isFavorite = function ( index )
{
    if( !AbchElement.isCategory( index ) )
    {
        return ContactPickerCore.contactData[index][4];
    }
    
    return false;
}
AbchElement.isCategory = function ( index )
{
    return (index >= ContactPickerCore.contactCount);
}

//// CPDictionary ///////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
var CPDictionary = function ()
{
    // initialize dictionary
    this._dictionary = {};
    
    // initialize count
    this.count = 0;
}
CPDictionary.prototype =
{




    add : function ( key, value )
    {
        if( this._dictionary[key] )
            return;
        this._dictionary[key] = value;
        this.count ++;
    },
    clear : function ()
    {
        this.count = 0;
        this._dictionary = {};
    },    
    remove : function ( key )
    {
        delete this._dictionary[key];
        this.count --;
    },
    getPrimitive : function ()
    {
        return this._dictionary;
    },
    getItem : function (key)
    {
        var ret = this._dictionary[key];
        if( typeof(ret) == "undefined" )
            return null;
        
        return ret;
    }    
}


WLHMType.registerNamespace("Microsoft.Live.ContactPicker.Util");
Microsoft.Live.ContactPicker.Util = {
    isNullOrEmpty : function (string)
    {
        return (string == null || string == "");
    },
    
    escapeRegex : function (regexString)
    {
    ///<summary>Escapes any regex characters in a string</summary>
        var returnString = regexString.replace( /([\\\\.\\\\{\\\\}\\\\(\\\\)\[\]\/+*?|])/gi, "\\$1" );
        return returnString;
    },
    
    getCharCode : function ( char )
    {
        ///<summary>Converts a keyCode into a charCode</summary>
        var letter = String.fromCharCode(char).toLowerCase();
        return letter.charCodeAt(0);    
    },
    
    concat : function ( tokens )
    {
        var length = arguments.length;
        var buffer = [];
        for( var i = 0; i < length; i++ )
            buffer[i] = arguments[i];
        return buffer.join('');    
    },
    
    getBrowser : function ()
    {    
        //get browser
        if (navigator.appName == "Microsoft Internet Explorer")
        {
            return(ContactPickerConstants.Constants.IE);
        }
        if (navigator.appName == "Netscape")
        {
            if (navigator.appVersion.indexOf("Safari") > -1)
            {
                return(ContactPickerConstants.Constants.SAFARI);
            }
            else
            {
                if (navigator.userAgent.indexOf("Firefox/3") != -1)
                {
                    return(ContactPickerConstants.Constants.FIREFOX3);
                }
                else
                {
                    return(ContactPickerConstants.Constants.FIREFOX);
                }
            }
        }
    },
    //Spencer Lo's _htmlEncode routine.  
    //Lots of magic numbers in here.
    //But if we set constants for them, we may have issues when we want to upgrade to the latest
    //or incorporate a fix of this. Plus the number wouldn't mean much in English.  They are various boundries.
    //around the set of ascii values we care about encoding. 
    //Client-side html encoding is needed unfortunately by
    //Autocomplete for searching - htmlDecoding the data during the comparison is problematic because it significantly 
    //affects perf (decoding every email and full name in the loop), and because the searching and replacing involved in bolding are intimately connected
    //and applying htmlDecode to the real display string lets html and css attacks in.  Contact picker 
    //searches through the actual markup and so does not have this issue.
    htmlEncode : function (strInput)
    {     
         var c;
         var HtmlEncode = '';
         
         if(strInput == null)
         {
          return null;
         }
         if (strInput == '')
         {
              return '';
         }
         
         var inputLength = strInput.length;
         //for(var cnt = 0; cnt < inputLength; cnt++)
         for(var cnt = inputLength; cnt--;)
         {
              c = strInput.charCodeAt(cnt);
              
              if (( ( c > 96 ) && ( c < 123 ) ) ||
                   ( ( c > 64 ) && ( c < 91 ) ) ||
                   ( c == 32 ) ||
                   ( ( c > 47 ) && ( c < 58 ) ) ||
                   ( c == 46 ) ||
                   ( c == 44 ) ||
                   ( c == 45 ) ||
                   ( c == 95 ))
              {
                   //HtmlEncode = HtmlEncode + String.fromCharCode(c);
                   HtmlEncode = String.fromCharCode(c) + HtmlEncode;
              }
              else
              {
                   //HtmlEncode = HtmlEncode + ;
                   HtmlEncode = '&#' + c + ';' + HtmlEncode;
              }
         }         
         return HtmlEncode;
    },
    
    ///<summary>Decodes Html to plain text</summary>
    htmlDecode : function (input)
    {
        var elt = document.createElement('div');
        elt.innerHTML = input;
        return elt.innerText;
    },
    
    instrumentWrapper : function (type)
    {
        if (window.fireCPIEvent)
        {
            fireCPIEvent(type);
        }
    },
    
    getPrivateMessageString : function ()
    {
        return ("<span title=\"" + ContactPickerStrings.pmtt + "\">" + 
            "&lt;" + ContactPickerStrings.privateMessage + "&gt;" +
            "</span>"
        );
    }
}
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Framework\Debug.js" />
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Framework\Events.js" />
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Framework\Kernel.js" />
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Controls\ContactPickerCommon.js" />

WLHMType.registerNamespace("Microsoft.Live.ContactPicker");
Microsoft.Live.ContactPicker.Search = function ( instance_name, parent )
{
    
    
    
    // keep track of the instance name
    this._instanceName = instance_name;
    
    // keep an internal reference to the base DOM element
    this._base = $( this._instanceName + "$SearchBox" );
    
    
    // get a reference to the overlay
    this._overlay = $( this._instanceName + "$SearchBoxOverlay" );
    
    
    // create circular reference to make inline event handling possible
    Control.create( this, this._base );

    // keep a reference to the input element
    this._input = $( this._instanceName + "$SearchInput" );
    
    
    // keep track of my parent
    this._parent = parent; 
    
    // search images
    this._searchImg = $( this._instanceName + "$SearchImg" );
    

    this._searchCancelImg = $( this._instanceName + "$SearchCancelImg" );
    
}

Microsoft.Live.ContactPicker.Search.prototype =
{
    isVisible : true,
    isInputVisible : true,
    _sourceList : null,
    _viewlist : null,
    _resultsList : null,
    _resultsBackbuffer : null,
    _prevQuery : "",
    _bPaste : false,
    _bCut : false,
    _bSpecialKey : false,    









    _click : function ()
    {
        
        if (this._input.style.display != "none")
        {
            $callFocus(this._input);
        }
    },
    clear : function ()
    {
        this._bCut = false;
        this._bPaste = false;    
        // clear input field
        this._input.value = "";
        if( this._resultsList != null )
        {
            this._prevQuery = ""; // clear the query
            this._resultsBackbuffer = null;
            this._hideResults(); // hide the results if they are showing
            this._resultsList = null; // reset the lists 
            this._viewlist = null;
        }
        
        this._parent._showSelectAll();
    },  
    dispose : function ()
    {
        Control.destroy( this );
        this._base = null;
        this._searchImg = null;
        this._searchCancelImg = null;
        this._input = null;
        this._overlay = null;
        this._parent = null;
    },
    cutHandler : function ()
    {
        this._bCut = true;
        setTimeout( Delegate.create( this, this.searchHandler ), 0 );    
    },
    pasteHandler : function ()
    {
        // TODO, figure out how to handle paste/cut in Firefox
        // refer to http://www.thescripts.com/forum/thread151196.html, using DOMCharacterDataModified event
        
        // set the paste flag.
        this._bPaste = true;
        
        // call the searchHandler by hand if the paste happened via mouse
        if( !event.ctrlKey )
            setTimeout( Delegate.create( this, this.searchHandler ), 0 );
    },
    searchKeyDown : function ()
    {



        if( this._viewlist == null )
            this._updateLists(); // get the results list and contact list for the current tab
            
        var charCode = Microsoft.Live.ContactPicker.Util.getCharCode( event.keyCode );
        
        switch( charCode )
        {
            case ContactPickerConstants.KeyCodes.RETURN:
                event.returnValue = false;
                return false;
                break;
            case ContactPickerConstants.KeyCodes.ESC: // esc, TODO replace magic number
                setTimeout( Delegate.create( this, this.clear ), 0 );
                return;
                break;
            case ContactPickerConstants.KeyCodes.BACK_BUTTON: // Backspace, TODO magic numbers
                return;
                break;
            case ContactPickerConstants.KeyCodes.DELETE: // delete key, TODO
                return;
                break;
            case ContactPickerConstants.KeyCodes.SHIFT:
                this._bSpecialKey = true;
                return;
                break;
            default:
                break;
        }
        // TODO: should this be if( backbuffer isEmpty ) ?
        if( this._input.value == "" ) // if this is the first search
            this._populateBackbuffer( charCode );




    },
    _populateBackbuffer : function ( charCode )
    {
        
        var search_results = this._getResultsList( charCode );
        if( search_results )
        {
            var results = null;
            results = this._getFormattedResults( search_results, (this._viewlist.id.indexOf( "FavoritesList" ) != -1) );
            this._resultsBackbuffer = results; // store the results in a clean back buffer                
        }
    },
    // searchHandler happens on key up
    searchHandler : function ()
    { 



        if( this._input.value == "" )
        {
            this.clear();
            return;        
        }
        // if a paste happened, the back buffer must be created here
        // should run just as fast, but might seem slower because we don't get to process
        // onkeydown, this is the price we pay to support paste! :-o
        if( this._bPaste || this._bCut || this._bSpecialKey )
        {
            this._bCut = false;
            this._bPaste = false;
            this._bSpecialKey = false;
            
            if( this._viewlist == null )
                this._updateLists(); // get the results list and contact list for the current tab            
            this._populateBackbuffer( this._input.value.charCodeAt( 0 ) );
        }
        
        var results = "";
        // cache query
        var query = this._input.value;
        if( query == "" )
            return;
        else if( query != this._prevQuery )
        {
            // search
            results = this._getResults( query );
            
            // update value
            this._prevQuery = query;
        }
        else
            return; // no changes in query
        
        if( results == "" && this._viewlist.childNodes.length != 0 )
            results = String.format( "<div class=\"NoResults\">{0}</div>", ContactPickerStrings.noResults );
        this._resultsList.innerHTML = results;

        this._showResults();




    },
    // Searches the results pool for your query.
    // returns a string
    _getResults : function ( query )
    {
        // TODO sync the checks for Firefox and Safari
        var results = "";
        query = Microsoft.Live.ContactPicker.Util.escapeRegex( query );
        // encode gt/lt
        query = query.replace( /</gi, "&lt;" );
        query = query.replace( />/gi, "&gt;" );
        if( this._resultsBackbuffer == null )
            return "";
        var length = this._resultsBackbuffer.length;
        // regex for searching through the name cell
        var regExStrCell3_1 = "([3][\\\"]?>)(" + query + ")([^<]*?)";
        // searches through the name cell, but looks through other parts of the name (middle,last etc)
        var regExStrCell3_2 = "([3][\\\"]?>)([^<]*? )(" + query + ")([^<]*?</div>)";
        // regex for searchin through the email cell
        var regExStrCell4_1 = "([4][\\\"]?>)(" + query + ")([^<]*?</div>)";

        for( var i = 0; i < length; i++ )
        {
            var searchContact = this._resultsBackbuffer[i];
            
            var regExCell3_1 = new RegExp( regExStrCell3_1, "gi" );        
            var matchFirstName = regExCell3_1.exec( searchContact );
            if( matchFirstName ) // if there is a hit on first name add to results
                results += searchContact.replace( regExCell3_1, "$1<span class=\"FontWeight\">$2</span>$3" ); //TODO don't bold DBCS, use class instead of style
            else
            { // if there wasn't a hit on the name, check the other name tokens and email
                var regExCell3_2 = new RegExp( regExStrCell3_2, "gi" );        
                var mInnerName = regExCell3_2.exec( searchContact );
                if( mInnerName ) // if there is a hit on the name tokens
                    results += searchContact.replace( regExCell3_2, "$1$2<span class=\"FontWeight\">$3</span>$4" );
                else
                { // check primary email
                    var regExCell4_1 = new RegExp( regExStrCell4_1, "gi" );
                    var mPrimaryEmail = regExCell4_1.exec( searchContact );
                    if( mPrimaryEmail )
                        results += searchContact.replace( regExCell4_1, "$1<span class=\"FontWeight\">$2</span>$3" );                   
                }
            }
        }
        
        return results;
    },
    // takes in a string of of results and processes it, 
    // removes bolding, chevrons. Converts the index prefix to "results" (for syncing)
    // changes all the tabindicies to 0
    // returns the modified string
    _processResults : function ( result )
    {
        var results_string = result;
                
        // convert favorites,#,# or people,#,# to results,#,#
        var regExStr = "people,|favorites,";
        var regEx = new RegExp( regExStr, "gi" );
        results_string = results_string.replace( regEx, "results," );

        // remove chevrons     
        regExStr = "<a .*?</a>";
        regEx = new RegExp( regExStr, "gi" );
        results_string = results_string.replace( regEx, "&nbsp;" );
        
        // remove tabindexes
        regExStr = "tabIndex=[\\\"]?-1[\\\"]?";
        regEx = new RegExp( regExStr, "gi" );
        results_string = results_string.replace( regEx, "tabIndex=0" );
        
        //remove </span>
        regExStr = "</span>";
        regEx = new RegExp( regExStr, "gi" );
        results_string = results_string.replace( regEx, "" );
        
        // remove </span class="FontWeight">
        regExStr = "<span class=[^>]*?>";
        regEx = new RegExp( regExStr, "gi" );
        results_string = results_string.replace( regEx, "" );
        
        // firefox and safari only
        if( Microsoft.Live.ContactPicker.Util.getBrowser() != ContactPickerConstants.Constants.IE )
            results_string = this._shouldCheckElt( results_string );                
        
        return results_string;
    },
    // this method is for Firefox/Safari Only. It is hre because ff and sf don't copy check box states and custom
    // attributes needed to show the results correctly in the respective browers
    _shouldCheckElt : function ( result )
    {
        var buffer = result;
        var idToken = "";
        var regExStr = /results,[0-9]+,[0-9]+\"/gi;
        var m = result.match( regExStr );
        if( m != null )
        {
            var dictionary = this._parent.__getSelectContactsDictionary();
            var length = m.length;
            for( var i = length; i--; )
            {
                idToken = m[i];
                var lookupToken = idToken.replace( /results,/, "" );
                var lookupToken = lookupToken.replace( /\"/, "" );
                if( dictionary.getItem(lookupToken) )
                {
                    var regExRplc = new RegExp( idToken, "i" );
                    buffer = buffer.replace( regExRplc, idToken + " CHECKED _selected=\"true\" " );
                }
            }
        }
        return buffer;
    },
    // builds the string of contacts that were returned from _getResultsList
    // returns an array of strings
    _getFormattedResults : function ( results, favorites )
    {



        var results_array = [];
        
        var length = results.length;
        for( var i = length; i--; )
        {
            // do not show categories as part of the results
            if( !AbchElement.isCategory( results[i] ) )
            {
                if( !favorites ) // am i searching for favorites only?
                    results_array[results_array.length] = Microsoft.Live.ContactPicker.Util.concat( "<div class=\"RowContainer\">", this._processResults( this._sourceList.childNodes[results[i]].innerHTML ), "</div>" );
                else
                {
                    if( AbchElement.isFavorite( results[i] ) ) // favorites only
                        results_array[results_array.length] = Microsoft.Live.ContactPicker.Util.concat( "<div class=\"RowContainer\">", this._processResults( this._sourceList.childNodes[results[i]].innerHTML ), "</div>" );
                }
            }
        }





        return results_array;
    },
    // gets an index list from the the search index
    // returns a list of indicies
    _getResultsList : function ( index )
    {
        
        return ContactPickerCore.searchIndex[index]; 
    },
    _showResults : function ()
    {   
        
        if( this._resultsList.style.display == "block" )
            return;
        this._resultsList.style.display = "block";
        this._viewlist.style.display = "none";
        this._searchImg.style.display = "none";
        this._searchCancelImg.style.display = "block";
        this._parent._hideSelectAll()
    }, 
    _hideResults : function ()
    {
        
        if( this._resultsList.style.display == "none" )
            return;
        this._resultsList.style.display = "none";
        this._viewlist.style.display = "block";
        this._searchImg.style.display = "block";
        this._searchCancelImg.style.display = "none";
    },
    _updateLists : function ()
    {
        // get the source data list
        this._sourceList = this._parent.getSourceList();
        this._viewlist = this._parent.getWorkingList();
        // get the result list
        this._resultsList = this._parent.getWorkingList( true );
    },
    hide : function ()
    {
        ///<summary>Hides the tab page.</summary>
        this._base.style.display = "none";
        this.isVisible = false;
    },
    hideOverlay : function ()
    {
        this._overlay.style.display = "none";
        this._input.style.display = "block";
        this.isInputVisible = true;
    },
    show : function ()
    {
        ///<summary>Shows the tab page.</summary>       
        this._base.style.display = "block";
        this.isVisible = true;
    },
    showOverlay : function ()
    {
        ///<summary>Shows the tab page.</summary>       
        this._overlay.style.display = "block";
        this._input.style.display = "none";
        this.isInputVisible = false;
    },
    isActive : function ()
    {
        return (this._resultsList.style.display == "block");
    },
    resultList : function ()
    {
        
        return this._resultsList;
    },
    focus : function ()
    {
        if (this.isInputVisible)
        {
            $callFocus(this._input);
        }
    }
}
Microsoft.Live.ContactPicker.Search.createClass( "Microsoft.Live.ContactPicker.Search" );
// Class SearchIndexer
//      Encapsulates the contact picker transformation functionality.
//
WLHMType.registerNamespace("Microsoft.Live.ContactPicker");
Microsoft.Live.ContactPicker.SearchIndexer = function ()
{
    // init events for this instance
    Events.init( this );
    
    // create a callback for indexing attempts
    this._iCallback = Delegate.create( this, this._indexCallBack );
}

Microsoft.Live.ContactPicker.SearchIndexer.prototype =
{
    _timerId : 0,
    RETRY_INTERVAL : 1000,
    RETRY_PROCESSING : 500,
    start : function ()
    {
        // check to see if indexing is necessary
        this._indexCallBack();
    },
    _buildIndex : function ()
    {
        // create the search index array
        ContactPickerCore.searchIndex = [];

        // get the length of all elements together
        for( var i = ContactPickerCore.contactData.length; i--; )
        {
            // get the search tokens
            var target = ContactPickerCore.contactData[i][0];
            var length = target.length;
            var start_index = 0;
            
            // store the keys
            for( var j = 0; j < length; j++ )
            {
                // get unicode at position j
                var search_index = target.charCodeAt( j );
                if( ContactPickerCore.searchIndex[search_index] == undefined )
                    ContactPickerCore.searchIndex[search_index] = [];
                ContactPickerCore.searchIndex[search_index][ContactPickerCore.searchIndex[search_index].length] = i;
            }
        }     
    },
    _checkTransform : function ()
    {
        ///<summary>Return true if the data is already transformed. False otherwise.</summary>
        ///<returns type="bool" />
        if( typeof( ContactPickerCore ) == "undefined" ) // check to see if data is ready
            setTimeout( this._iCallback, this.RETRY_INTERVAL ); // try again
        else if( typeof( ContactPickerCore.searchIndex ) == "undefined" )
            setTimeout( this._iCallback, this.RETRY_INTERVAL ); // try again
        else if( ContactPickerCore.lock ) // is the data locked (being processed)?
            setTimeout( this._iCallback, this.RETRY_PROCESSING ); // try again, string it being processed
        else if( ContactPickerCore.searchIndex != null ) // Check to see if index has been built
        {
            if( ContactPickerCore.searchIndex.length >= 0 )
                return true;
        }
        else // Build index
            return this._indexList();
        
        return false;
    },
    _indexCallBack : function ()
    {
        // check status of global string
        var status = this._checkTransform();
        // update status
        if( status )
            this.fireEvent( "onsearchindexready", [this] );
    },
    _indexList : function ()
    {
        // lock the resource so no other instance transforms the string
        this._LOCKRESOURCE();
        // build index
        this._buildIndex();
        // unlock the resource
        this._UNLOCKRESOURCE();
        // set ready flag
        return true;
    },
    _LOCKRESOURCE : function ()
    {
        ///<summary>Locks the string resource while it is being processed.</summary>
        ContactPickerCore.lock = true;
    },
    _UNLOCKRESOURCE : function ()
    {
        ///<summary>Unlocks the string resource.</summary>
        ContactPickerCore.lock = false;
    }
}
Microsoft.Live.ContactPicker.SearchIndexer.createClass( "Microsoft.Live.ContactPicker.SearchIndexer" );
Events.addEventing( Microsoft.Live.ContactPicker.SearchIndexer );
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Framework\Debug.js" />
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Framework\Events.js" />
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Framework\Kernel.js" />
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Controls\Search.js" />
/// <reference path="C:\enlist\shared\server\shared\Product\UI\ContactPicker\src\js\Controls\ContactPickerCommon.js" />
WLHMType.registerNamespace("Microsoft.Live.ContactPicker");
Microsoft.Live.ContactPicker.ContactPicker = function ( instance_name )
{
    ///<summary>tabpage constructor. Instantiating this class will wire up the tab page.</summary>
    ///<param name="instance_name" type="String">the name of the instance.</param>
    
        
    // keep track of the instance name
    this._instanceName = instance_name;
    
    // keep an internal reference to the base DOM element
    this._base = $( this._instanceName + "$ContactPickerContainer" );
    
    
    // create circular reference to make inline event handling possible
    //this._base._ContactPicker = this;
    Control.create( this, this._base );

    // verify isVisible is in the correct state
    if( this._base.style.display.toLowerCase() == "none" )
        this.isVisible = false;
    
    // find the seleceted tab
    this._selectedTab = this._getSelectedTab();
    
    // init events for this instance
    Events.init( this );
    
    // attach search box
    this._search = new Microsoft.Live.ContactPicker.Search( this._instanceName, this );
    
    
    // get references to the different list containers
    this._peopleListContainer = $( this._instanceName + "$PeopleListContainer" );
    this._categoryListContainer = $( this._instanceName + "$CategoryListContainer" );
    this._favoriteListContainer = $( this._instanceName + "$FavoritesListContainer" );
    
    
    

    // get references to the different lists
    this._peopleList = $( this._instanceName + "$PeopleList" );
    this._categoryList = $( this._instanceName + "$CategoryList" );
    this._favoriteList = $( this._instanceName + "$FavoritesList" );
    
    
    
    
    // define constants
    this._initializeControl();
    
    // keep an internal array of favorites
    // this is done to make syncing between tabs easier
    this._favoritesContacts = [];
    
    this._selectAll = $( this._instanceName + "$SelectAllCheckbox" );
    this._selectAllCategory = $( this._instanceName + "$SelectAllCheckboxCategory" );
    
    
    
    this._selectedSelectAll = this._selectAll;

    // get a reference to the select all text    
    this._selectAllText = $( this._instanceName + "$SelectAllText" );
    
    
    // create dictionary for selected contacts
    this._selectedContacts = new CPDictionary();
    
    // create reference to hidden field for server selected contacts
    this._selectedContactsServer = $( this._instanceName + "$SelectedContacts" );
    
    // make sure the field is empty on render
    this._selectedContactsServer.value = "";
        
    // get reference to the lookup hash for this instance
    eval( String.format( "this._selectedLookup = {0}$Lookup;", this._instanceName ) );
    
    
    // preselected contacts
    this._preSelectedContacts = [];
    
    this._peopleListNoContacts = $( this._instanceName + "$NoContacts" );
    this._categoryListNoContacts = $( this._instanceName + "$NoCategories" );
    this._favoriteListNoContacts = $( this._instanceName + "$NoFavorites" );
    
    
    
    
    // create an instance of the searchindexer
    this._searchIndexer = new Microsoft.Live.ContactPicker.SearchIndexer();
    this._searchIndexer.attachEvent( "onsearchindexready", Delegate.create( this, this._onSearchIndexReady ) );
    // start the search indexer
    this._searchIndexer.start();
    
    // flag used to determine if the selectall checkbox is globally disabled
    this._suppressSelectAll = false;
}

Microsoft.Live.ContactPicker.ContactPicker.prototype =
{
    isVisible : true,
































    _onContactAddedDelegate : null,
    _onContactRemovedDelegate : null,
    _onCategoryExpandedDelegate : null,
    base : function ()
    {
        ///<summary>Returns a reference to the tabpage base element this class encapsulates.</summary>
        ///<returns type="DOMReference" />
        return this._base;
    },
    _initializeControl : function ()
    {
        // TODO: move out constants
        // state definitions
        this.READY = 0;
        this.NOT_READY = 1;
        // state variable
        this._state = this.NOT_READY;
    },
    _onSearchIndexReady : function ( sender, arguments )
    {   
        // populate data



        this._populateContacts(); // TODO: delay load in chunks







        this._populateCategories(); // TODO: delay load in chunks




        // make sure the hidden field is empty on render
        this._selectedContactsServer.value = "";

        // preselect contacts (if any)
        this._preselect();
        
        // add no contacts messages
        this._handleNoContacts();
           
        // remove load icon (this is temporary)
        $( this._instanceName + "$Loading" ).style.display = "none";
        
        // set the class state to ready
        this._state = this.READY;        
    },
    _handleNoContacts : function ()
    {
        if( this._peopleList.childNodes.length == 0 )
            this._peopleListNoContacts.style.display = "block";
        if( this._categoryList.childNodes.length == 0 )
            this._categoryListNoContacts.style.display = "block";
        if( this._favoriteList.childNodes.length == 0 )
            this._favoriteListNoContacts.style.display = "block";
    },
    _preselect : function ()
    {
        for( var i = this._preSelectedContacts.length; i--; )
            this.onContactAdded( this, this._preSelectedContacts[i] );
    },
    _populateCategories : function ()
    {
        // TODO: remove inline onmouse in/out
        var outerCategoryTemplate = "<div class=\"CategoryRowContainer CategoryUnselected\"><div class=\"CategorySubContainer\" onmouseover=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onMouseOverCategory', event, this);\" onmouseout=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onMouseOutCategory', event, this);\" onclick=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onToggleSelection', event);\"><div class=\"CategoryRow\"><div class=\"Cell1\">{0}</div><div class=\"Cell2\"><input class=\"Checkbox\" id=\"{1}\" type=\"checkbox\" /></div><div class=\"Cell3\"><span class=\"FontWeight\">{2}</span></div><div class=\"Cell4\">{3} {4}</div></div>";
        var categoryUserTemplate = "<div class=\"CategoryContainer\" style=\"display: none;\"><div class=\"CategoryRow\"><div class=\"Cell1\"></div><div class=\"Cell2\">{0}</div><div class=\"Cell3\">{1}</div><div class=\"Cell4\">{2}</div></div>";
        var userEmailTemplate = "<div class=\"CategoryRow\" style=\"display: none;\"><div class=\"SubCell\">{0}</div></div>";
        var expandString = "<a href=\"javascript:;\" onkeydown=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onToggle', event);if(event.keyCode != ContactPickerConstants.KeyCodes.TAB)return false;\"><img alt=\"" + ContactPickerStrings.expand + "\" class=\"Collapsed\" border=\"0\" onclick=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onToggle', event);\" src=\"" + ContactPickerStrings.expandImg + "\" /></a>";
        var categories = "";
        var contactLength = ContactPickerCore.contactData.length;
        
        // populate top level category names
        for( var i = ContactPickerCore.contactCount; i < contactLength; i++ )
        {
            var category = new AbchElement( i );
            
            var name = category.fullName;
            var users = category.users;
            var usersLength = users.length;
            var categoryRow = "";
            
            // check to see if contact should be preselected
            this._shouldPreselect(category);
            
            var regExStr = "(.*)\\{0\\}(.*)\\{1\\}(.*)\\{2\\}(.*)\\{3\\} \\{4\\}(.*)";
            var regEx = new RegExp( regExStr, "gi" );
            var replaceString = Microsoft.Live.ContactPicker.Util.concat( "$1", 
                (usersLength > 0 ? expandString : "&nbsp;"),
                "$2",
                this._instanceName,
                ",category,",
                i,
                "$3",
                "<span class=\"FontWeight\">",
                name,
                "</span>",
                "$4",
                usersLength,
                " ",
                (usersLength == 1 ? ContactPickerStrings.categoryCountSingular : ContactPickerStrings.categoryCountPlural),
                "$5" );
                
            categoryRow = outerCategoryTemplate.replace( regEx, replaceString );
            // populate users
            for( var j = 0; j < usersLength; j++ )
            {
                var contact = new AbchElement(users[j]);
                
                var emails = contact.getEmailArray();
                if (emails != null)
                {
                    var contactRow = ""; 
                    regExStr = "(.*)\\{0\\}(.*)\\{1\\}(.*)\\{2\\}(.*)";
                    regEx = new RegExp( regExStr, "gi" );

                    replaceString = Microsoft.Live.ContactPicker.Util.concat( "$1",
                        "&nbsp;",
                        "$2",
                        (contact.fullName == "" ? contact.email : contact.fullName),
                        "$3",
                        ((contact.isSpacesFriend && Microsoft.Live.ContactPicker.Util.isNullOrEmpty(contact.email) && !ContactPickerCore.hideEmails)? Microsoft.Live.ContactPicker.Util.getPrivateMessageString() : contact.email),
                        "$4" );
                    contactRow += categoryUserTemplate.replace( regEx, replaceString );                    
                }
                contactRow += "</div>";
                categoryRow += contactRow;
                delete contact;
            }
            
            delete category;
            categoryRow += "</div></div>";
            categories += categoryRow;
        }
        
        this._categoryList.innerHTML = categories;
    },
    _populateContacts : function ()
    {
        
        var contactLength = (ContactPickerCore.contactData.length - ContactPickerCore.categoryCount);

        var singleContactTemplate = "<div class=\"RowContainer Visibility\"><div class=\"Row Unselected\" onmouseover=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onMouseOverContact', event, this);\" onmouseout=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onMouseFromContact', event, this);\" onclick=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onToggleSelection', event);\"><div class=\"Cell1\">{0}</div><div class=\"Cell2\"><input class=\"Checkbox\" id=\"{1},0\" type=\"checkbox\" /></div><div class=\"Cell3\">{2}</div><div class=\"Cell4\">{3}</div></div>";
        var expandString = "<a href=\"javascript:;\" onkeydown=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onToggle', event);if(event.keyCode != ContactPickerConstants.KeyCodes.TAB)return false;\"><img alt=\"" + ContactPickerStrings.expand + "\" class=\"Collapsed\" border=\"0\" onclick=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onToggle', event);\" src=\"" + ContactPickerStrings.expandImg + "\" /></a>";
        var singleSubTemplate = "<div class=\"Row Unselected\" onmouseover=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onMouseOverContact', event, this);\" onmouseout=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onMouseFromContact', event, this);\" onclick=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.ContactPicker', '_onToggleSelection', event);\"><div class=\"Cell1\">&nbsp;</div><div class=\"Cell2\"><input class=\"Checkbox\" id=\"{0},{1}\" tabIndex=\"-1\" type=\"checkbox\" /></div><div class=\"FontSecondaryColor Cell3\">{2}</div><div class=\"Cell4\">{3}</div></div>";
        var people = ""; 
        var favorite = "";

        for( var i = 0; i < contactLength; i++ )
        {
            var contact = new AbchElement( i );
            
            var emails = contact.getEmailArray();
            var emailsLength =  emails.length;
            var contactRow = "";
            
            // check to see if we should pre select this contact
            this._shouldPreselect(contact);
            
            // single/multiple email contact 
            var regExStr = "(.*)\\{0\\}(.*)\\{1\\}(.*)\\{2\\}(.*)\\{3\\}(.*)";
            var regEx = new RegExp( regExStr, "gi" );
            
            var replaceString = Microsoft.Live.ContactPicker.Util.concat( "$1", 
                (emailsLength > 1 ? expandString : "&nbsp;"),
                "$2",
                this._instanceName,
                ",{favorite}", // so we can have unique ids between favorites and people
                i,
                "$3",
                (contact.isGroup ? "<span class=\"FontWeight\">" + contact.fullName + "</span>" : (contact.fullName == "" ? contact.email : contact.fullName) ),
                "$4",
                ((contact.isSpacesFriend && Microsoft.Live.ContactPicker.Util.isNullOrEmpty(emails[0]) && !ContactPickerCore.hideEmails) ? Microsoft.Live.ContactPicker.Util.getPrivateMessageString() : emails[0]),
                "$5" );
                
            contactRow = singleContactTemplate.replace( regEx, replaceString );
            // append extra emails if needed
            for( var j = 1; j < emailsLength; j++ )
            {
                var regExStr = "(.*)\\{0\\},\\{1\\}(.*)\\{2\\}(.*)\\{3\\}(.*)";
                var regEx = new RegExp( regExStr, "gi" );

                replaceString = Microsoft.Live.ContactPicker.Util.concat( "$1",
                    this._instanceName,
                    ",{favorite}", // so we can have unqie ids between favorites and people
                    i,
                    ",",
                    j,
                    "$2",
                    ( contact.fullName != "" ? contact.fullName : emails[j] ),
                    "$3",
                    emails[j],
                    "$4" );
                contactRow += singleSubTemplate.replace( regEx, replaceString );
            }
            contactRow += "</div>";
            
            // clean up
            delete contact;

            // append row
            // strip the token {favorite} from people contacts
            people += contactRow.replace(/\{favorite\}/g, "people,");
            if( contact.isFavorite )
            {
                // replace {favorite} with _favorite
                favorite += contactRow.replace(/\{favorite\}/g, "favorite,");
                // store the index in the favorites array
                this._favoritesContacts[i] = true;
            }
        }
        
        this._peopleList.innerHTML = people;
        this._favoriteList.innerHTML = favorite;  
    },
    _shouldPreselect : function (contact)
    {
        

        // look up by guid first
        if( this._selectedLookup[contact.serverSideID] == true )
        {
            var selections = 0;
            if( !contact.isCategory && !contact.isGroup )
            {
                // look through emails to see which emails to select for the given contact
                var emails = contact.getEmailArray();
                for( var i = emails.length; i--; )
                {
                    if( this._selectedLookup[emails[i].toLowerCase()] == true )
                    {
                        selections ++;
                        var abchElt = new AbchElement( contact.getClientId(), false, i );
                        this._preSelectedContacts[this._preSelectedContacts.length] = abchElt;
                    }
                }
            }
            // if no selections happened, assume the primary email
            if( selections == 0 )
                this._preSelectedContacts[this._preSelectedContacts.length] = contact;
        }
        // if guid didn't yeild results, try just by email only if contact is not a category/group
        // TODO: update this when groups (cricles) have emails
        else if( !contact.isCategory && !contact.isGroup )
        {
            var emails = contact.getEmailArray();
            for( var i = emails.length; i--; )
            {
                if( this._selectedLookup[emails[i].toLowerCase()] == true )
                {
                    var abchElt = new AbchElement( contact.getClientId(), false, i );
                    this._preSelectedContacts[this._preSelectedContacts.length] = abchElt;
                }
            }
        }
        else
        {
            // try match by name if the contact is a group or category
            if( this._selectedLookup[contact.fullName.toLowerCase()] == true)
                this._preSelectedContacts[this._preSelectedContacts.length] = contact;
        }
    },    
    // occurs when the select all check box is checked
    _selectAllHandler : function ()
    {
        if( this._state != this.READY )
            return;



        var checkboxElt = event.srcElement;
        var bChecked = checkboxElt.checked;
        
        switch( this._selectedTabName )
        {
            case "PeopleTab":
                this.fireEvent( ( bChecked ? "onAllSelected" : "onAllDeselected" ), [this, ContactPickerConstants.Constants.PEOPLE] );
                this._checkAll( this._peopleList, bChecked );
                break;
            case "CategoryTab":
                this.fireEvent( ( bChecked ? "onAllSelected" : "onAllDeselected" ), [this, ContactPickerConstants.Constants.CATEGORIES] );
                this._checkAll( this._categoryList, bChecked, true );
                break;
            case "FavoritesTab":
                this.fireEvent( ( bChecked ? "onAllSelected" : "onAllDeselected" ), [this, ContactPickerConstants.Constants.FAVORITES] );
                this._checkAll( this._favoriteList, bChecked );
                break;
            default:
                
                break;
        }




    },
    _checkAll : function ( list, bChecked, isCategory )
    {
        // TODO: see if you can speed this up with a regex replace.
        var elts = $tag( list, "input" );
        for( var i = elts.length; i--; )
        {
            this._updateCheckedElement( 
                (isCategory ? elts[i].parentNode.parentNode.parentNode.parentNode : elts[i].parentNode.parentNode), // the price we pay for category expansion :(
                elts[i], 
                isCategory, 
                bChecked );
            // sync only if not a category
            if( !isCategory)
                this._syncSelection( elts[i] );
        }
    },
    
    _onMouseOverCategory : function (sender)
    {
        if (!sender)
        {
            return;
        }
        
        var parent = sender.parentNode;
        if ($containsClass(parent, "CategorySelected"))
        {
            return;
        }
        $addClass(parent, "CategoryHover");        
    },
    
    _onMouseOutCategory : function (sender)
    {
        if (!sender)
        {
            return;
        }    
        
        var parent = sender.parentNode;
        if (parent._selected == 'false' || !parent._selected)
        {
            parent.className = 'CategoryRowContainer CategoryUnselected';
        }
    },
    
    // occurs when mouse hover over a contact
    _onMouseOverContact : function (sender)
    {
        if (!sender)
        {
            return;
        }
        
        if ($containsClass(sender, "Selected"))
        {
            return;
        }
        $addClass(sender, "Hover");
    },
    // occurs when mouse leaves the contact
    _onMouseFromContact : function (sender)
    {
        // firefox and safari only
        if( Microsoft.Live.ContactPicker.Util.getBrowser() != ContactPickerConstants.Constants.IE )
        {
            var checked = sender.children[1].children[0].checked;
            
            if (!checked)
            {
                sender.className = 'Row Unselected';
            }
        }      
        else if (sender._selected == "false" || !sender._selected)
        {
            sender.className = 'Row Unselected';
        }
        else
        {}
    },
    _onTabChanged : function ()
    {
        // TODO: consider using rafi's functions for checking class names/replacing them
        // TODO: elt.parentNode.parentNode.parentNode is too fragile, wrap this in a function that will take care of it
        // and try to make it resilient to html structure changes
        



        var elt = window.event.srcElement;
        
        // new, improved way to handle finding the parent node.
        // the old "if" way had a bug. :(
        // TODO: investigate using a custom attribute to make this this supa resilient to structure change
        while( elt.tagName.toLowerCase() != "li" )
            elt = elt.parentNode;
            
        if( elt == this._selectedTab )
            return;        
        elt.className = "Selected";
        
        this._selectedTab.className = "";
        this._selectedTab = elt;
        this._selectedTabName = this._getTabName();
        // update the ui
        this._updateUI();




    },
    _onToggle : function (srcElt)
    {



        
        var elt = (srcElt||event.srcElement);

        if( elt.tagName.toLowerCase() == "a" )
        {
            // only do stuff if the return was pressed
            if( event.keyCode != ContactPickerConstants.KeyCodes.RETURN )
                return;
            elt = elt.firstChild;
            
        }
        
        // get the class name so we can determine the state of the calling node
        var className = elt.className;

        switch( className )
        {
            case "Collapsed":
                // set the tooltip text
                elt.title = ContactPickerStrings.collapse;
                elt.className = "Expanded";
                elt.src = ContactPickerStrings.collapseImg;
                this._toggleAllSubNodes( elt, 0 );
                break;
            case "Expanded":
                // set the tooltip text
                elt.title = ContactPickerStrings.expand;
                elt.className = "Collapsed";
                elt.src = ContactPickerStrings.expandImg;
                this._toggleAllSubNodes( elt, -1 );
                break;
            default:
                
                break;
        }




    },
    _toggleAllSubNodes : function ( elt, index )
    {
        
        // find the root element for the row
        var nextElt = elt.parentNode.parentNode.parentNode;
            
        // get the container for the rows
        var containerElt = nextElt.parentNode;
        
        // handle category clicks
        if( containerElt.className.indexOf( "CategorySubContainer" ) != -1 || containerElt.className == "CategoryContainer" )
        {
            this._toggleAllCategorySubNodes( containerElt, (index == 0 ? "block" : "none") );
            return;
        }
        
        
    
        if( index == -1 )
            containerElt.className = "RowContainer Visibility"; // hide all elements
        else
            containerElt.className = "RowContainer"; // show all elements
        
        // set the tab index
        // if elements are hidden, don't allow user to tab through
        while( nextElt = nextElt.nextSibling )
            nextElt.childNodes[1].firstChild.tabIndex = index;
    },
    _toggleAllCategorySubNodes : function ( elt, style )
    {
        
                
        
        var nextElt = elt.firstChild;
        while( nextElt = nextElt.nextSibling )
        {
            nextElt.style.display = style;
        }
    },
    _onToggleSelection : function ()
    {



        var isCategory = false;
        var sender = event.srcElement;
        var tagName = sender.tagName.toLowerCase();     
        
        
        
        // handle chevron toggling first
        if( this._shouldToggleExpand( sender ) )
            return;

        // check to see if the user clicked on the img or the check box
        if( tagName == "img" )
            return;
        else if( tagName == "input" )
        {
            var checkboxElt = sender;
            sender = sender.parentNode.parentNode;
            
            // detect a category toggle
            if( sender.className.indexOf( "CategoryRow" ) != -1 )
            {
                isCategory = true;
                while( sender.className.indexOf( "CategoryRowContainer" ) == -1 )
                    sender = sender.parentNode;
            }
            // ontoggleselection happens after onmouseup (the element is already toggled)
            // so I pass in the value of the selected element and let the code handle it.
            // this can be done clearner and will be revisited.
            this._updateCheckedElement( sender, checkboxElt, isCategory, checkboxElt.checked );

            // sync the selection
            this._syncSelection( checkboxElt );
            
            // fire update event
            this._updateAssociatedControl( checkboxElt );
            
            return;
        }
        else if( sender.className.indexOf( "CategoryRow" ) != -1 )
        {
            isCategory = true;
            while( sender.className.indexOf( "CategoryRowContainer" ) == -1 )
                sender = sender.parentNode;        
        }
        else if( sender.className.indexOf( "Row" ) != -1 ) // we're money, just check the checkbox
        { }
        else // ack, find the root node first
        {
            while( sender.className.indexOf( "Row" ) == -1 )
                sender = sender.parentNode;
            // check to see if this is a category toggle
            if( sender.className.indexOf( "CategoryRow" ) != -1 )
            {
                isCategory = true;
                while( sender.className.indexOf( "CategoryRowContainer" ) == -1 )
                    sender = sender.parentNode;
            }
        }
        // find the check box
        if(!isCategory)
            var checkboxElt = sender.childNodes[1].firstChild;
        else
            var checkboxElt = sender.firstChild.firstChild.childNodes[1].firstChild;

        
                    
        // update ui
        this._updateCheckedElement( sender, checkboxElt, isCategory );
        
        // sync the selection
        this._syncSelection( checkboxElt );
        
        //fire update event
        this._updateAssociatedControl( checkboxElt );




    },
    _shouldToggleExpand : function (sender)
    {
        if( sender.className.indexOf( "Cell1" ) != -1 )
        {
            // find the img cell
            var toggleElt = sender;
            while( toggleElt )
            {   
                if( toggleElt.nodeType == 1 )
                {
                    if( toggleElt.tagName.toLowerCase() == "img" )
                        break;
                }
                toggleElt = toggleElt.firstChild;
            }
            // toggle the chevron
            if( toggleElt )
            {
                this._onToggle( toggleElt );
                return true;
            }
        }
        return false;
    },
    _updateAssociatedControl : function ( checkboxElt )
    {
        
        var id = checkboxElt.id;
        var tokens = id.split( ',' );
        
        var eventElt = null;
        if( tokens.length == 4 )
            eventElt = new AbchElement( tokens[2], false, tokens[3] );
        else if( tokens.length == 3 )
            eventElt = new AbchElement( tokens[2], false );
        else
        {
        }
        
        if( checkboxElt.checked )
            this.fireEvent( "onContactAdded", [this, eventElt] );
        else
            this.fireEvent( "onContactRemoved", [this, eventElt] );
    },
    _updateCheckedElement : function ( sender, checkbox, isCategory, isChecked )
    {
        
        if( typeof( isChecked ) != "undefined" )
            checkbox.checked = isChecked; // set the check state, the isChecked argument is optional and used only for syncing
        else
            checkbox.checked = !checkbox.checked; // toggle the checkbox
        // uncheck the select all is the checkbox is unchecked and the selectall is checked
        if( this._selectedSelectAll.checked && !checkbox.checked )
            this._selectedSelectAll.checked = false;
        
        // keep track of selected elements
        this._updateSelectedItems( checkbox );
        
        var className = ( isCategory == true ? "CategoryRowContainer Category" : "Row " );
        className += ( checkbox.checked == true ? "Selected" : "Unselected" );
        // the _selected property is used onmouseover and onmouseout. 
        // when set to true, onmouseout won't try to remove the highlighting
        sender._selected = checkbox.checked;
        sender.className = className;

    },
    _updateSelectedItems : function ( checkboxElt )
    {
        var idAttr = checkboxElt.getAttribute( "id" );
        var tokens = idAttr.split( ',' );
        var length = tokens.length;
        var id = tokens[2] + (length == 4 ? "," + tokens[3] : "");
        if( checkboxElt.checked )
        {
            this._updateServerSelectedContacts( id, true );        
            this._selectedContacts.add( id, true );         
        }
        else
        {
            this._selectedContacts.remove( id );
            this._updateServerSelectedContacts( id, false );
        }
    },
    _updateServerSelectedContacts : function ( id, isAdd )
    {
        
        var idTokens = id.split( ',' );
        var length = idTokens.length;
        
        // get a reference to the element
        var abchElt = null;
        if( idTokens.length == 2 )
            abchElt = new AbchElement( idTokens[0], false, idTokens[1] );
        else
            abchElt = new AbchElement( id );
        
        // build contact structure
        var serverEltString = [ "[",
            abchElt.serverSideID,
            ";",
            escape(abchElt.fullName),
            ";",
            escape(abchElt.email),
            ";",
            abchElt.isSpacesFriend,
            ";",
            abchElt.isGroup,
            ";",
            abchElt.isFavorite,
            ";",
            abchElt.isCategory,
            "]" ].join('');
            
        // avoid adding duplicates (caused by multiple sync code paths)
        if( !this._selectedContacts.getItem( id ) && isAdd )
            this._selectedContactsServer.value = (this._selectedContactsServer.value == "" ? "" : this._selectedContactsServer.value + ",") + serverEltString;
        if( !isAdd )
        {
            /*
             * This is done to remove
             * a contact from the input value
             * there are 3 cases as showed below
             * all are necessary in that order
             * line 1: say i want to removed [2]
             * [1],[2]
             * line 2: i want to remove [1]
             * [1],[2]
             * line 3: i want to remove the only item in a list
             * [*]
             */
            this._selectedContactsServer.value = this._selectedContactsServer.value.replace( "," + serverEltString, "" );
            this._selectedContactsServer.value = this._selectedContactsServer.value.replace( serverEltString + ",", "" );
            this._selectedContactsServer.value = this._selectedContactsServer.value.replace( serverEltString, "" );
        }
    },
    _updateUI : function ()
    {
        

        // clear search field
        this._search.clear();
        switch( this._selectedTabName )
        {
            case "PeopleTab":
                if (!this._suppressSelectAll)
                {
                    this._manageSelectAllCheckbox( false );
                }
                
                this._search.hideOverlay();
                this._search.clear();
                this._updatePageVisibility("block", "none", "none");
                Microsoft.Live.ContactPicker.Util.instrumentWrapper(ContactPickerConstants.Instrumentation.PeopleTab);
                break;
            case "CategoryTab":
                if (!this._suppressSelectAll)
                {
                    this._manageSelectAllCheckbox( true );
                }
                this._search.showOverlay();
                this._search.clear();
                this._updatePageVisibility("none", "block", "none");
                Microsoft.Live.ContactPicker.Util.instrumentWrapper(ContactPickerConstants.Instrumentation.CategoryTab);
                break;
            case "FavoritesTab":
                if (!this._suppressSelectAll)
                {
                    this._manageSelectAllCheckbox( false );
                }
                this._search.hideOverlay();
                this._search.clear();
                this._updatePageVisibility("none", "none", "block");
                Microsoft.Live.ContactPicker.Util.instrumentWrapper(ContactPickerConstants.Instrumentation.FavoritesTab);
                break;
            default:
                
                break;
        }
    },
    _manageSelectAllCheckbox : function ( isCategory )
    {
        // show the shared select all if the user isn't looking at the category tab
        this._selectAll.style.display = ( isCategory ? "none" : "block" );
        // show the category select all if the user is looking at the category tab
        this._selectAllCategory.style.display = ( isCategory ? "block" : "none" );
        // update the selected check box
        this._selectedSelectAll = ( isCategory ? this._selectAllCategory : this._selectAll );
        // always reset the checkbox when changing tabs when changing to people or favorites?
        this._selectedSelectAll.checked = this._shouldChecked( isCategory );
    },
    _shouldChecked : function (isCategory)
    {
        if( !isCategory )
        {
            var searchPool = ( this._selectedTabName == "PeopleTab" ? this._peopleList : this._favoriteList ).innerHTML;
            // if there are no contacts, do nothing
            if( searchPool == "" )
                return false;
            var regExStr = "class=[\"]?Row Unselected[\"]?";
            var regEx = new RegExp( regExStr, "gi" );
            var match = regEx.exec( searchPool );
            if( match )
                return false;
            
            return true;
        }
        else
        {
            var categoryCount = this._categoryList.childNodes.length;
            var contacts = this.getSelectedContacts();
            var categoriesSelectedCount = 0;
            for( var i = contacts.length; i--; )
            {
                if( contacts[i].isCategory )
                    categoriesSelectedCount ++;
            }
            
            return (categoryCount > 0 && (categoryCount == categoriesSelectedCount));
        }
        
        return false;
    },
    _updatePageVisibility : function ( people, category, favorite )
    {
        this._peopleListContainer.style.display = people;
        this._categoryListContainer.style.display = category;
        this._favoriteListContainer.style.display = favorite;
    },
    _syncSelection : function ( checkboxElt )
    {
        // TODO: fire events        
        
        
        // check to see if the sync is coming from a search result
        if( this._isSyncFromSearchResult( checkboxElt ) )
            checkboxElt = this._syncSearchResult( checkboxElt ); // sync with my current tab
        
        switch( this._selectedTabName )
        {
            case "PeopleTab":
                this._syncWithFavorites( checkboxElt );
                break;
            case "CategoryTab":
                break;
            case "FavoritesTab":
                this._syncWithPeople( checkboxElt );
                break;
            default:
                
                break;
        }        
    },
    _syncSearchResult : function ( checkboxElt )
    {
        var client_id = checkboxElt.getAttribute( "id" );
        var client_id_tokens = client_id.split( "," );
        client_id_tokens[1] = ( this._selectedTabName == "PeopleTab" ? "people" : "favorite" );
        client_id = client_id_tokens.join( "," );
        this._syncUpdate( client_id, checkboxElt.checked );
        
        return $( client_id ); // return the correct checkbox in the tab, so the rest of _syncSelection will work naturally    
    },
    _isSyncFromSearchResult : function ( checkboxElt )
    {
        var client_id = checkboxElt.getAttribute( "id" );
        return (client_id.indexOf( "results" ) != -1 ? true : false);
    },
    _syncUpdate : function ( id, isChecked )
    {
        var elt = $( id );
        if( elt == null )
            return; // die gracefully if the element somehow isn't found
        
        this._updateCheckedElement( elt.parentNode.parentNode, elt, false, isChecked ); 
    },
    _syncWithPeople : function ( checkboxElt )
    {
        var client_id = this._getFavoritesId( checkboxElt );
        this._syncUpdate( client_id, checkboxElt.checked );    
    },
    _syncWithFavorites : function ( checkboxElt )
    {
        var client_email_id = checkboxElt.getAttribute( "id" );
        var client_id_tokens = client_email_id.split( "," );
        var client_id = client_id_tokens[2];
        // is this contact a favorites contact?
        if( this._favoritesContacts[client_id] == true )
        {
            client_id = this._getPeopleId( checkboxElt );
            this._syncUpdate( client_id, checkboxElt.checked );
        }        
    },
    _getFavoritesId : function ( checkboxElt )
    {
            
        var client_email_id = checkboxElt.getAttribute( "id" );
        
        
        return client_email_id.replace(/favorite,/g, "people," );
    },
    _getPeopleId : function ( checkboxElt )
    {
        var client_email_id = checkboxElt.getAttribute( "id" );
        return client_email_id.replace(/people,/g, "favorite," );
    },
    _getSelectedTab : function ()
    {
        // get the first tab
        var start_elt = $( this._instanceName + "$PeopleTab" );
        
        
        // step through until the selected tab is found
        while( start_elt )
        {
            if( start_elt.className )
            {
                // set the name of the tab
                this._selectedTabName = this._getTabName( start_elt );
                return start_elt;
            }
            start_elt = start_elt.nextSibiling;
        }
        
        return null;
    },
    _getTabName : function ( tabRef )
    {
        
        
        return (tabRef || this._selectedTab).id.substring((this._instanceName.length + 1));
    },
    dispose : function ()
    {
        ///<summary>Clean up, including detaching any events.</summary>
        // remove circular reference
        Control.destroy( this );
        if (this._search)
        {
            this._search.dispose();
        }
        this._base = null;
    },
    hide : function ()
    {
        ///<summary>Hides the tab page.</summary>
        this._base.style.display = "none";
        this.isVisible = false;
    },
    show : function ()
    {
        ///<summary>Shows the tab page.</summary>       
        this._base.style.display = "block";
        this.isVisible = true;
        this._search.focus();
    },
    
    reAssociateControl : function (newAutoCompleteControl)
    {
        ///<summary>
        ///Used when the host page needs to reassociate this instance of CP
        ///with a different instance of AC.
        ///</summary>       
                
        if(!this._onContactAddedDelegate || !newAutoCompleteControl)        
            return;
        //clear selections and detach events
        this.clearAll();                  
        if(this._associatedControl)
        {                
            this._associatedControl.detachEvent( "onContactAdded", this._onContactAddedDelegate);
            this._associatedControl.detachEvent( "onContactRemoved", this._onContactRemovedDelegate );
            this._associatedControl.detachEvent( "onCategoryExpand",  this._onCategoryExpandedDelegate);
            this._associatedControl.detachAssociatedCPEvents();
        }
                   
        //resynch
        var eltCount = newAutoCompleteControl.selectedAbchElements.length;
        for(var i = eltCount;i--;)
        {
            this.onContactAdded(null,newAutoCompleteControl.selectedAbchElements[i])        
        }                
        //wire up events with new ac control
        this.associateControl(newAutoCompleteControl);
        newAutoCompleteControl.associateControl(this);        
    },    
    
    getSourceList : function ()
    {
        var id = (this._instanceName + "$PeopleList");
        return $( id );
    },    
    // returns a the current list based on tab
    // if bResults is true, the results list is returned
    getWorkingList : function ( bResults )
    {
        switch( this._selectedTabName )
        {
            case "PeopleTab":
                var id = ( bResults ? (this._instanceName + "$PeopleResultsList") : (this._instanceName + "$PeopleList") );
                return $( id );
                break;
            case "CategoryTab":
                
                break;
            case "FavoritesTab":
                var id = ( bResults ? (this._instanceName + "$FavoritesResultsList") : (this._instanceName + "$FavoritesList") );
                return $( id );
                break;
            default:
                
                break;
        }
        
        return null;
    },
    _hideSelectAll : function ()
    {
        this._selectedSelectAll.style.display = "none";
        this._selectAllText.style.display = "none";
    },
    hideSelectAll : function ()
    {
        this._hideSelectAll();
        this._suppressSelectAll = true;
    },
    _showSelectAll : function ()
    {
        if( this._suppressSelectAll )
            return;
        this._selectedSelectAll.style.display = "block";
        this._selectAllText.style.display = "block";            
    },
    showSelectAll : function ()
    {
        this._suppressSelectAll = false;
        this._showSelectAll();
    },
    // 
    associateControl : function ( control )
    {
        
        // store a reference to the control CP will sync with
        this._associatedControl = control;       
                
        //store a reference to the delegate so we can detach events later when we reassociate the controls
        this._onContactAddedDelegate = Delegate.create( this, this.onContactAdded );
        this._onContactRemovedDelegate = Delegate.create( this, this.onContactRemoved );
        this._onCategoryExpandedDelegate = Delegate.create( this, this.onCategoryExpand );                
         // attach events so cp can handle them
        this._associatedControl.attachEvent( "onContactAdded", this._onContactAddedDelegate);
        this._associatedControl.attachEvent( "onContactRemoved", this._onContactRemovedDelegate );
        this._associatedControl.attachEvent( "onCategoryExpand",  this._onCategoryExpandedDelegate);

    },
    makeCheckboxId : function ( type, eltId, emailId )
    {
        if( type == ContactPickerConstants.Constants.PEOPLE )
        {
            return Microsoft.Live.ContactPicker.Util.concat(
                this._instanceName,
                ",people,",
                eltId,
                ",",
                emailId 
            );
        }
        else if( type == ContactPickerConstants.Constants.CATEGORIES )
        {
            return Microsoft.Live.ContactPicker.Util.concat(
                this._instanceName,
                ",category,",
                eltId 
            );
        }
        else
        {
                
        }
        return null;        
    },
    onContactAdded : function ( sender, args )
    {
        var eltId = args.getClientId();
        if( eltId == ContactPickerConstants.Constants.NONE )
            return;
        var checkboxElt = null;
        var isCategory = AbchElement.isCategory( eltId );
        var emailId = (isCategory ? null : args.emailIndex);
        
        var id = this.makeCheckboxId( 
            (isCategory ? ContactPickerConstants.Constants.CATEGORIES : ContactPickerConstants.Constants.PEOPLE),
            eltId,
            emailId
        );
        
        // update the contact with this event data
        this._updateContactFromEvent( id, isCategory, true );
    },
    onContactRemoved : function ( sender, args )
    {
        var eltId = args.getClientId();
        if( eltId == ContactPickerConstants.Constants.NONE )
            return;
        var checkboxElt = null;
        var isCategory = AbchElement.isCategory( eltId );
        var emailId = (isCategory ? null : args.emailIndex);
        
        var id = this.makeCheckboxId( 
            (isCategory ? ContactPickerConstants.Constants.CATEGORIES : ContactPickerConstants.Constants.PEOPLE),
            eltId,
            emailId
        );
        
        // update the contact with this event data
        this._updateContactFromEvent( id, isCategory, false );
    },
    _updateContactFromEvent : function ( id, isCategory, isChecked )
    {
        var checkboxElt = $( id );
        if( isCategory )
        {
            var sender = checkboxElt;
            while( sender.className.indexOf( "CategoryRowContainer" ) == -1 )
                sender = sender.parentNode;
            
            this._updateCheckedElement( sender, checkboxElt, true, isChecked );            
        }
        else
        {
            checkboxElt.checked = isChecked;
            this._syncWithPeople( checkboxElt );
            this._syncWithFavorites( checkboxElt );        
        }        
    },    
    onCategoryExpand : function ( sender, args )
    {
        var eltId = args.getClientId();
        if( eltId == ContactPickerConstants.Constants.NONE )
            return;
        var checkboxElt = null;
        
        var elts = args.users;
        var eltsLength = elts.length;
        
        // deselect the category
        this.onContactRemoved( this, args );
        for( var i = eltsLength; i--; )
        {
            var abchElt = new AbchElement( elts[i] );
            this.onContactAdded( this, abchElt ); // use oncontactadded to minimize code duplication
        }   
    },
    __getSelectContactsDictionary : function ()
    {
        return this._selectedContacts;
    },
    getSelectedContacts : function ()
    {
        var dictionary = this._selectedContacts.getPrimitive();
        var contacts = [];
        var length = 0;
        for( keys in dictionary )
        {
            var tokens = keys.split( ',' );
            if( tokens.length == 2 )
                contacts[length] = new AbchElement( tokens[0], false, tokens[1] );
            else
                contacts[length] = new AbchElement( tokens[0] );

            length ++;
        }
        
        return contacts;
    },
    
    clearAll : function ()
    {    
        this._checkAll( this._peopleList, false);
        this._checkAll( this._categoryList, false, true );
        this._checkAll( this._favoriteList, false );       
    }
}
Microsoft.Live.ContactPicker.ContactPicker.createClass( "Microsoft.Live.ContactPicker.ContactPicker" );
Events.addEventing( Microsoft.Live.ContactPicker.ContactPicker );

/// <reference path="ContactPickerCommon.js/>
/// <reference path="../Framework/Control.js/>
/// <reference path="../Framework/Types.js/>
/// <reference path="../Framework/Events.js/>
/// <reference path="../Framework/Kernel.js/>

//AutoComplete Control
//NOTE: WLHMType must become only 'Type' in the Hotmail Codebase.
WLHMType.registerNamespace("Microsoft.Live.ContactPicker");
Microsoft.Live.ContactPicker.AutoComplete = function (instanceName)
{
    
    
    // keep track of the instance name
    this._instanceName = instanceName;
    
    // keep an internal reference to the base DOM element
    this._elt = $( this._instanceName + "$AutoComplete" );
    
        
    //create a reference in the dom to this instance of the control to make eventing possible
    Control.create( this, this._elt );
    
    this._input = $(this._instanceName + "$InputBox");   
    
    //fails in Firefox if input and tag are put inside an <li> tag:
    //this._input = Control.getByTag(this._elt,"SearchInput");
    //Due to other issues as well and a need to stay consistent with contact picker, moving back to traditional instancing approach generally
    
   
    this._resultsContainer = $(this._instanceName + "$ResultsContainer");
    
    
    // keep a reference to the input div (the div holding the blocks that is made to look like an input box)
    this._inputDiv = $(this._instanceName + "$InputDiv");
    
    
    this._inputUL = $(this._instanceName + "$InputUL");
    this._inputULInitialContents = this._inputUL.innerHTML;
            

    this._inputBoxLi = $(this._instanceName + "$InputBoxLi");
            
    
    this._browserType = Microsoft.Live.ContactPicker.Util.getBrowser();
        
    // init events for this instance
    Events.init( this );      
               
    this.selectedAbchElements = [];  
    
    // create reference to hidden field for server selected contacts
    this._selectedContactsServer = $( this._instanceName + "$SelectedContacts" );
    
          
    // get reference to the lookup hash for this instance
    eval( String.format( "this._selectedLookup = {0}$Lookup;", this._instanceName ) );
      
    
    // get reference to the free selected email array
    eval( String.format( "this._freeSelectedLookup = {0}$FreeSelected;", this._instanceName ) );
      
    
    //make results div go away when user clicks anywhere off of it
    document.attachEvent('onclick',Delegate.create( this, this._clearResults ));
    
    // initialize these if they are in the #DEBUG block
    this._setTimeOutHandle = 0;
    this._lastInput = "";
    
    // get a reference to the text ruler
    this._textRuler = $(this._instanceName +"$TextRuler");
    
    this._currentKeyFocusElt = -1;
    this._previousSelectedElt = null;
    
    // create an instance of the searchindexer
    this._searchIndexer = new Microsoft.Live.ContactPicker.SearchIndexer();    
    this._searchIndexer.attachEvent( "onsearchindexready", Delegate.create( this, this._onSearchIndexReady ) );
    this._searchIndexer.start();
}

Microsoft.Live.ContactPicker.AutoComplete.prototype =
{        
    //private properties  
    _resultRowCount:0,
    _currentAutoCompleteSelectedIndex : ContactPickerConstants.KeyCodes.NONE_SELECTED,
    _lastAutoCompleteSelectedIndex : ContactPickerConstants.KeyCodes.NONE_SELECTED,     
    _isWaitingOnReadyNotification:false,
    _isReady:false,
    _scrollBottomAnchor:0,
    _uniqueElementID:0,
    _inputULInitialContents : "",    
    _origMarkup:"",
    _countIt:0,
    _onContactAddedDelegate : null,
    _onContactRemovedDelegate : null,
    _onCategoryExpandedDelegate : null,
    _onAllSelectedDelegate : null,
    _onAllDeselectedDelegate : null,
    _associatedControl : null,
    pasteDelimRegEx : ";|,|\\t|\\n",
    _emailPattern : "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
    _keyHandled : false,





















    populateInput : function (string)
    {
        if (Microsoft.Live.ContactPicker.Util.isNullOrEmpty(string))
        {
            return;
        }
        this._input.value = string;
        this._doSearch();
    },

    _measureString : function (string)
    {
        if (string == " " || string == "")
        {
            // hardedcoded default value
            return 50;
        }
        // so we don't put script in the span elt
        var processed = Microsoft.Live.ContactPicker.Util.htmlEncode(string);
        
        this._textRuler.innerHTML = processed;
        var width = this._textRuler.offsetWidth;
        // add 25 px for buffer
        return width + 25;
    },
    
    _scrollInputToBottom : function ()
    {
        this._inputDiv.scrollTop = this._inputDiv.scrollHeight;
    },
    
    _onChangeHandler : function ()
    {
        ///<summary>Routine for Firefox2 ONLY.  Handles the oninput event, the only event in Firefox2 to fire on a paste.
        ///This fires on every change of the text box's contents in Firefox and Safari.
        ///</summary>
        
        if (this._browserType != ContactPickerConstants.Constants.FIREFOX)
        {
            return;
        }
        
        var isPaste = false;
        var delta = this._input.value.length - this._lastInput.length;
        // there are some cases in here that do nothing
        // i choose to leave them defined incase we need them later.
        if (!delta)
        {
            if (this._input.value != this._lastInput)
            {
                // no length change, but 1 character changes
                isPaste = true;
            }
            else
            {
                isPaste = false;
                // no change
            }
        }
        else if (-1 <= delta && delta <= 1)
        {
            /*
            // the change was on character long
            if (this._input.value != "")
            {
                // a character was added or deleted
                // but we are not empty
            }
            else
            {
                // the contents of the input box are empty
            }
            */
            isPaste = false;
        }
        else if (delta > 1)
        {
            // this looks like a paste
            isPaste = true;
        }
        else
        {
            /*
            if (this._input.value != "")
            {
                // this is a cut or delete
            }
            else
            {
                // the input box was left empty
            }*/
            isPaste = true;            
        }
        
        this._lastInput = this._input.value;
        if (isPaste)
        {
            this._setInputWidth(this._input.value);
            this._doPaste();
        }
        else
        {
            this._doSearch();
        }
    },
    
    _pasteHandler : function ()
    {       
        // Let firefox 3 work the same way firefox 2 does
        if (this._browserType == ContactPickerConstants.Constants.FIREFOX)
        {
            return;
        }
            
        // the input.value is not available at this point.  Use settimeout to call doPaste,
        // at which point it will be available            
        setTimeout(Delegate.create(this, this._doPaste), 0);                
    },
    
    _doPaste : function ()
    {
        /// <summary>Performs AC paste operation</summary>
        // todo, check isReady flag before doing anything   
        if(ContactPickerCore.isManagedRestricted)
        {
            this._input.value = "";
            this.fireEvent('onIsManagedRestricted', [this, null]);
        }                
        var pastedData = this._input.value;
        if (pastedData == "")
        {
            this._setInputWidth(" ");
            this._clearResults();
            return;
        }

        if (this._addElementsByString(pastedData))
        {
            this._input.value = "";
            this._lastInput = "";
            this._setInputWidth(" ");
            setTimeout(Delegate.create(this, this._scrollInputToBottom), 50);
            return;
        }
        if (pastedData)
        {
            this._doSearch();
        }
    },

    completeFreeFormEmail : function ()
    {
        ///<summary>When user tabs off of box, complete any pending freeform entries</summary>         
        if (this._input.value != "")
        {
            if(ContactPickerCore.isManagedRestricted)
            {
                this._input.value = "";
                this.fireEvent('onIsManagedRestricted', [this, null]);
                return;
            }                
            this._currentAutoCompleteSelectedIndex = ContactPickerConstants.Constants.NONE;
            this._completeEntry(true,false);
        }            
    },
    
    _delayedCompleteBlock : function ()
    {
        this._setTimeOutHandle = setTimeout(Delegate.create(this, this.completeFreeFormEmail), 200);             
    },

    _preselect : function ()
    {
        ///<summary>Performs preselections of abch elements</summary>
        
        if (this._selectedLookup == null || this._freeSelectedLookup == null)
        {
            return;
        }
        
        // do all address book emails first
        var uniqueId = 1;
        var contactDataLengh = ContactPickerCore.contactData.length;
        for (var i = contactDataLengh; i--;)
        {
            var abchElement = new AbchElement(i);
            var eltToSelect = this._shouldPreselect(abchElement);
            if (eltToSelect)
            {
                eltToSelect.setUniqueId(uniqueId++);
                this._addAbchElement(eltToSelect, false);
            }
        }
        
        // do all free select emails
        var freeSelectedLength = this._freeSelectedLookup.length;
        for (var j = freeSelectedLength; j--;)
        {
            var email = this._freeSelectedLookup[j];
            var eltToSelect = new AbchElement();
            eltToSelect.email = email;
            eltToSelect.setUniqueId(uniqueId++);
            this._addAbchElement(eltToSelect, false);
        }
        
        this._persistState();
    },
    
    _persistState : function ()
    {
        ///<summary>Performs preselections of abch elements</summary>
        this._selectedContactsServer.value = "";
        var totalElementCount = this.selectedAbchElements.length;
        var persistElements = [];
        for( var i = 0; i < totalElementCount; i++)
        {
            // build contact structure
            var output = "";
            var abchElt = this.selectedAbchElements[i];            
            if(!abchElt.isValid)
                continue;                        
            if(abchElt.fullName == null)
                abchElt.fullName = "";
            if(abchElt.email == null)
                abchElt.email = "";                
            var serverEltString = [ "[",
                abchElt.serverSideID,
                ";",
                escape(Microsoft.Live.ContactPicker.Util.htmlDecode(abchElt.fullName)),
                ";",
                escape(Microsoft.Live.ContactPicker.Util.htmlDecode(abchElt.email)),
                ";",
                abchElt.isSpacesFriend,
                ";",
                abchElt.isGroup,
                ";",
                abchElt.isFavorite,
                ";",
                abchElt.isCategory,
                "]" ].join('');    
                                
              persistElements[i] = serverEltString;
         }
         output = persistElements.join(',');
         this._selectedContactsServer.value = output;                     
    },        

    _shouldPreselect : function (contact)
    {
        
        // look up by guid first
        if (this._selectedLookup[contact.serverSideID] == true)
        {
            var selections = 0;
            if (!contact.isCategory && !contact.isGroup)
            {
                // look through emails to see which emails to select for the given contact
                var emails = contact.getEmailArray();
                if(emails)
                {
                    for (var i = emails.length; i--;)
                    {
                        if (this._selectedLookup[emails[i].toLowerCase()] == true)
                        {
                            selections ++;
                            var abchElt = new AbchElement( contact.getClientId(), false, i );
                            return abchElt;
                        }
                    }
                }
            }
            // if no selections happened, assume the primary email
            if (selections == 0)
            {
                return contact;
            }
        }

        // if guid didn't yeild results, try just by email only if contact is not a category/group
        // TODO: update this when groups (cricles) have emails
        if (!contact.isCategory && !contact.isGroup)
        {
            var emails = contact.getEmailArray();
            if(emails)
            {
                for (var i = emails.length; i--;)
                {
                    if (this._selectedLookup[emails[i].toLowerCase()] == true)
                    {
                        var abchElt = new AbchElement(contact.getClientId(), false, i);
                        return abchElt;
                    }
                }
            }
        }
        else
        {
            // try match by name if the contact is a group or category
            if (this._selectedLookup[contact.fullName.toLowerCase()])
            {
                return contact;
            }
        }
        
        return null;
    },
    
    _reset : function ()
    {
    ///<summary>After a selection is made, set DOM and variables back to initial pre-search state </summary>        
        if(this._input)
            this._input.value ="";
        if(this._resultsContainer)
            this._resultsContainer.style.display = "none";
        this._currentAutoCompleteSelectedIndex = ContactPickerConstants.KeyCodes.NONE_SELECTED;
        this._lastAutoCompleteSelectedIndex = ContactPickerConstants.KeyCodes.NONE_SELECTED;                              
    },    

    _clearResults : function ()
    {
    ///<summary>clear the results from the DOM and make results container invisible</summary>
        this._resultsContainer.innerHTML = "";
        this._resultsContainer.style.display = "none";
    },
        
    _isEmailValid : function (inputVal)
    {
        ///<summary>Returns true if email passes regex validation</summary>                          
        // this pattern should match 99.9% of all emails
        var emailRegExp = new RegExp(this._emailPattern, "ig");
        var pass = emailRegExp.test(inputVal);   
        return pass;
     },
    
    _isDomainValid : function (inputVal)
    {
        var badDomains = ";home.com;attbi.com;example.com;hotmail.co;yaho.com;hotmai.com;yahoo.co;yahoomail.com;direcway.com;homail.com;ims-ms-daemon;local.transport;ethome.net.tw;ethome.net.tw;hotmil.com;hotmial.com;yhoo.com;collegeclub.com;sbcglobal.com;hotamil.com;hotmail.com.au;yaoo.com;yahoo.net;yhaoo.com;mail.hotmail.com;passport.com;kimo.com.tw;yahooo.com;yahho.com;ol.com;gateway.net;hotmail.com.mx;otmail.com;htomail.com;aol.co;altavista.com;hotmaill.com;taiwan.com;hotmail.con;ahoo.com;hotmail.om;hotmail.net;hormail.com;hotail.com;hotamail.com;yahoo.om;homtail.com;msn.net;sprint.ca;angelfire.com;cm1.ethome.net.tw;yohoo.com;a0l.com;alo.com;msn.de;gmail.co;";        
        var domain = this._getBetween(inputVal,"@","");
        return !this._containsOneOf(badDomains, domain);
    },

    _containsOneOf : function (list, inputVal)
    {
        // if the inputVal is null or empty, do nothing
        if (Microsoft.Live.ContactPicker.Util.isNullOrEmpty(inputVal))
        {
            return false;
        }    
        return list.indexOf(";" + inputVal.toLowerCase() + ";") > -1;
    },
    
    _containsStartsWith : function (list, inputVal)
    {
        // if the inputVal is null or empty, do nothing
        if (Microsoft.Live.ContactPicker.Util.isNullOrEmpty(inputVal))
        {
            return false;
        }
        return list.indexOf(";" + inputVal.toLowerCase()) > -1;
    },
    
    _isInWhiteList : function (inputVal, partial)
    {
        return partial
            ? this._containsStartsWith(ContactPickerCore.emailWhiteList, inputVal)
            : this._containsOneOf(ContactPickerCore.emailWhiteList, inputVal);
    },
    
    _isEmailAndDomainValid : function (inputVal)
    {
        var isEmailValid = this._isEmailValid(inputVal);
        var isDomainValid = this._isDomainValid(inputVal);
        return ((isEmailValid || !ContactPickerCore.performValidEmailCheck) && isDomainValid)
    },
    
    _completeEntry : function (onlyAddValidBlocks, setFocusAfter)
    {
            ///<summary>When a user entry is ready for completion, whether there has been
            ///a match or we are dealing with a freeform typed email, this routine is run</summary> 
            ///<param name="onlyAddValidBlocks" type="bool">True if this routine should disregard any freeform emails that would
            ///be invalid or result in red blocks.  False otherwise.</param>                                
            ///<param name="setFocusAfter" type="bool">True if focus should be set back to hidden input box after freeform entry is complete.  False otherwise.</param>                                
            
            var acRow = $(this._instanceName + "$R" + (this._currentAutoCompleteSelectedIndex + 1));            
            if(acRow == null)
            {
                //User hit enter but there was no selection from list.  
                //If we are in ManagedRestriced account, do not allow freeform emails
                if(ContactPickerCore.isManagedRestricted)
                {
                    this._input.value = "";
                    this.fireEvent('onIsManagedRestricted', [this, null]);
                    return;                
                }
                
                //If valid email, then make a box out of it                
                //validate custom-entered email
                //This reg ex is from Hotmail - verified with Walter that it is the one we should be using
                var inputVal = this._input.value;
                if(inputVal == "")
                    return;
                var isValid = this._isEmailAndDomainValid(inputVal);
                var pass = isValid || this._isInWhiteList(inputVal, false);
                var abchElement = new AbchElement();                
                inputVal = Microsoft.Live.ContactPicker.Util.htmlEncode(inputVal.replace(/^\s+|\s+$/g,"")); //trim
                if(pass)
                {
                    if (isValid)
                    {
                        abchElement.email = inputVal;
                    }
                    else
                    {
                        abchElement.fullName = inputVal; // quickName
                    }
                    this._addAbchElement(abchElement,setFocusAfter);
                    this.fireEvent('onContactAdded', [this, abchElement]);                    
                }
                else
                {
                    abchElement.email = inputVal; 
                    if(!onlyAddValidBlocks || !ContactPickerCore.performValidEmailCheck)
                    {
                        abchElement.isValid = false;
                        this._addAbchElement(abchElement,setFocusAfter);  
                        this.fireEvent('onContactAdded', [this, abchElement]);                                      
                    }
                }
                this._persistState();
            }
            else
            {
                this._makeSelection([acRow.getAttribute("cid"),acRow.getAttribute("eid"),true]);
            }            
    },
    
    _clearLostFocusTimeout: function  ()
    {
        clearTimeout(this._setTimeOutHandle);  
    },
    
    _makeSelection : function  (params)
    {    
    ///<summary>Add the selected contact to the input box and to the public selectedContacts Collection</summary>
    ///<param name="params" type="Array">
    ///An array of the following parameters:
    ///1. The 'clientID' is the unique numeric identifier used client-side to identify a contact. (index of global array)
    ///2. emailIndex:  The email associated with the chosen contact to display.  In other words, the 1st, 2nd, etc email associated with this contact
    ///3. setFocusAfter:  If true, focus is set back to the hidden box after this operations.  If false, it will not be.
    ///</param>        
    
    //The params are passed in as one array because the eventing system
    //will also call this routine and deals with the passing of multiple
    //parameters by packaging one param into an array    
        this._clearLostFocusTimeout();
        //clearTimeout(this._setTimeOutHandle);    
    
        var clientID = params[0];
        var emailIndex = params[1];        
        var setFocusAfter = params[2];        
        abchElement = new AbchElement(clientID); 
        if(abchElement._emailArray)
            abchElement.email = abchElement._emailArray[emailIndex];
        abchElement._selectedElementID = Microsoft.Live.ContactPicker.Util.concat(abchElement._clientID," ",emailIndex);
        
        //if allow dups config is set to false and selection was a duplicate, clear input box
        if(!ContactPickerCore.allowDuplicateSelection)
        {
            var elementIsDup = false;
            var selectedElementsLength = this.selectedAbchElements.length;
            for(var i= 0;i< selectedElementsLength;i++)
            {
                var selectedAbchEl = this.selectedAbchElements[i];
                var isContact =  !selectedAbchEl.isCategory && !selectedAbchEl.isGroup;               
                if(selectedAbchEl._clientID == abchElement._clientID && (selectedAbchEl.email == abchElement.email || !isContact))
                {
                    elementIsDup = true;
                    break;
                }
            }
            this._reset();
            if(elementIsDup)
                return;    
        }
        
        //add abchElement
        abchElement._selectedElementID = Microsoft.Live.ContactPicker.Util.concat(abchElement._clientID,"-",emailIndex);
        abchElement.emailIndex = emailIndex;
        this._addAbchElement(abchElement,setFocusAfter);

        //fire selection event for public consumption
        this.fireEvent('onContactAdded', [this, abchElement]);        
        
        this._persistState();

    },    
    
    _removeContact : function (paramArray)
    {
    ///<summary>Removes a contact from the input box</summary>    
    ///<param name="paramArray" type="Array">:
    ///     paramArray["UniqueSelectedElementID"]:
    ///         a unique incremental id used to identify the correct element in the selectedAbchElements array to remove
    ///         This is necessary because we are allowing duplicate entries.
    ///     paramArray["FireEvent"]:
    ///         Indicates whether or not a public event should be fired.
    ///</param>
    ///Parameter array is being used because this method is accessed by inline eventing
    ///which can only send one argument.        
        var uniqueSelectedElementID = paramArray["UniqueSelectedElementID"];
        var fireEvent = paramArray["FireEvent"];        
        
        //this call fails in Firefox :(
        //var tag = String.concat("Block_",uniqueSelectedElementID);                    
        //var removedLi = Control.getByTag(this._elt,tag);               
        
        var id = Microsoft.Live.ContactPicker.Util.concat(this._instanceName,"$",uniqueSelectedElementID);
        var removedLi = $(id);               
        
        //remove from DOM and public array of selected abch objects
        if(removedLi)
        {
            var selectedAbchElementsLength = this.selectedAbchElements.length;
            this._inputUL.removeChild(removedLi);
            for( var i = 0; i < selectedAbchElementsLength; i++)
            {
                if(this.selectedAbchElements[i]._uniqueSelectedElementID == uniqueSelectedElementID)
                {
                    //fire event for public consumption
                    if(fireEvent)
                    {
                        this.fireEvent('onContactRemoved', [this, this.selectedAbchElements[i]]);
                    }
                    
                    //remove element from the array
                    this.selectedAbchElements.splice(i, 1);                                        
                    if(fireEvent)
                        this._persistState();                        
                    break;                                            
                }
            } 
                           
        }        
    }, 

    _keyUp : function (keyCode)
    {    



        ///<summary>handle the keyup event on the text box
        ///This is used to detect alphanumberic characters entered by the user.
        ///In particular, keydown will not suffice for detecting characters that are the 
        ///result of pressing Shift +
        //</summary>    

        var charCode = (keyCode || Microsoft.Live.ContactPicker.Util.getCharCode(event.keyCode));
        if (charCode == ContactPickerConstants.KeyCodes.SHIFT)
        {
            this._isSpecialKey = false;
        }
        
        // block number of selections possible (for Spaces)
        if (ContactPickerCore.selectionMaximum > 0 && 
            this.selectedAbchElements.length >= ContactPickerCore.selectionMaximum)
        {
            event.returnValue = false;
            return;
        }
        else if (this._keyHandled)
        {
            this._keyHandled = false;
            if (!this._input.value.length)
            {
                this._setInputWidth(" ");
                this._clearResults();
            }
            return;
        }
        else if (!this._input.value.length)
        {
            this._setInputWidth(" ");
            this._clearResults();
            return;
        }
        else
        {
        }
        
        this._doSearch();




    },    
    
    _clearInput : function ()
    {
        this._input.value = "";
    },
    
    _deleteElement : function ()
    {
        if(this.selectedAbchElements.length > 0)
        {
            var lastElement = this.selectedAbchElements[this.selectedAbchElements.length-1];
            var uniqueSelectedElementID = this.selectedAbchElements[this.selectedAbchElements.length -1]._uniqueSelectedElementID;
            this._removeContact({"UniqueSelectedElementID":uniqueSelectedElementID,"FireEvent":true});
            this._clearResults();
            if(this.selectedAbchElements.length == 0)
            {
                this._reset();
                return;
            }
        }
    },
    
    _createElementFromInput : function ()
    {
        var input = this._input.value.replace(/^\s+|\s+$/g,"");
        if (input == "")
            return;
        this._completeEntry(false,true);        
    },
    
    _checkIfMatch : function (query)
    {
        query = query.toLowerCase();
        return this._getContactRows(query.charCodeAt(0), query) || this._isInWhiteList(query, true);
    },
    
    /* TODO: Refactor Ghetto forking code
     * Dear Dev,
     *  Yes, this code forking is god awful, confusing and messy. Unfortunately,
     * under the circumstances there is no time to refactor this code correctly
     * to have consistent bahvior in FF3 and SF3. The ideal situation would be to have
     * an element act as a focus anchor that all browser can focus to. Further more, this elt
     * should expose keyboard events. That way we could have a unified solution without all of this
     * forking ghetto-ness. Unfortunately, this wasn't obvious when the code was written.
     * Firefox 3 and Safari 3 behave inconsistently compared with the rest of the browsers (IE6,7,FF2).
     * With that said, you will (and I as well, to my regret) have to deal with the code as is
     * until there is sufficient time to tackle the problem correctly.
     *                                                              Sincerely,
     *                                                              VladDu
     */
    _focusOutInput : function ()
    {
        // this blur handler is for the input because
        // the behavior in FF3 and SF3 is (which requires blur on the input) is
        // inconsistent with IE6,7, and FF2.    
        if (this._browserType == ContactPickerConstants.Constants.FIREFOX3 ||
            this._browserType == ContactPickerConstants.Constants.SAFARI)
        {
            this._updateSelectedElt(true);
        }
    },
    
    _focusOutMainDiv : function ()
    {
        if (this._currentKeyFocusElt != -1)
        {
            this._updateSelectedElt(true);
        }
    },
    
    _keyDownForContainer : function (keyCode)
    {
        // this method is used for keyboard selection
        if (event.srcElement == this._input && !keyCode)
        {
            return;
        }
        var charCode = (keyCode || Microsoft.Live.ContactPicker.Util.getCharCode( event.keyCode ));
        switch (charCode)
        {
            case ContactPickerConstants.KeyCodes.KEY_LEFT:
            case ContactPickerConstants.KeyCodes.KEY_RIGHT:
                this._keyHandled = true;
                this._selectByKeyboard(charCode);
                break;
            case ContactPickerConstants.KeyCodes.BACK_BUTTON:
            case ContactPickerConstants.KeyCodes.DELETE:
                this._acCancelBubble()
                this._deleteSelectedElt();
                // delay so the key doesn't go to the input box
                $callFocus(this._input);
                break;
            case ContactPickerConstants.KeyCodes.TAB: // cased for firefox
                break;
            default:
                this._input.focus();
                this._keyDown(charCode);
                break;
        }
    },
    
    _assignFromChar : function (fromChar, assignChar)
    {
        if (fromChar == "")
        {
            return assignChar;
        }
        else
        {
            return fromChar;
        }
    },
    
    _keyDown : function (keyCode)
    {
        // tell updateSelectedElt to clear the current keyboard selection
        if (keyCode)
        {
            this._updateSelectedElt(true);
        }
            
        var charCode = (keyCode || Microsoft.Live.ContactPicker.Util.getCharCode( event.keyCode ));
        // this will server as the select delimiter (see below)
        // because String.fromCharCode() is broken accross browsers :(
        var fromChar = "";
        
        
        if (this._browserType == ContactPickerConstants.Constants.FIREFOX3 ||
            this._browserType == ContactPickerConstants.Constants.SAFARI)
        {
            // this will reset the selection for firefox3 and safari.
            // this is done this way because ff3 and sf3 behave differently
            // compared to ff2 ie6,7
            if (charCode != ContactPickerConstants.KeyCodes.KEY_LEFT &&
                charCode != ContactPickerConstants.KeyCodes.KEY_RIGHT &&
                charCode != ContactPickerConstants.KeyCodes.DELETE &&
                charCode != ContactPickerConstants.KeyCodes.BACK_BUTTON)
            {
                this._updateSelectedElt(true);
            }
        }
        
        switch (charCode)
        {
            case ContactPickerConstants.KeyCodes.FF_SEMICOLON:
                fromChar = this._assignFromChar(fromChar, ";");
                if (this._browserType != ContactPickerConstants.Constants.FIREFOX &&
                    this._browserType != ContactPickerConstants.Constants.FIREFOX3)
                {
                    break; // fall through only if firefox
                }        
            case ContactPickerConstants.KeyCodes.COMMA:
                fromChar = this._assignFromChar(fromChar, ",");
            case ContactPickerConstants.KeyCodes.SEMICOLON:
                fromChar = this._assignFromChar(fromChar, ";");
                // lets '<' and ':' etc. work correctly
                if (this._isSpecialKey)
                {
                    break;
                }
            case ContactPickerConstants.KeyCodes.SPACE: 
                fromChar = this._assignFromChar(fromChar, " ");
                // todo if there are more results don't complete obejct
                // this code will look ahead to see if there are more matches on several character
                // else it will complete the block
                var query = this._input.value + fromChar;
                if (this._checkIfMatch(query))
                {
                    break;
                }
            case ContactPickerConstants.KeyCodes.TAB:
                if (!this._checkIfMatch(this._input.value) && !this._input.value)
                {
                    break;
                }
            case ContactPickerConstants.KeyCodes.RETURN:
                this._keyHandled = true;
                this._createElementFromInput();
                event.returnValue = false;
                return false;
                break;
            case ContactPickerConstants.KeyCodes.ESC:
                if (this._resultsContainer.style.display == "block")
                {
                    setTimeout(Delegate.create(this, this._clearResults), 0);
                }
                else
                {
                    setTimeout(Delegate.create(this, this._clearInput), 0);
                }
                this._keyHandled = true;
                return false;
                break;
            case ContactPickerConstants.KeyCodes.BACK_BUTTON:
                if (this._input.value.length == 0)
                {
                    if (this._browserType == ContactPickerConstants.Constants.FIREFOX3 ||
                        this._browserType == ContactPickerConstants.Constants.SAFARI)
                    {
                        if (this._currentKeyFocusElt != -1)
                        {    
                            this._deleteSelectedElt();
                            return;
                        }
                    }
                    // delete the previous elements
                    this._deleteElement(); // TODO: delete deletes the wrong element
                    // if there are duplicates
                    this._keyHandled = true;
                }
                break;
            case ContactPickerConstants.KeyCodes.DELETE:
                // this is here because of ff3 sf3 ghetto-ness
                if (this._currentKeyFocusElt != -1)
                {
                    this._deleteSelectedElt();
                }
                break;
            case ContactPickerConstants.KeyCodes.SHIFT:
                this._isSpecialKey = true;
                return;
                break;
            case ContactPickerConstants.KeyCodes.KEY_UP: // fall through
            case ContactPickerConstants.KeyCodes.KEY_DOWN:
                this._keyHandled = true;
                this._selectByArrowKeys(charCode);
                return;
                break;
            case ContactPickerConstants.KeyCodes.KEY_LEFT:
            case ContactPickerConstants.KeyCodes.KEY_RIGHT:
                // firefox 3 lameness hack fix. See todo comment above.
                if (this._browserType == ContactPickerConstants.Constants.FIREFOX3)
                {
                    this._inputDiv.focus();
                }
                this._keyDownForContainer(charCode);
                break;
            default:
                break;
        }
        
        // manually fire key up, since we came from the container div
        if (keyCode)
        {
            this._keyUp(charCode);
        }
    },
    
    _selectByKeyboard : function (key)
    {
        ///<summary>Manages the keyboard left/right selection of objects.</summary>
        ///<param name="key" type="int">The key code, can only be KEY_LEFT or KEY_RIGHT.</param>
        
        if (key != ContactPickerConstants.KeyCodes.KEY_LEFT && key != ContactPickerConstants.KeyCodes.KEY_RIGHT)
        {
            return;
        }
        
        // switch the keys around if we are rtl
        if (ContactPickerCore.isRTL)
        {
            if (key == ContactPickerConstants.KeyCodes.KEY_LEFT)
            {
                key = ContactPickerConstants.KeyCodes.KEY_RIGHT;
            }
            else if(key == ContactPickerConstants.KeyCodes.KEY_RIGHT)
            {
                key = ContactPickerConstants.KeyCodes.KEY_LEFT;
            }
        }
                
        var query = this._input.value;
        var inputULElt = this._inputUL.children;
        if (key == ContactPickerConstants.KeyCodes.KEY_LEFT)
        {
            if (query == "")
            {
                if (this._currentKeyFocusElt == -1)
                {
                    // only the stabilizer and inputul exist, so do nothing
                    if (inputULElt.length == 2)
                    {
                        return;
                    }
                    this._currentKeyFocusElt = inputULElt.length - 2;
                }
                else
                {
                    // this is 1 because the first elt will always be the inivisble stabilizer
                    if (this._currentKeyFocusElt > 1)
                    {
                        this._currentKeyFocusElt--;
                    }
                }
            }
        }
        else
        {
            if (this._currentKeyFocusElt == -1)
            {
                // no change
                return;
            }
            else
            {
                if ((this._currentKeyFocusElt + 1) != (inputULElt.length - 1))
                {
                    this._currentKeyFocusElt++;
                }
            }
        }
        
        // update the selection
        this._updateSelectedElt(false);
    },

    _deleteSelectedElt : function ()
    {
        if ((this._currentKeyFocusElt == -1) || !this._previousSelectedElt)
        {
            return;
        }
        var id = this._previousSelectedElt.id;
        var idTokens = id.split('$');
        if (idTokens && idTokens.length == 2)
        {
            id = idTokens[1];
        }
        
        this._currentKeyFocusElt = -1;
        this._removeContact( 
            {
                'UniqueSelectedElementID' : id,
                'FireEvent' : true
            });
    },
    
    _updateSelectedElt : function (shouldClear)
    {
        if (this._currentKeyFocusElt == -1)
        {
            return;
        }
        if (this._previousSelectedElt || shouldClear)
        {
            if ($containsClass(this._previousSelectedElt, "BlockInvalid"))
            {
                $removeClass(this._previousSelectedElt, "SelectedInvalid");
            }
            else
            {        
                $removeClass(this._previousSelectedElt, "Selected");
            }
            if (shouldClear)
            {
                this._currentKeyFocusElt = -1;
            }
        }
        
        this._previousSelectedElt = this._inputUL.children[this._currentKeyFocusElt];
        if (this._previousSelectedElt)
        {
            // scroll to elt
            this._inputDiv.scrollTop = this._previousSelectedElt.offsetTop;
            this._inputDiv.focus();
            if ($containsClass(this._previousSelectedElt, "BlockInvalid"))
            {
                $addClass(this._previousSelectedElt, "SelectedInvalid");
            }
            else
            {
                $addClass(this._previousSelectedElt, "Selected");
            }
        }
        
    },
    
    _setInputWidth : function (inputValue)
    {
        ///<summary>Sets the width of the hidden input box according to the number of characters in it</summary>
        ///<param name="inputValue" type="string">The contents ofthe hidden input box</param>            
    
        //Adjust width of input box dynamically as user types
        //The goal here is that we want the hidden input box to grow as the user types        
        //and be just a little bit bigger than the text in it so that everything naturally
        //wraps in an optimal way as the user types.  The problem is that with the 
        //fonts we are using, each letter is a different width.  And of course, the fonts
        //themselves are different sizes.  (Ie if user does not have Segoe and therefore uses
        //Verdana).  We have to accomodate the widest case, which I believe, by trial and
        //error, to be 'K' in Segoe.  Using 'K' in Segoe and kept tweeking these numbers
        //(.8 and 1) until the Ks were accomodated but not much room was left after that.
        
        var width = this._measureString(inputValue);
        this._input.style.width = width + "px";
    },
    
    _setLoadingIndicator : function ()
    {
        ///<summary>Displays the loading indicator. The contact data isn't ready.</summary>
    
        this._resultsContainer.innerHTML =  "&nbsp;<img class=\"LoadingIcon\" src=\"" + ContactPickerStrings.loadingSmallImg + "\"/>";
        this._resultsContainer.style.display = "block";                
        this._isWaitingOnReadyNotification = true;    
    },
    
    _doSearch: function ()
    {        
        ///<summary>Perform search</summary>
        ///<param name="charCode" type="int">The unicode value of the pressed key in the text box</param>    

        var query = this._input.value.toLowerCase();
        // expand/retract the search input textbox
        this._setInputWidth(query);
        // get the first character so we can get the results from the search index.
        var searchIndexKey = query.charCodeAt(0);

        // If the data has not yet come over the wire and the search index built, show loading
        // icon and wait for ready event to fire so we can complete the search at that time.            
        if (!this._isReady)
        {
            this._setLoadingIndicator();
            return;
        }
        
        var searchResults = ContactPickerCore.searchIndex[searchIndexKey];
        if (!searchResults)
        {
            this._clearResults();
            return;
        }
        //this._searchIndexElementCount = searchResults.length;
        
        var rowMarkup = this._getContactRows(searchIndexKey, query); 
        if( rowMarkup != "")
        {
            if(this._resultsContainer)            
            {
                this._resultsContainer.innerHTML = ["<table cellpadding=\"0\" cellspacing=\"0\" class=\"ResultsTable\">", 
                                                    rowMarkup, 
                                                    "</table>"].join('');
                this._resultsContainer.style.display = "block";
            }   
            this._selectRow(0);
            
            //Force IE to display the vertical scrollbar
            if (this._browserType == ContactPickerConstants.Constants.IE)
            {
                setTimeout(Delegate.create( this, this._selectFirstRow ), 50);
            }
            
            this._resultsContainer.scrollTop = 0;            
        }
        else
        {
            this._clearResults();
        }
    },    
    
    _selectFirstRow : function ()
    {
    ///<summary>Force IE to display the vertical scrollbar right away</summary>    
         this._selectRow(0);
    },  

    _selectByArrowKeys : function (charCode)
    {
        ///<summary>enable browsing of contacts with arrow keys</summary>
        ///<param name="charCode" type="int">The unicode value of the pressed key in the text box</param>                    
            if(charCode == ContactPickerConstants.KeyCodes.KEY_DOWN)
            {
                if(this._currentAutoCompleteSelectedIndex < this._resultRowCount - 1)                 
                    this._selectRow(this._currentAutoCompleteSelectedIndex + 1);
            } 
            else//KEY_UP
            {
                if(this._currentAutoCompleteSelectedIndex > 0) 
                    this._selectRow(this._currentAutoCompleteSelectedIndex - 1);
            }                                   
    },

    _selectRow : function (selectIndex)
    {    
        ///<summary>Highlights the selected results row</summary>               
        ///<param name="selectIndex" type="int">The index of the row to select</param>                                 
            this._lastAutoCompleteSelectedIndex = this._currentAutoCompleteSelectedIndex;
            this._currentAutoCompleteSelectedIndex = selectIndex;
            if(this._lastAutoCompleteSelectedIndex != ContactPickerConstants.Constants.NONE)
            {
                //clear previously selected row
                var acRowLast = $(this._instanceName + "$R" + (this._lastAutoCompleteSelectedIndex + 1));
                if (acRowLast)
                {                
                    this._onResultLeave(acRowLast);
                }
            }
            if(this._currentAutoCompleteSelectedIndex != ContactPickerConstants.Constants.NONE)
            {
                //select newly hovered row
                var acRow = $(this._instanceName + "$R" + (this._currentAutoCompleteSelectedIndex + 1));
                if (acRow)
                {
                    this._onResultHover(acRow);
                }
                
                // Manage scrollbar programatically                
                // We do not want to scroll unless the selected item is at the top or bottom of the list.  To manage this
                // keep track of the 'bottom anchor', the last row index that was displayed at the bottom                
                if( this._currentAutoCompleteSelectedIndex > this._scrollBottomAnchor)
                {
                   this._scrollBottomAnchor = this._currentAutoCompleteSelectedIndex;
                }
                if (this._currentAutoCompleteSelectedIndex < (this._scrollBottomAnchor - ContactPickerConstants.Constants.ROWS_PER_CONTAINER))
                {
                   this._scrollBottomAnchor = this._currentAutoCompleteSelectedIndex + (ContactPickerConstants.Constants.ROWS_PER_CONTAINER + 1);
                }
                if (this._lastAutoCompleteSelectedIndex != ContactPickerConstants.KeyCodes.NONE_SELECTED)
                {
                    if(this._currentAutoCompleteSelectedIndex > this._lastAutoCompleteSelectedIndex)
                    {
                        //DOWN:
                        if(this._currentAutoCompleteSelectedIndex >= this._scrollBottomAnchor)  
                        {
                            var acRowTop = $(this._instanceName + "$R" + (this._currentAutoCompleteSelectedIndex - ContactPickerConstants.Constants.ROWS_PER_CONTAINER));
                            if (acRowTop)
                            {
                                this._resultsContainer.scrollTop = acRowTop.offsetTop;
                            }
                        }
                    }    
                    else
                    {
                        //UP:
                        if(this._currentAutoCompleteSelectedIndex < (this._scrollBottomAnchor - ContactPickerConstants.Constants.ROWS_PER_CONTAINER))
                        {
                            var acRowTop = $(this._instanceName + "$R" + (this._currentAutoCompleteSelectedIndex + 1))
                            if (acRowTop)
                            {
                                this._resultsContainer.scrollTop = acRowTop.offsetTop;
                            }
                        }
                    }
                }                                              
           }    
    },    
    
    _addAbchElement : function (abchElement, setFocusAfter, returnMarkupOnly)
    { 
    ///<summary>Inserts contact block into the Text box DOM and updates public array</summary>
    ///<param name="abchElement" type="abchElement">The abchElemnt object representing the abch element to add.</param>             
    ///<param name="setFocusAfter" type="bool">Whether or not to set focus to the hidden input box after this operation</param> 
    ///<param name="returnMarkupOnly" type="bool">True if calling function wants the markup returned and not inserted into the DOM
    ///     (as is the case in a bulk insert like when synching with Contact Picker's SelectAll).  False otherwise.
    ///</param> 
    ///
        
        //bold name if it is a category or group
        var boldBegin = "";
        var boldEnd = "";
        if(abchElement.isCategory || abchElement.isGroup)
        {
            boldBegin = "<b>";
            boldEnd = "</b>";
        }            


        //Assign temporary clientID to objects created by freeform email typing so we can 
        //link up to the operative object in .selectedAbchElements and block element for later removal
        if(abchElement._isNew)
        {
           abchElement._clientID =  ContactPickerConstants.Constants.NONE;
           abchElement._selectedElementID = abchElement._clientID;
        } 
                                                                           
        //get UL element surrounding the li elements that are the blocks
        var inputUL = this._inputUL;
           
        //User is now able to select duplicate people/categories.  Using incremental id to uniquely identify an element in DOM and object array
        if(abchElement._uniqueSelectedElementID == 0)
        {            
            abchElement._uniqueSelectedElementID = ++this._uniqueElementID;
        }
        var blockStyle;
        var blockEmailStyle;
        if(!abchElement.isValid)
        {
            blockStyle = "BlockInvalid";
            blockEmailStyle = "BlockEmailInvalid";            
        }
        else
        {
            blockStyle = "Block";
            blockEmailStyle = "BlockEmail";
        }

        //Build markup for the li 'block'                        
        var innerHTML = "";             
        if(returnMarkupOnly)                            
            innerHTML = Microsoft.Live.ContactPicker.Util.concat("<li class=\"",blockStyle,"\" id=\"",this._instanceName,"$",abchElement._uniqueSelectedElementID,"\">");
            
        
        //'+' symbol for category expansion
        if(abchElement.isCategory && (abchElement._isNew || abchElement.users.length > 0))
            innerHTML +=  Microsoft.Live.ContactPicker.Util.concat("<img class=\"ExpandCategory\" alt=\"",
                ContactPickerStrings.expandCategory,
                "\" title=\"",
                ContactPickerStrings.expandCategory,
                "\" border=\"0\" src=\"" + ContactPickerStrings.expandAcImg + "\" onclick=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.AutoComplete', '_doExpansion', event,{'id':",
                abchElement._clientID,
                ", 'UniqueSelectedElementID':",
                abchElement._uniqueSelectedElementID,
                ", 'FireEvent':true",
                ", 'ExpandDuplicates':false",
                "})\"/>");           
        
        //name
        if(abchElement.fullName != "")
        {
            innerHTML += Microsoft.Live.ContactPicker.Util.concat("<span class=\"BlockName\">",
                                    boldBegin,
                                    abchElement.fullName,
                                    boldEnd,
                                    "</span>");  
        }                                    
                                              
        //email
        var parenBegin = "";
        var parenEnd = ""; 
        if(abchElement.fullName != null && abchElement.fullName != "" && abchElement.email != null && abchElement.email != "")//ContactPickerCore.includeEmails)       
        {
            //tip from Nikita to get () to behave in ltr and rtl
            parenBegin = "<span dir='ltr'>(</span>";
            parenEnd = "<span dir='ltr'>)</span>";         
        }                       
        if(!abchElement.isCategory)
            innerHTML += Microsoft.Live.ContactPicker.Util.concat("<span class=\"" + blockEmailStyle + "\">",parenBegin,abchElement.email,parenEnd,"</span>");                

        //delete image
        innerHTML +=  Microsoft.Live.ContactPicker.Util.concat("<img class=\"CloseLi\" alt=\"",
                                    ContactPickerStrings.removeContact,
                                    "\" title=\"",
                                    ContactPickerStrings.removeContact,
                                    "\" border=\"0\" src=\"" + ContactPickerStrings.deleteContactImg + "\" onclick=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.AutoComplete', '_removeContactAndSetFocus', event,",                                    
                                    "{'UniqueSelectedElementID':" + abchElement._uniqueSelectedElementID + ",'FireEvent':true}",
                                    ")\"/>");           
                       
        //close li
        if(returnMarkupOnly) 
            innerHTML += "</li>";                            
        
        //add to this.selectedAbchElements
        this.selectedAbchElements.add(abchElement);        
                          
        //emit html to the DOM                
        if(returnMarkupOnly)
        {
            return innerHTML;
        }
        else                
        {
             //The hidden input box is in an li so that in all browsers it is positioned correctly.
             //Insert this new li just before the li containing the input box
             this._reset();
             var newLi = document.createElement("li");
             newLi.className = blockStyle;
             newLi.id = Microsoft.Live.ContactPicker.Util.concat(this._instanceName,"$",abchElement._uniqueSelectedElementID);
             newLi.innerHTML = innerHTML;
             this._inputUL.insertBefore(newLi,this._inputBoxLi);
        }
           
                
        if(setFocusAfter == true) 
        {
            setTimeout(Delegate.create(this, this._setInputFocus), 50);             
        }                        
    },
    
    _removeContactAndSetFocus : function (paramArray)
    {
        this._removeContact(paramArray);
        $callFocus(this._input);
        //
        // In Firefox 3 and Safari 3, clicking the 'x' to close an object will fire this method first,
        // then _setInputFocus() since the outer div has an onclick handler.
        // this should stop that from happening.
        this._acCancelBubble();
    },
    
    _setInputFocus : function ()
    {
    ///<summary>Sets focus to the hidden input box.
    ///</summary>
        
        this._input.focus();
        if(this._browserType == ContactPickerConstants.Constants.SAFARI)
            this._inputDiv.scrollTop = this._input.offsetTop;       
        
        //move cursor to end
        if (this._input.createTextRange)
        {
            var fieldRange = this._input.createTextRange();
            fieldRange.moveStart('character', this._input.value.length);
            fieldRange.collapse();
            fieldRange.select();
        }
    },

    _getContactEltByEmail : function (email)
    {
        email = Microsoft.Live.ContactPicker.Util.htmlEncode(email);
        email = email.toLowerCase();
        var contactKey = email.charCodeAt(0);
        var contactIndicies = ContactPickerCore.searchIndex[contactKey];
        if (!contactIndicies)
        {
            return null;
        }
        
        for( var i = contactIndicies.length; i--; )
        {
            var contactId = contactIndicies[i];
            var abchElt = new AbchElement(contactId);
            var emails = abchElt.getEmailArray();
            if (!emails)
            {
                continue;
            }
            for( var j = emails.length; j--; )
            {
                if (emails[j].toLowerCase() == email)
                {
                    // set the right email index
                    abchElt.email = emails[j];
                    abchElt.emailIndex = j;                
                    return abchElt;
                }
            }
        }
        
        return null;
    },
    
    _getContactRows : function (firstLetterKeyCode, fullSearchText)
    {
///<summary>return the results drop down markup associated with the abch records that match the query</summary>      
///<param name="firstLetterKeyCode" type="string">The keycode</param>             
///<param name="fullSearchText" type="string">Whether or not to set focus to the hidden input box after this operation

        var contactRow = [];
        var data = ContactPickerCore.contactData;
        var rowIndex = 0;        
        var finalMarkupArray = [];                
        var contactResultCount;        
        fullSearchText = fullSearchText.toLowerCase();
        
        //we checked for this in doSearch, but check again to support other code paths in the future        
        try
        {
            contactResultCount = ContactPickerCore.searchIndex[firstLetterKeyCode].length;
        }
        catch(ex)
        {
            return("");
        }
                                      
        // loop through the abch elements associated with the firstLetterKeyCode
        var htmlEncodedSearchText = Microsoft.Live.ContactPicker.Util.htmlEncode(fullSearchText);
        var htmlEncodedSearchTextLength = htmlEncodedSearchText.length;
        for(var i=contactResultCount;i--;)
        {              
            var contactId = ContactPickerCore.searchIndex[firstLetterKeyCode][i];
            
            //retrieve the data 
            var abchElement = new AbchElement(contactId,true);
            var fullName = abchElement.fullName;                                       
            var matchFound = false;
            var matchOnEmailFound = false;

            //Look for matches at the beginning of each token and replace, 
            //but when replacing preserve the original capitalization  
            var regExString = "(^|\\s)" + htmlEncodedSearchText;
            var regEx;
            try
            {
                regEx = new RegExp(regExString,"gi");            
            }
            catch(ex)
            {
                return;
            } 

            var matches = fullName.match(regEx)
            if(matches)
            {
                var matchesLength = matches.length;
                fullName = fullName.replace( regEx, " {matchtoken}");                        
                for(r = 0;r< matchesLength;r++)
                {
                    fullName = fullName.replace("{matchtoken}","<b>" + matches[r] + "</b>");
                }
                if(fullName.indexOf(" ") == 0)
                    fullName = fullName.substring(1,fullName.length);
                    
                matchFound = true;    
            }
            regEx = null;
            

            //Is there a match on one of the emails?            
            var emailArrayLength = 0;
            if (abchElement._emailArray)
            {            
                emailArrayLength = abchElement._emailArray.length;
                if(!matchFound)
                {
                    for(var emailIndex = 0;emailIndex < emailArrayLength;emailIndex++)
                    {    
                        var email = abchElement._emailArray[emailIndex];           
                        if(email !=null && email.toLowerCase().indexOf(htmlEncodedSearchText) == 0)                        
                        {
                            matchOnEmailFound = true;
                            break;
                        }
                    }
                }
            }                                                               
            if (fullSearchText.length > 1)            
            {                
                if (!(matchFound || matchOnEmailFound))
                {
                    continue;
                }
            }
             
            //Multiple emails:                            
            //Loop through all the emails associated with a contact and add them, if they are a match,
            //to the results. 
            if (emailArrayLength == 0)
            {
                var email = abchElement.isSpacesFriend ? Microsoft.Live.ContactPicker.Util.getPrivateMessageString() : "";
                this._populateContactRow(contactRow,++rowIndex,contactId,0,abchElement._clientID, fullName, email);
                finalMarkupArray[rowIndex] = contactRow.join("");
            }
            else
            {
                var isSpacesFriend = abchElement.isSpacesFriend;
                for (var emailIndex = 0; emailIndex < emailArrayLength; emailIndex++)
                {                  
                    var email = abchElement._emailArray[emailIndex];  
                    matchOnEmailFound = false;
                    if (!matchFound && email.toLowerCase().indexOf(htmlEncodedSearchText) == 0)
                    {
                        email = Microsoft.Live.ContactPicker.Util.concat( 
                            "<b>",
                            email.substring(0,htmlEncodedSearchTextLength),
                            "</b>",
                            email.substring(htmlEncodedSearchTextLength,email.length));
                        matchOnEmailFound = true;
                    }            
                    if (matchFound || matchOnEmailFound)
                    {                
                        //display email in email-only contacts
                        //in the place of name
                        if (fullName == "" && email != "")
                        {
                            fullName = email;
                        }
                        else if (fullName != "" && email == "")
                        {
                            if (isSpacesFriend)
                            {
                                email = ContactPickerStrings.privateMessage;
                            }
                        }

                        this._populateContactRow(contactRow, ++rowIndex, contactId, emailIndex, abchElement._clientID, fullName, email, isSpacesFriend);
                        finalMarkupArray[rowIndex] = contactRow.join("");                                          
                    }                
                }
            }            
            delete abchElement;
        }
        this._resultRowCount = rowIndex;
        var finalMarkup = finalMarkupArray.join("")
        return(finalMarkup);        
    },
    
    _populateContactRow : function (contactRow, rowIndex, contactId, emailIndex, clientID, fullName, email, isSpacesFriend)
    {    
    ///<summary>Populates the contactRow array with markup representing one results row</summary>            
    ///<param name="contactRow" type="int">The contactRow array to be populated</param>
    ///<param name="rowIndex" type="object">The index of the row to be generated</param>                     
    ///<param name="emailIndex" type="int">the index of the email associated with this row</param>
    ///<param name="clientID" type="object">the clientID of the abchElement associated with this row</param>
    ///<param name="fullName" type="object">The full name associated with this row</param>
    ///<param name="email" type="object">The email associated with this row</param>    

            //build markup for a results row
            contactRow[0] = Microsoft.Live.ContactPicker.Util.concat(
                "<tr id=\"", this._instanceName, "$R", rowIndex, "\" cid=\"", contactId, "\" eid=\"", emailIndex, "\" class=\"ResultRow\"  onmouseover=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.AutoComplete', '_onResultHover', event, this)\" onmouseout=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.AutoComplete', '_onResultLeave', event, this)\"");
            contactRow[1] = " onclick=\"if(window.Control)Control.invoke('Microsoft.Live.ContactPicker.AutoComplete', '_makeSelection', event,[" + clientID + "," + emailIndex + ",true])\">";                    
            contactRow[2] = Microsoft.Live.ContactPicker.Util.concat("<td class=\"PadderBorderLeft\"><div class=\"ContactName\">", fullName,"</div></td>");
            var spacesTooltip = isSpacesFriend ? " title=\"" + ContactPickerStrings.pmtt + "\"" : "";            
            contactRow[3] = Microsoft.Live.ContactPicker.Util.concat("<td class=\"PadderBorderRight\"><div", spacesTooltip, " class=\"ContactEmail\">", (email ? email : "&nbsp;"), "</div></td>");
            contactRow[4] = "</tr>";                             
    },

    _onResultHover : function (sender)
    {
        if (!sender)
        {
            return;
        }
        
        $addClass(sender, "Hover");
        var children = sender.children;
        if (children)
        {
            $addClass(children[0], "HoverBorderLeft");
            $addClass(children[1], "HoverBorderRight");
        }
    },

    _onResultLeave : function (sender)
    {
        if (!sender)
        {
            return;
        }

        $removeClass(sender, "Hover");
        var children = sender.children;
        if (children)
        {
            $removeClass(children[0], "HoverBorderLeft");
            $removeClass(children[1], "HoverBorderRight");
        }
    },
    
    _acCancelBubble : function ()
    {
        event.cancelBubble = true;
        event.returnValue = false;    
    },
    
    _doExpansion : function (paramArray)
    {    
    ///<summary>Expands a category into its child nodes (contacts in the category)</summary>        
    ///<param name="paramArray" type="array">
    ///paramArray["id"]: The clientID
    ///paramArray["UniqueSelectedElementID"]:the unique selected element id
    ///             This is a class-wide incrementing variable used to uniquely identify a selected abch element (since duplicates can be selected).        
    ///
    ///paramArray["FireEvent"]:True if public onCategoryExpanded event should be fired.  False otherwise.
    ///paramArray["ExpandDuplicates"]:True if duplicate categories should be expanded.  False otherwise.
    
    ///</param>                 
    ///(array param is used to accomodate inline eventing)    



        //
        // In Firefox 3 and Safari 3, clicking the '+' to expand an object will fire this method first,
        // then _setInputFocus() since the outer div has an onclick handler.
        // this should stop that from happening.  
        this._acCancelBubble();
        
        Microsoft.Live.ContactPicker.Util.instrumentWrapper(ContactPickerConstants.Instrumentation.ExpandCategory);
        
        //Make new array holding the new selected abch elements 
        //(since we do not yet have an .insertAt at our disposal for arrays)
        var newAbchArray = [];        
        var clientID = paramArray["id"];
        var uniqueSelectedElementID = paramArray["UniqueSelectedElementID"];
        var expandDuplicates = paramArray["ExpandDuplicates"];
        var category = new AbchElement(clientID);
        var categoryChildrenCount = category.users.length;   
        var contact;     
        var fireEvent = paramArray["FireEvent"];        
        
        var selectedAbchElementsCount = this.selectedAbchElements.length;
        for(var i=0;i< selectedAbchElementsCount;i++)
        {  
            var doExpand;
            var selectedElement = this.selectedAbchElements[i];
            if(expandDuplicates)
                doExpand = (selectedElement._clientID == clientID);
            else
                doExpand = (selectedElement._uniqueSelectedElementID == uniqueSelectedElementID);                      
            if(doExpand)
            {                                    
                //Is this the category to be expanded?  If so, replace it with
                //its child contacts
                for(var childIndex = 0;childIndex< categoryChildrenCount;childIndex++)
                {
                    contact = new AbchElement(category.users[childIndex]);                       
                    contact._selectedElementID = Microsoft.Live.ContactPicker.Util.concat(contact._clientID,"-0");           
                    newAbchArray.add(contact);
                }      
            }
            else
            {
                //Otherwise, add the contact to the new list
                contact = this.selectedAbchElements[i];
                    newAbchArray.add(contact);
            }
        }
        
        //remove all blocks from the text box       
        this._inputUL.innerHTML = this._inputULInitialContents;
        //re-establish references (because they were inside inputUL)
        this._inputBoxLi = $(this._instanceName + "$InputBoxLi");
        this._input = $(this._instanceName + "$InputBox");        
        //clear public array
        this.selectedAbchElements = null;
        this.selectedAbchElements = [];
                   
        //Go through the newly built array, and call ._addAbchElement
        //on each element.  This will add the block to the text box DOM
        //and add the object to the public array
        var newElementCount = newAbchArray.length;
        var elementAdded = false;
        for(i=0;i< newElementCount;i++)
        {
            //check for duplicates as a result of expansion (array.contains does not seem to work
            //for objects)
            var selectedAbchElementsCount = this.selectedAbchElements.length;
            var recordIsDup = false;
            if(!ContactPickerCore.allowDuplicateSelection)
            {
                for(var dupIndex = 0;dupIndex < selectedAbchElementsCount;dupIndex++)        
                {
                    if(this.selectedAbchElements[dupIndex]._selectedElementID == newAbchArray[i]._selectedElementID)
                    {
                        recordIsDup = true;
                        break;
                    }
                }
            }
            if(!recordIsDup)
            {
                this._addAbchElement(newAbchArray[i],false);
                elementAdded = true;
            }            
        }
        //fire selection event for public consumption and synchronization with Contact Picker
        if(fireEvent && elementAdded)
        {
            this.fireEvent("onCategoryExpand", [this, category]);
            fireEvent = false;
        }   
        
        this._persistState();                 
                        




                
        //set focus to input box
        setTimeout(Delegate.create(this, this._setInputFocus), 50);             
    },

    _onSearchIndexReady : function ()
    {
        ///<summary>Handle notification of completion by Search Indexer</summary>        
         if(!this._isReady)
            this._preselect();
         
         this._isReady = true;
         
         if(this._isWaitingOnReadyNotification)
         {
            this._isWaitingOnReadyNotification = false;
            this._doSearch("");
         }
    },  
    
    _isTypeElementMatch : function (abchType,abchElement)
    {    
        ///<summary>Given an abchType and an abchElement, determines whether
        ///         there is a match.  For example:  If abchType is CATEGORY, this function
        ///         returns true if abchElement.isCategory is true and false otherwise.
        ///</summary>       
        ///<param name="abchType" type="int">The type of AbchElement to perform the operation on (People, Categories or Favorites)</param>
        ///<param name="abchElement" type="object">The abchElement object to use in the comparison</param>                     
    
           switch(abchType)
            {
                case ContactPickerConstants.Constants.PEOPLE:
                {
                    if(!abchElement.isCategory)
                        return(true);
                    break;                        
                }
                case ContactPickerConstants.Constants.FAVORITES:
                {
                    if(abchElement.isFavorite)
                        return(true);
                    break;                        
                }
                case ContactPickerConstants.Constants.CATEGORIES:
                {
                    if(abchElement.isCategory)
                        return(true);             
                    break;                                                                                                                                        
                }                            
            }    
            return(false);    
    },
    
    hmAddElementsByString : function (pastedString)
    {
        // function stub for hotmail
        
        
    },    

    _addElementsByString : function (pastedString)
    {
        if (!pastedString)
        {
            return;
        }
        var regEx = new RegExp(this._emailPattern, "gi");
        var match = null;
        var foundEmails = false;
        while ((match = regEx.exec(pastedString)) != null)
        {
            var email = match[0];
            if (!email)
            {
                continue;
            }
            
            // check to see if there already exists a contact with this email
            var abchElt = this._getContactEltByEmail(email);
            if (!abchElt)
            {
                // this email belongs to a new contact
                abchElt = new AbchElement();
                abchElt.email = Microsoft.Live.ContactPicker.Util.htmlEncode(email);
                abchElt._clientID = ContactPickerConstants.Constants.NONE;
                if (abchElt.fullName == "" && abchElt.email != "")
                {
                    abchElt.isValid = this._isEmailAndDomainValid(email) || this._isInWhiteList(email, false);
                }
            }
            
            this._addAbchElement(abchElt, false);
            this._persistState();
            this._scrollInputToBottom();
            this.fireEvent('onContactAdded', [this, abchElt]);
            foundEmails = true;
        }
        
        return foundEmails;
    },
    
    _getBetween : function (stringIn,leftBracket,rightBracket)
    {
        var posLeft;
        var posRight;
        var posLeft = stringIn.indexOf(leftBracket,0);
        if(posLeft > -1)
        {
            posRight = stringIn.indexOf(rightBracket,posLeft+1);
            if(posRight == -1 || rightBracket == "")
                posRight =  stringIn.length;  
        }    
        if(posLeft > -1 && posRight > -1 && posRight > posLeft)
            return stringIn.substring(posLeft + 1,posRight);
        return "";
    },

    //public methods
    //------------------------------------------------------------
    dispose : function ()
    {
    ///<summary>Clean up circular references</summary>        
    
        // remove circular reference
        Control.destroy(this);
        this._elt = null;

    },    

    setFocus : function ()    
    {
    ///<summary>Set focus to hidden text box in this control</summary>            
        // set focus
        this._setInputWidth(this._input.value);
        setTimeout(Delegate.create(this, this._setInputFocus), 50);
    },
    
    onContactAddedHandler : function (sender, abchElement) 
    {
        ///<summary>Handles the onContactAdded event fired by the Contact Picker for synchronization</summary>        
        ///<param name="sender" type="object">The object firing the event.  In this case, Contact Picker</param>             
        ///<param name="abchElement" type="AbchElement">The AbchElement to be added</param>                
        abchElement._selectedElementID = Microsoft.Live.ContactPicker.Util.concat(abchElement._clientID,"-",abchElement.emailIndex);        
        this._addAbchElement(abchElement, false);
        this._persistState();
        this._scrollInputToBottom();
    },
    
    onContactRemovedHandler : function (sender, abchElement) 
    {
        ///<summary>Handles the onContactRemoved event fired by the Contact Picker for synchronization</summary>        
        ///<param name="sender" type="object">The object firing the event.  In this case, Contact Picker</param>             
        ///<param name="abchElement" type="AbchElement">The AbchElement to be removed</param>
        
        //AutoComplete allows duplicate entries for the same user/email combination.  If there are
        //duplicate entries representing the abchElement removed from Contact Picker, we must remove
        //all of the duplicates.                
        var elemCount = this.selectedAbchElements.length;
        if(abchElement.email == null)
            abchElement.email = "";
        for(var i=elemCount;i--;)
        {      
            var selectedElement = this.selectedAbchElements[i];
            if(selectedElement._clientID == abchElement._clientID && selectedElement.email == abchElement.email)
            {                                    
                 this._removeContact({"UniqueSelectedElementID":selectedElement._uniqueSelectedElementID,"FireEvent":false});                
            }
        }
        this._persistState();
        this._scrollInputToBottom();
    },
             
    
    onAllDeselectedHandler : function (sender, abchType) 
    {
        ///<summary>Manages the deselection of all people, categories or favorites necessary
        ///         for synchronization with the Contact Picker
        ///</summary>     
        ///<param name="sender" type="object">The object firing the event.  In this case, Contact Picker</param>             
        ///<param name="abchType" type="int">The type of AbchElement to perform the operation on (People, Categories or Favorites)</param>
        
        //AutoComplete allows duplicate entries for the same user/email combination.  If there are
        //duplicate entries representing the abchElement removed from Contact Picker, we must remove
        //all of the duplicates.
        var elemCount = this.selectedAbchElements.length;
        for(var i=elemCount;i--;)
        {      
            var abchElement = this.selectedAbchElements[i];
            if(abchElement && (abchElement.getClientId() != ContactPickerConstants.Constants.NONE) && this._isTypeElementMatch(abchType,abchElement))
            {
                this._removeContact({"UniqueSelectedElementID":abchElement._uniqueSelectedElementID,"FireEvent":false});                
            }
        } 
        this._persistState();
        this._scrollInputToBottom();
    },

    onAllSelectedHandler : function (sender, abchType) 
    {
        ///<summary>Manages the selection of all people, categories or favorites necessary
        ///         for synchronization with the Contact Picker
        ///</summary>        
        ///<param name="sender" type="object">The object firing the event.  In this case, Contact Picker</param>             
        ///<param name="abchType" type="int">The type of AbchElement to perform the operation on (People, Categories or Favorites)</param>
                
        var innerHtml = "";
        var totalAbchElementCount =ContactPickerCore.contactData.length;
        for (var i=0; i< totalAbchElementCount; i++)
        {              
            //loop through each contact in the global array            
            var baseAbchElement = new AbchElement(i,false);           
            
            if (!this._isTypeElementMatch(abchType,baseAbchElement))
            {
                continue;
            }
                        
            if (baseAbchElement.isCategory)
            {
                emailCount = 1;
            }
            else
            {
                emailCount = baseAbchElement._emailArray.length;
            }
            if (emailCount)
            {
                for (var emailIndex = 0; emailIndex< emailCount; emailIndex++)
                {            
                    var abchElement = new AbchElement(i, false, emailIndex)
                    abchElement._selectedElementID = Microsoft.Live.ContactPicker.Util.concat(abchElement._clientID, "-", emailIndex);                        
                    innerHtml += this._addAbchElement(abchElement, false, true); 
                    delete abchElement;                 
                }
            }
            else
            {
                innerHtml += this._addAbchElement(baseAbchElement, false, true);
            }
        }
        //This is a large operation so gather all HTML for one final insertion.
        //However, this requires that we remove the Li containing the hidden input box,
        //append it to the end afterwards and reset the references.
        var id = Microsoft.Live.ContactPicker.Util.concat(this._instanceName,"$InputBoxLi");
        var inputBoxLi = $(id);
        this._inputUL.removeChild(inputBoxLi);        
        this._inputUL.innerHTML += innerHtml;
        this._inputUL.appendChild(inputBoxLi);
        this._inputBoxLi = inputBoxLi;        
        id = Microsoft.Live.ContactPicker.Util.concat(this._instanceName,"$InputBox");
        this._input = $(id);
        
        this._persistState();
        this._scrollInputToBottom();
    },
        
    associateControl : function (contactPicker) 
    {
        ///<summary>Associates an instance of the contact picker with this control for synching purposes
        ///</summary>        
        ///<param name="contactPicker" type="object">An instance of the contact picker control with which we want to synch</param>             
        ///<summary>Associates an instance of the contact picker with this control for synching purposes
        ///</summary>        
        ///<param name="contactPicker" type="object">An instance of the contact picker control with which we want to synch</param>             
        
        //keep track of delegates for later detaching (in reassociate control)
        this._onContactAddedDelegate = Delegate.create( this, this.onContactAddedHandler );
        this._onContactRemovedDelegate = Delegate.create( this, this.onContactRemovedHandler );
        this._onAllSelectedDelegate = Delegate.create( this, this.onAllSelectedHandler );        
        this._onAllDeselectedDelegate = Delegate.create( this, this.onAllDeselectedHandler );                
        contactPicker.attachEvent( "onContactAdded",  this._onContactAddedDelegate ); 
        contactPicker.attachEvent( "onContactRemoved",  this._onContactRemovedDelegate); 
        contactPicker.attachEvent( "onAllSelected", this._onAllSelectedDelegate); 
        contactPicker.attachEvent( "onAllDeselected",  this._onAllDeselectedDelegate );           
        this._associatedControl = contactPicker;  

    },


    detachAssociatedCPEvents : function (contactPicker) 
    {    
        ///<summary>
        ///Detaches the events that were attached in associateControl.  This is to faciliate the
        ///reAssociateControl call in CP. 
        ///</summary>              
        if(!this._onContactAddedDelegate)
            return;                
        var contactPicker = this._associatedControl;
        contactPicker.detachEvent( "onContactAdded",  this._onContactAddedDelegate ); 
        contactPicker.detachEvent( "onContactRemoved",  this._onContactRemovedDelegate); 
        contactPicker.detachEvent( "onAllSelected", this._onAllSelectedDelegate); 
        contactPicker.detachEvent( "onAllDeselected",  this._onAllDeselectedDelegate );         
        
    },
    
    _getMatchesArray : function (selectedElementsCount)
    {
        ///<summary>
        ///The abch elments in this.selectedAbchElements at this point have no client id and have not yet been 'linked up' with the true contacts in the underlying contacts list.
        ///This routine creates an associative array of full abchElements that match the 'non-linked up' selected elements.
        ///</summary> 
        if(selectedElementsCount == 0)
            return;
        //first create a string of all the selected contacts to provide an O1 lookup to the next loop 
        //of all contacts        
        var selectedElementsString = "";
        for(var i = 0;i<selectedElementsCount;i++)
        {
             var abchElement = this.selectedAbchElements[i];
             if(abchElement.isCategory)
                selectedElementsString += abchElement.fullName + ";"
             else
                selectedElementsString += abchElement.email + ";"
        }
        selectedElementsString = selectedElementsString.toLowerCase();
        //Loop through all contacts to find a match based on name/email, place matches in an
        //associative array
        var totalElements = ContactPickerCore.contactData.length;
        var matchesArray = [];
        var matchesArrayCount = 0;        
        for(i = totalElements;i--;)
        {            
            var abchElement = new AbchElement(i);            
            if(!abchElement.isGroup )
            {
            
                var elementSearchString;
                if(abchElement.isCategory)             
                {
                    elementSearchString = abchElement.fullName + ";"  
                    elementSearchString = elementSearchString.toLowerCase();
                    if(selectedElementsString.indexOf(elementSearchString) > -1)
                    {
                        //We have a match.
                        //save to an associative array
                        matchesArray[elementSearchString] =  abchElement;                            
                        matchesArrayCount++;
                    }
                    continue;                                           
                }
                else
                    elementSearchString = abchElement.email + ";"         

                var emails = abchElement.getEmailArray();
                if(emails)
                {
                    for( var emailIndex = emails.length; emailIndex--; )
                    {                     
                        var email = emails[emailIndex];
                        elementSearchString = email + ";"
                        elementSearchString = elementSearchString.toLowerCase();
                        if(selectedElementsString.indexOf(elementSearchString) > -1)
                        {
                            //We have a match between the data that was pasted into AC or otherwise came in externally and a CPCore data element
                            //Both the email (in the case of contact) or the full name (in the case of category) matched.
                            //save to an associative array
                            abchElement.emailIndex = emailIndex;
                            abchElement.email = email;
                            matchesArray[elementSearchString] =  abchElement;                            
                            matchesArrayCount++;
                        }
                    }
                }
            }            
        }
        return (matchesArray);          
    }
}

//register with typing system
Microsoft.Live.ContactPicker.AutoComplete.createClass( "Microsoft.Live.ContactPicker.AutoComplete" );
//Eventing
Events.addEventing( Microsoft.Live.ContactPicker.AutoComplete );
