jQuery.cookie = function(name, value, options) {
    if (typeof value != 'undefined') {
        options = options || {};
        if (value === null) {
            value = '';
            options = $.extend({}, options); 
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires == 'number' || 
                                options.expires.toUTCString)) {
            var date;
            if (typeof options.expires == 'number') {
                date = new Date();
                date.setTime(date.getTime() + (options.expires *24*60*60*1000));
            } else {
                date = options.expires;
            }
            expires = '; expires=' + date.toUTCString();
        }
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, 
                           domain, secure].join('');
    } else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
};

String.prototype.asTweet = function() { 
    var text = this;

    var tinyurl_match = /(http:\/\/(cli\.gs|twurl\.nl|snurl\.com|digg\.com|sn\.im|bit\.ly|tiny\.cc|tinyurl\.com|snipr\.com|ff\.im|ow\.ly|tr\.im|is\.gd)\/([\w#!:.?+=&%@!\-\/]*))/;
    var url_match = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/g;
    var username_match = /([@]+[A-Za-z0-9_]{1,15})/g;
    var hashtag_match = /([\ ]+)([#]+[A-Za-z0-9-_]{2,16})/g;
    var hashtag_match2 = /^([#]+[A-Za-z0-9-_]{2,16})/g;

    text = text.replace(tinyurl_match, '$1');
    text = text.replace(url_match, '<a target="_blank" href="$1">$1</a>');

    text = text.replace(username_match, '<a href="" class="username">$1</a>');
    text = text.replace(hashtag_match, '$1<a href="" class="hashtag">$2</a>');
    text = text.replace(hashtag_match2, '<a href="" class="hashtag">$1</a>');

    return text;
};

var Listy = {}; // master application object

Listy.authRequired = function() { 
    alert('This requires you to connect your Twitter account');
    return false; 
};

var Twitter = { 'requests':[] };

Twitter.User = function(name, details) {
    this.name = name;
    this.details = details;
};

Twitter.addRequest = function(req) { this.requests.push(req); return Twitter; };
Twitter.executeRequests = function(cbk) { 
    if (!this.requests.length) return;

    this.request({'args':{'multi-request':JSON.stringify(this.requests)},'cbk':cbk,
                  'type':'POST'});
    this.requests = [];
};

// req has (str) url, (obj) args, (fn) cbk, (str) type, (bool) jsonp, (str) context
//         (obj) post
Twitter.request = function(req) {
    var url = (req.url) ? req.url : 'service/account.php';
    var args = (req.args) ? req.args : [];
    var cbk = (req.cbk) ? req.cbk : function() {};

    var options = {url:url, cache:false, data:args, dataType:'json', 
                   type:((req.type) ? req.type : 'GET'), success:cbk};

    if (req.context) options.context = req.context;
    if (req.jsonp) options.dataType = 'jsonp';

    $.ajax(options);
};

var Store = { 'temp' : {}, 'binds' : {} };

// add to array or... extend object
Array.prototype.merge = function(key, persist) {
    key = key.toLowerCase();

    if (this[0] instanceof Array) {
        var arr = (Store.temp[key]) ? Store.temp[key].slice(0) : [[]];

        arr[0] = this[0].concat(arr[0]);

        Store.temp[key] = [[]];

        for (var i = 0, v, uniques = {}; v = arr[0][i]; i++) {
            if (typeof(v) == 'string' || typeof(v) == 'number') {
                if (!uniques[v]) {
                    Store.temp[key][0].push(v);
                    uniques[v] = true;
                }
            } else { 
                Store.temp[key][0].push(v);
            }
        }
    }
    else {
        Store.temp[key] = [$.extend(Store.temp[key][0], this[0])];
    }

    if (Store.binds[key]) {
        $.each(Store.binds[key], function(i,n) { $(n).trigger('update'); });
    }

    if (localStorage && (localStorage.getItem(key) || persist)) 
        this._safeSetItem(key, Store.temp[key]);

    return Store.temp[key];
};

Array.prototype._safeSetItem = function(key, value) {
    try { 
        if (value === null) localStorage.removeItem(key);
        else localStorage.setItem(key, JSON.stringify(value));
    } catch (e) { 
        ['Notice: Reducing cache.'].store('warning', false);
        
        for (var i = 0, ll = localStorage.length, name; i < ll; i++) {
            name = localStorage.key(i);
            
            if (name && name.substring(0,14) == 'details:user:@')
                if (![].get(name).token) localStorage.removeItem(name);
            
            if (name && name.substring(0,5) == 'seen:')
                localStorage.removeItem(name);
        }
        
        try { 
            localStorage.setItem(key, JSON.stringify(value));
        } catch (e) {
            ['Storage super overflow!!! Wiping Cache.'].store('warning');
            
            localStorage.clear();
            
            localStorage.setItem(key, JSON.stringify(value));
        }
    }
};

Array.prototype.store = function(key, persist) { 
    key = key.toLowerCase();

    if (persist && localStorage) {
        this._safeSetItem(key, this);
    }

    Store.temp[key] = this;

    if (Store.binds[key])
        $.each(Store.binds[key], function(i,n) { $(n).trigger('update'); });

    return this[0];
};

Array.prototype.get = function(key) {
    if (Store.temp[key]) return this.concat(Store.temp[key])[0];

    this.length = 0;

    key = key.toLowerCase();

    var val = Store.temp[key];

    if (typeof(val) != 'undefined') return this.concat(val)[0]; // saved in temp var

    if (!localStorage) return this.concat(val); // exit early

    var val = localStorage.getItem(key);

    if (val === null || typeof(val) == 'undefined') return null; // empty array

    val = $.parseJSON(val);
    
    Store.temp[key] = val; //save for repeat access

    return (this.concat(val).length) ? this.concat(val)[0] : null;
};

Listy.miniUser = function(u) { // convert a big user object into a small user object
    var user = {};

    user.description = u.description;
    user.location = u.location;
    user.screen_name = u.screen_name;
    user.statuses_count = u.statuses_count;
    user.followers_count = u.followers_count;
    user.friends_count = u.friends_count;
    user.favourites_count = u.favourites_count;
    user.created_at = u.created_at;
    user.name = u.name;
    user.url = u.url;
    user.profile_image_url = u.profile_image_url;
    user.id = u.id;

    if (u.verified) user.verified = u.verified;
    if (u.protected) user.protected = u.protected;

    return user;
};

Listy.filters = {};

Listy.filters.socialTest = function(filter, filtered, context) { 
    var not = (filter['match'][1] == '-');

    var api = (filter['type'] == 'hasFollower') ? 'friends' : 'followers';

    var name = filter['match'][2];
    
    // social graph cbk (issue: only supports 5k ids)
    var cbk = $.proxy(function(r) { 
        if (!r || !r.length) return;
            
        for (var i = 0,ids = {}; id = r[i]; i++) ids[id] = 1;
            
        var filtered = [];
        
        for (var i=0,d,match,u=[]; u = this.users[i]; i++) {
            d = [].get('details:user:@'+u.name);
            
            if (!d) continue;
            
            if (!ids[d.id] ^ !this.not) // XOR
                filtered.push(u);
        }
        
        $.proxy(Listy.findUsers, this.context)(filtered);
    }, {'context' : context, 'users' : filtered, 'not':not});
    
    var followGraph = $.proxy(function(r) { 
        var details = [].get('details:user:@'+this.name);
            
        if (!r || !details || !r.length) return this.cbk(r);
        
        for (var i = 0, id; id = r[i]; i++) {
            var key = (this.type == 'followers') ? 
                details.id+':follower:'+id : 
                id+':follower:'+details.id;
            
            [true].store(key, false);
        }
        
        return this.cbk(r);
        }, {'cbk':cbk, 'name':name, 'type':api});
    
    Twitter.request({ 'cbk' : followGraph,
                'args':{'api':'api.twitter.com/1/'+api+'/ids.json',
                        'args':{'screen_name':name}}});
};

Listy.filters.followsback = function(match, filtered, context) { 
    var reqs = {'friends':
                {'api':'api.twitter.com/1/friends/ids.json',
                 'args':{'cursor':'-1'}},
                'followers':
                {'api':'api.twitter.com/1/followers/ids.json',
                 'args':{'cursor':'-1'}}};
                                
    for (var i = 0,d,user, names = []; user = filtered[i]; i++) {
        if ([].get('follow_back:user:@'+user.name)) continue;
        
        d = [].get('details:user:@'+user.name);

        var gt = (match[1] == 'gt') ? 1 : -1;
        
        if (!d || !d.followers_count || !d.friends_count 
            || (gt * 100 * d.friends_count / d.followers_count < gt * match[2]))
            continue; //skip obvious
        
        reqs.friends.args.screen_name = user.name;
        Twitter.addRequest($.extend(true,{},reqs.friends));
        
        reqs.followers.args.screen_name = user.name;
        Twitter.addRequest($.extend(true,{},reqs.followers));
                                    
        names.push(user.name); names.push(user.name);
    };
                                
    var cbk = $.proxy(function(response) { 
        var filtered = [];
                                        
        var findUsers = function(self) {
            for (var i = 0, u,p = null; u = self.users[i]; i++) {
                p = [].get('follow_back:user:@'+u.name);
                
                if (!p) continue;
                                                
                var min = self.match[2], gt = (self.match[1]) == 'gt' ? 1 : -1;
                
                if (gt * parseInt(p.replace(/%/,'')) > gt * min)
                    filtered.push(u);
            }
                                            
            $.proxy(Listy.findUsers, self.context)(filtered);
        }
        
        if (!response.length) return findUsers(this);
        
        for (var i = 0, r, sets = {}; r = response[i]; i++) {
            if (!sets[this.n[i]]) sets[this.n[i]] = {};
            
            sets[this.n[i]][(i%2) ? 'followers':'friends']=r.ids;
        }
        
        for (i in sets) {
            var mFriends = 0, fTable = {}, target;
            
            target = sets[i].followers;
            
            if (!target) continue;
            
            for (var j = 0, follower; follower = target[j]; j++)
                fTable[follower] = true; 
            
            target = sets[i].friends;
            
            if (!target) continue;
            
            for (var j = 0, friend; friend = target[j]; j++)
                if (fTable[friend]) mFriends++; 
            
            p = mFriends / sets[i].followers.length * 100;
            
            [Math.round(p)+'%'].store('follow_back:user:@'+i)
        }
                                        
        findUsers(this);
    }, {'context':context, 'users':filtered, 'n':names,
        'match' : match});
    
    if ([].get('api_units') && 
        [].get('api_units').identified_calls &&
        [].get('api_units').identified_calls < 
        Twitter.requests.length) {
        ['Insufficient API units to complete this search'].
            store('warning');
        
        return cbk([]);
    }
    
    if (Twitter.requests.length) 
        ['Calculating ' + (Twitter.requests.length / 2) + 
         ' Follow Back Percentage' + 
         ((Twitter.requests.length > 1) ? 's' : '')].
            store('doing');
    
    if (Twitter.requests.length) Twitter.executeRequests(cbk);
    else cbk([]);
};

Listy.parseUsersResponse = function(r, type) {
    var users = [];

    if (type == 'parsed') return r;

    var timeAgo = function(tweet) { //prepends 'ago' to a tweet
        var time = new Date(Date.parse(tweet.created_at)).getTime();

        time = Math.round((new Date().getTime() - time) / 1000 / 60);

        if (time < 60) time += 'm ';
        else if (time < 60 * 60) time = Math.round(time / 60) + 'h ';
        else time = Math.round(time / 60 / 24) + 'd ';

        tweet.user.tweet_text = '('+time+'ago) ' + tweet.text;

        return tweet;
    };

    switch (type) {
        case 'users':
            for (var i = 0, u; u = r[i]; i++)
                users.push(new Twitter.User(u.screen_name, u));
            break;
        case 'lists':
            for (var i = 0, l; l = r.lists[i]; i++) {
                l.user.tweet_text = 'List &#8220;'+l.name+'&#8221 '
                    + '('+ l.member_count + ' members)';

                users.push(new Twitter.User(l.user.screen_name, l.user));
            }
            break;
        case 'retweets':
            for (var i = 0, t; t = r[i]; i++) {
                if (!t.retweeted_status) continue;

                t.retweeted_status.user.tweet_text = t.retweeted_status.text;

                t.retweeted_status = timeAgo(t.retweeted_status);
                
                users.push(new Twitter.User(t.retweeted_status.user.screen_name,
                                            t.retweeted_status.user));
            }
            break;
        case 'dms':
            for (var i = 0, d; d = r[i]; i++) {
                d.sender.tweet_text = d.text;

                users.push(new Twitter.User(d.sender.screen_name, d.sender));
            }
            break;
        case 'tweets':
            if (!r) break;

            for (var i = 0, t; t = r[i]; i++) {
                t = timeAgo(t);

                t.user.source = t.source.replace(/</g, '&lt;').replace(/>/g,'&gt;').
                    replace(new RegExp('"', 'g'), '&quot;');

                users.push(new Twitter.User(t.user.screen_name, t.user));
            }
            break;
        case 'mentioned-to':
            var username_match = /\B@[a-z0-9_]{1,15}\b/gim;
            
            for (var i = 0, t, popular = [], mentions = {}; t = r[i]; i++) {
                var matches = t.text.match(username_match);

                if (!matches) continue;

                for (var j = 0, n; n = matches[j]; j++) {
                    n = n.substring(1);

                    mentions[n] = (mentions[n]) ? mentions[n] + 1 : 1;
                }
            }

            for (n in mentions) popular.push({'name':n, 'm':mentions[n]});
            
            popular.sort(function(a, b) { 
                return (a.m == b.m) ? 0 : ((a.m > b.m) ? -1 : 1); });
            
            for (var i = 0, u, lookup = [], tweet_text = ''; u = popular[i]; i++) {
                if (i == 100) break;

                if (![].get('details:user:@'+u.name)) lookup.push(u.name);

                tweet_text = ' (' + mentions[u.name] + ' mentions) ';

                users.push(new Twitter.User(u.name, {'tweet_text':tweet_text}));
            }

            if (!lookup.length) break;

            Twitter.request({
                'cbk' : function(r) { 
                    if (!r || !r.length) return;

                    for (var i = 0, mini = Listy.miniUser, user; user = r[i]; i++)
                        [mini(user)].store('details:user:@'+user.screen_name);

                    $('#contacts').trigger('update');
                },
                'args' : {
                    'api' : 'api.twitter.com/1/users/lookup.json',
                    'args' : { 'screen_name' : lookup.join(',') }
                }
            });

            break;
        case 'talked-about':
            var talkedAbout = {};

            for (var i = 0, t; t = r[i]; i++) {
                var username_match = /([@]+[a-z0-9_]{1,15})/gim;                
                var names = t.text.match(username_match);

                if (names)
                    for (var j = 0, u; u = names[j]; j++)
                        talkedAbout[u] = (talkedAbout[u]) ? talkedAbout[u] + 1 : 1;
            }

            for (name in talkedAbout) {
                var userData = {'mentions':talkedAbout[name]};

                m = (talkedAbout[name] == 1) ? 'mention' : 'mentions';

                userData.tweet_text = '(' + talkedAbout[name] + ' '+m+') ';

                users.push(new Twitter.User(name.slice(1), userData));
            }

            if (!users.length) break;

            users.sort(function(a, b) { 
                var x = a.details.mentions; 
                var z = b.details.mentions; 
                    
                return (x == z) ? 0 : ((x < z) ? 1 : -1);
            });

            users = users.slice(0, 100);

            for (var i = 0, lookup = [], u; u = users[i]; i++)
                if (![].get('details:user:@'+u.name)) lookup.push(u.name);

            Twitter.request({
                'cbk' : function(r) { 
                    if (!r || !r.length) return;

                    for (var i = 0, mini = Listy.miniUser, user; user = r[i]; i++)
                        [mini(user)].store('details:user:@'+user.screen_name);

                    $('#contacts').trigger('update');
                },
                'args' : {
                    'api' : 'api.twitter.com/1/users/lookup.json',
                    'args' : { 'screen_name' : lookup.join(',') }
                }
            });

            break;
        case 'otter' : 
            if (!r || !r.response || !r.response.list || !r.response.list.length)
                break;

            for (var i = 0, u; u = r.response.list[i]; i++) {
                u.profile_image_url = u.photo_url;

                users.push(new Twitter.User(u.nick, u));
            }
                           
            break;
        case 'bt_tweets':
            if (r.tweets) // url type response
                for (var i = 0, t; t = r.tweets[i]; i++)
                    users.push(new Twitter.User(t.tweet_from_user, t));
            break;
        case 'search_tweets':
            if (r.results) { // search result type response
                for (var i = 0, t; t = r.results[i]; i++) {
                    t.user = {};
                    t = timeAgo(t);

                    users.push(new Twitter.User(t.from_user, t));
                }
            }
            break;
        case 'cached_users':
            if (r.length && r.length > 0)
                for (var i = 0, u; u = r[i]; i++)
                    users.push(new Twitter.User(u.screen_name, {}));
            break;
        case 'list_users':
            if (!r || !r.users) { users = []; break; }

            for (var i = 0, ii = r.users.length; i < ii; i++) {
                if (u = r.users[i]) // sometimes user is null for some reason TWITBUG
                    users.push(new Twitter.User(u.screen_name, u));

                if ([].get('user') && this.q == 'follows:@'+[].get('user').name)
                    [true].store([].get('user').id+':followed-by:@'+u.screen_name);
            }
            break;
        case 'single_user':
            if (r.screen_name) // single user type response
                users.push(new Twitter.User(r.screen_name, r));
            break;
        case 'listings':
            if (r.lists && r.lists.length > 0) {
                for (var i = 0, l; l = r.lists[i]; i++) {
                    l.user.tweet_text = '( listed on "'+l.name+'" )';

                    users.push(new Twitter.User(l.user.screen_name, l.user));
                }
            }
            break;
        default: 
            throw new Error('invalid result set');
    }

    return users;
};

Listy.findUsers = function(r) { // parse a twitter response looking for users
    if (this.filter) { 
        return this.filter(r, this);
    }

    if (!r) 
        return ['Twitter did not respond, please try again'].store('warning');

    if (r.error && r.error.message && r.error.message == 'invalid results')
        return ['Twitter is over capacity'].store('warning');

    if (r.error && r.error == "Could not authenticate you.")
        return ['Authentication error - please reload the page'].store('warning');

    if (r.error && /^rate limit/gi.test(r.error)) {
        $.getJSON('http://api.twitter.com/1/account/rate_limit_status.json?callback=?',
                  function(r) {
                      var date = new Date(r.reset_time_in_seconds * 1000);
                      var tod = 'AM', hours = date.getHours();
                      var minutes = date.getMinutes();

                      if (minutes < 10) minutes = '0' + minutes;

                      if (hours > 12) { hours -= 12; tod = 'PM'; }

                      ['Twitter Rate Limit Exceeded - try again after ' +
                       hours + ':' + minutes + ' ' + tod].store('warning');
                  });

        return ['Rate Limit Exceeded'].store('warning');
    }

    var users = Listy.parseUsersResponse(r, this.results_type);

    users = [].get('current').concat(users);

    for (var i = 0, ii = users.length, unique = [], names = {}; i < ii; i++) {
        if (!names[users[i].name]) {
            names[users[i].name] = true;
            unique.push(users[i]);
        }
    }

    $.each(unique, function(i,u) { 
        var details = u.details;

        if (!details) return;

        if (typeof(details.profile_link_color) != 'string') return; // dont store

        [Listy.miniUser(details)].store('details:user:@'+u.name, true);
    });

    [unique].store('current');

    [{'range':unique.length}].merge('cursor');

    if (this.results_type != 'parsed')
        Listy.parseResponseToken(r, this.cursor_type);

    if (r.results && !r.next_page) 
        [{'total':unique.length}].merge('cursor');
    else if (r.response && r.response.total) { 
        [{'total':parseInt(r.response.total)}].merge('cursor');
    }
    else if (r.totalresults) {
        var total = (r.tweets && r.tweets.length == 0) ? 
            unique.length : parseInt(r.totalresults);

        [{'total':total}].merge('cursor');
    }
    else if (![].get('cursor').token)
        [{'total':[].get('current').length}].merge('cursor');

    return unique;
};

Listy.parseResponseToken = function(r, cursor_type) {
    if (!r) return;

    switch (cursor_type) {
        case 'explicit_paged':
            [{'token':r.page+1}].merge('cursor');
            break;
        case 'token':
            if (r.next_cursor_str && r.next_cursor_str !== '0') 
                [{'token':r.next_cursor_str}].merge('cursor');            
            else
                [{'token':null}].merge('cursor');
            break;
        case 'max_id':
            if (r && r.length)
                [{'token':r[r.length - 1].id}].merge('cursor');            
            break;
        case 'implicit_paged':
            if (r.length == 0) break;

            var token = [].get('cursor').token;

            if (!token) token = 1;

            token++;

            [{'token':token}].merge('cursor');
            break;
        case 'result_count':
            var token = (r.tweets && r.tweets.length == 0) ?
                null : Math.round(r.startindex / r.itemsperpage) + 2;

            [{'token':token}].merge('cursor');
            break;
        default:
            [{'token':null, 'total':[].get('current').length}].merge('cursor');
            break;
    }
}

Listy.previewUser = function(name) {
    window.scroll(0,0);

    $('body').addClass('preview');

    [new Date()].store('seen:'+name, true);

    [{'current':$('#contacts li.selected').prevAll('li').length+1,}].merge('cursor');
    [{'preview':'@'+name}].merge('cursor');

    $('#contacts > ul > li.u'+name).removeClass('unseen');

    var img = ([].get('user_image:'+name)) ? [].get('user_image:'+name) : 
              'http://tweetbe.at.s3.amazonaws.com/icons/glyphish/11-clock.png';

    var template = '<div class="sources"></div><img src="'+img+'"/><div class="types"></div><div class="week"></div><div class="month"></div><div class="year"></div><h1>'+name+'</h1><div class="error"></div><p class="labels"></p><span class="list_info">Show Lists</span> <span class="favorites"></span> <span class="following"></span> <span class="followers"></span> <span class="follow_back">Calculate Follow Back %</span> <span class="location"></span> <span class="statuses"></span> <span class="created_at"></span> <span class="protected"></span> <span class="verified"></span> <p class="description"></p> <p class="subscriptions"></p> <p class="lists"></p> <p class="listed"></p> <div class="tweets"></div>';

    if ([].get('is:blocked:'+name)) 
        template = '<h5 class="blocked">User has been blocked.</h5>' + template;
 
    $('#preview').html(template); // will be filled in 

    if ([].get('user:lists:'+name))
        $.each([].get('user:lists:'+name), function(i, in_name) { 
            var list = [].get('list:'+in_name);

            var name = (list) ? list.name : in_name;

            if (name == '@'+[].get('user').name+'::friends::') 
                $('#preview>p.labels').
                    append('<label class="friend">Following</label> ');
            else {
                var lister = in_name.substring(1,[].get('user').name.length+1);

                if (lister == [].get('user').name)
                    $('#preview > p.labels').append('<label>'+name+'</label> ');
            }
        });

    var timeline = function(r) { 
        if (!r) return ['Twitter error'].store('warning');

        if (r.error && r.error.note) return [r.error.note].store('warning');
        if (r.error) return [r.error].store('warning');

        if (r.length == 0) return $('#preview > div.error').text('no tweets');

        var user = r[0].user;

        [r].store('timeline:'+user.screen_name);

        var cache = ([].get('user:lists:'+user.screen_name));

        [Listy.miniUser(user)].store('details:user:@'+user.screen_name, (cache && cache.length > 0));

        if (![].get('cursor').preview || user.screen_name.toLowerCase() != 
            [].get('cursor').preview.slice(1).toLowerCase()) return;

        $('#preview').trigger('update');

        $('#preview > span.followers').click(function() { 
            $.historyLoad('follows:'+[].get('cursor').preview); });

        $('#preview > span.favorites').click(function() { 
            if (![].get('user')) return Listy.authRequired();

            $.historyLoad('favorites:'+[].get('cursor').preview); });

        $('#preview > span.following').click(function() { 
            $.historyLoad('followed-by:'+[].get('cursor').preview); });

        $('#preview > span.location').click(function() { 
            $.historyLoad('location:@'+$(this).text().replace(/[\ ]/g,'+')) });

        //http://maps.google.com/maps?ll=21.502762,0.211701 for location

        var tweetHtml = function(t) {
            time = new Date(Date.parse(t.created_at)).getTime();

            time = Math.round((new Date().getTime() - time) / 1000 / 60);

            if (time < 60) time += ' minutes';
            else if (time < 60 * 60) time = Math.round(time / 60) + ' hours';
            else time = Math.round(time / 60 / 24) + ' days';

            return '<p>'+t.text.asTweet()+' <span>'+time+' ago</span></p>'; };

        for (var i = 0, days={}, sources={}, types={}, h = '', t; t = r[i]; i++) {
            h+= tweetHtml(t);

            sources[t.source] = (sources[t.source]) ? sources[t.source]+1 : 1;

            if (t.text.substring(0,1) == '@')
                types['replies'] = (types.replies) ? types.replies + 1 : 1;
            else if (t.text.match(/\bRT\b/i) !== null)
                types['retweets'] = (types.retweets) ? types.retweets + 1 : 1;
            else if (t.text.match(/\bhttp/) !== null)
                types['links'] = (types.links) ? types.links + 1 : 1;
            else 
                types['status'] = (types.status) ? types.status + 1 : 1;

            var d = new Date(Date.parse(t.created_at));
            d = d.getFullYear() + '.' + d.getMonth() + '.' + d.getDate();

            days[d] = (days[d]) ? days[d]+1 : 1;
        }

        var interpolate = function(totals, numDays) {
            var d, max = 0, range = [], dates = [], time = new Date();

            for (var i = 0; i < numDays; i++) {
                d = new Date(time.getTime() - (1000 * 24 * 60 * 60 * i));
                dates.unshift(d);
                d = d.getFullYear() + '.' + d.getMonth() + '.' + d.getDate();
                range.unshift(((totals[d]) ? totals[d] : -1));
                if (totals[d] > max) max = totals[d];
            }

            var sCode = function(vals, max) { 
                var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'+
                            '0123456789';
                for (var i = 0, data = ['s:'], current; current = vals[i]; i++)
                    data.push(((!isNaN(current) && current >= 0) ? 
                               chars.charAt(Math.round((chars.length-1) * 
                                                       current / max)) : '_'));

                return data.join('');
            };
            
            return {'range':range,'max':max,'dates':dates,'sCode':sCode(range,max)};
        };

        var weekday = function(d) { 
            return (['Sun','Mon','Tues','Wed','Thurs','Fri','Sat'])[d.getDay()]; };

        var monthday = function(d) { 
            var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep',
                          'Oct','Nov','Dec']

            return months[d.getUTCMonth()] + ' ' + d.getDate(); };

        var stats = interpolate(days, 7); // past 7 days stats

        var chart = 'http://chart.apis.google.com/chart?cht=lc&chco=9FC33C&chd='+
                    stats.sCode+'&chs=300x150&chxt=x,y&chxr=1,0,'+stats.max+','+
                    Math.round(1+stats.max/3)+'&chtt=This+Week+Tweets&chxl=0:||'+
                    weekday(stats.dates[1])+'|||'+weekday(stats.dates[3])+'||'+
                    weekday(stats.dates[6])+'|'+'&chg=20,50,1,5&chf=bg,s,FFFFFF00';

        if (r.length > 5) $('#preview > div.week').html(
            '<img width="300" height="150" src="'+chart+'"/>');

        var stats = interpolate(days, 30); // past 30 days stats

        var chart = 'http://chart.apis.google.com/chart?cht=lc&chco=9FC33C&chd='+
                    stats.sCode+'&chs=300x150&chxt=x,y&chxr=1,0,'+stats.max+','+
                    Math.round(1+stats.max/3)+'&chtt=Past+Month+Tweets&chxl=0:||'+
                    monthday(stats.dates[1])+'|||'+monthday(stats.dates[15])+'||'+
                    monthday(stats.dates[29])+'|'+'&chg=20,50,1,5&chf=bg,s,FFFFFF00';

        if (r.length > 15) $('#preview > div.month').html(
            '<img width="300" height="150" src="'+chart+'"/>');

        var stats = interpolate(days, 365); // past 1 year stats

        var chart = 'http://chart.apis.google.com/chart?cht=lc&chco=9FC33C&chd='+
                    stats.sCode+'&chs=300x150&chxt=x,y&chxr=1,0,'+stats.max+','+
                    Math.round(1+stats.max/3)+'&chtt=Past+Year+Tweets&chxl=0:||'+
                    monthday(stats.dates[1])+'|||'+monthday(stats.dates[150])+'||'+
                    monthday(stats.dates[360])+'|'+'&chg=20,50,1,5&chf=bg,s,FFFFFF00';

//        if (r.length > 30) $('#preview > div.year').html(
//            '<img width="300" height="150" src="'+chart+'"/>');

        var labels = [], percentages = [];
        for (var s in sources) { 
            var source = (s[0] == '<' || s[1] == '<') ? 
                s.replace(/^[^>]*/g,'').slice(1, -4) : s; 
            labels.push(source);
            percentages.push(Math.round(100*sources[s] / r.length));
        }
        $('#preview > div.sources').html('<img src="http://chart.apis.google.com/chart?chtt=Tweet+Sources&cht=p3&chd=t:'+percentages.join(',')+'&chco=BCD778&chf=bg,s,FFFFFF00&chs=300x100&chl='+labels.join('|')+'"/>');

        labels = []; percentages = [];
        for (var t in types) {
            labels.push(t.substring(0,1).toUpperCase()+t.substring(1));
            percentages.push(Math.round(100 * types[t] / r.length));
        }
        $('#preview > div.types').html('<img src="http://chart.apis.google.com/chart?chtt=Tweet+Types&cht=p3&chd=t:'+percentages.join(',')+'&chco=BCD778&chf=bg,s,FFFFFF00&chs=300x100&chl='+labels.join('|')+'"/>');        

        $('#preview > div.tweets').html(h);

        $('#preview > div.tweets > p > a').click(function() { 
            var q = $(this).text();

            var link = {
                'h' : function(u) { return true; },
                '@' : function(u) { Listy.go('|'+u.slice(1)); return false; },
                '#' : function(u) { $.historyLoad(u); return false; }
            };

            return link[q[0]]($(this).text()); });
    }; // end process timeline

    if ([].get('details:user:@'+name))
        $('#preview').trigger('update');

    if ([].get('timeline:'+name))
        timeline([].get('timeline:'+name));
    else {
        var args = {'api':'twitter.com/statuses/user_timeline.json',
                    'args':JSON.stringify({'screen_name':name,'count':50})};
        Twitter.request({'cbk':timeline, 'args':args});
    }

    $('#preview > span.follow_back').click(function() { 
        var cbk = function(r) { 
            if (!r || !r.length) return;

            var target = {'user'     :this.name,
                          'friends'  :r[0], 
                          'followers':r[1],
                          'account'  :[].get('user').id};

            target.friends = target.friends.ids || target.friends;
            target.followers = target.followers.ids || target.followers;

            if (!Listy.intersections)
                return ['Your browser does not support this feature'].store('warning');


            Listy.intersections.postMessage(target);
        };

        var name = [].get('cursor').preview.slice(1);
        
        cbk = $.proxy(cbk, {'name':name});

        ['Calculating follow back percentage'].store('doing');
        
        Twitter.addRequest({'api':"api.twitter.com/1/friends/ids/"+name+".json",
                    'args':{'cursor':'-1'}}).
                addRequest({'api':'api.twitter.com/1/followers/ids/'+name+'.json',
                    'args' : {'cursor':'-1'}}).
            executeRequests(cbk);
    }); // end follow_back click

    $('#preview > span.list_info').click(function() { 
        if (![].get('user')) return Listy.authRequired();

        Twitter.addRequest({'api':'api.twitter.com/1/'+name+'/lists.json'}).
           addRequest({'api':'api.twitter.com/1/'+name+'/lists/memberships.json'}).
           addRequest({'api':'api.twitter.com/1/'+name+'/lists/subscriptions.json'});

        $(this).hide();

        var lists = function(r) { 
            var locs = ['#preview p.lists','#preview p.listed',
                        '#preview p.subscriptions'];

            var lHtml = function(l) { 
                return '<a href="" class="list" rel="list:'+l.full_name+'">'+
                    l.user.name+' - '+l.name+' <span>'+l.member_count+' members: '+
                    l.subscriber_count+' following</span> <label>'+l.description+
                '</label>'; };

            var register = function(l) { [l].store('list:'+l.full_name); return l; };

            for (var x = 0, rr; rr = r[x]; x++) {
                if (rr.lists) {
                    for (var i = 0, h = '', list; list = rr.lists[i]; i++)
                        h+= lHtml(register(list));
                }
            
                $(locs[x]).html(h); 
            }

            $('#preview > p > a.list').click(function() { 
                $.historyLoad($(this).attr('rel')); return false; });
        };
        
        Twitter.executeRequests(lists);
    }); // end get twitter lists
};
    
Listy.findContacts = function(n) { 
    $("#credit").remove();

    var q = $('#header >form > input:first-child').val();

    if (!n) {
        $('#contacts > ul').html('');
        [{'q':q,'current':1,'selected':1}].store('cursor');
        [[]].store('current');
        [].store('selections');
    }

    var process = { // processes a query type
        'req' : function(results_type, cursor_type, args, url, filter) { // make request
            var req = {
                'cbk' : Listy.findUsers,
                'context' : {
                    'results_type' : results_type, 
                    'cursor_type' : cursor_type,
                    'q' : [].get('cursor').q
                }
            };

            if (filter) req.context.filter = filter;

            if (args) req.args = args;

            if (url) {
                req.url = url; 
                req.jsonp = true;
            }

            if (req.args && req.args.cache) {
                var cbk = (req.args.filter) ? req.args.filter : req.cbk;
                var context = (req.args.filter) ? req : req.context;

                return $.proxy(cbk, context)(req.args.cache);
            }

            Twitter.request(req);
        },
        'findFilters' : function(q) { // returns {cleanQuery:string, filter:function}
            var cleanQuery = q, filters = [];

            filters.push(/\bfollows[\-]?back:[lg]t:\d{1,3}\b/i);
            filters.push(/\bfollowed-by:[lg]t:(follows|\d{1,6})\b/i);
            filters.push(/\bfollows:[lg]t:(followed-by|\d{1,6})\b/i);
            filters.push(/\bfavs:[lg]t:\d{1,6}\b/i);
            filters.push(/-?\bfollowed-by:@([a-z0-9_]{1,17})\b/i);
            filters.push(/-?\bfollows:@([a-z0-9_]{1,17})\b/i);

            for (var i = 0, doFilter = false, filter; filter = filters[i]; i++)
                if (doFilter = filter.exec(q)) break;

            if (!doFilter) return {'cleanQuery':q, 'filter':null}; // no filters found

            do {
                for (var i = 0, j = 0, filter; filter = filters[i]; i++) {
                    if (filter.test(cleanQuery)) {
                        cleanQuery = cleanQuery.replace(filter, '');

                        j++;
                    }
                }
            } while (j > 0);

            return {
                'cleanQuery' : cleanQuery.replace(/\s*$/,''),
                'filter' : 
                    function(r, context) { 
                        delete context.filter; // avoid recursion

                        var users = []; // users to check

                        var found = Listy.parseUsersResponse(r, context.results_type);

                        Listy.parseResponseToken(r, context.cursor_type);

                        // get rid of users who do not fit the filters
                        for (var i = 0, u; u = found[i]; i++)
                            if (![].get('details:user:@'+u.name))
                                users.push(u.name);

                        var cbk = $.proxy(function(r) { // user lookup callback
                            if (!r) return;

                            for (var i = 0, mini = Listy.miniUser, u; u = r[i]; i++)
                                [mini(u)].store('details:user:@'+u.screen_name);

                            var numFilters = 0, filters = {
                                'pFollowsback':/\bfollows[\-]?back:([lg]t):(\d{1,3})\b/,
                                'nFollows':/\bfollows:([lg]t):(followed\-by|\d{1,6})\b/,
                                'nFollowed':/\bfollowed-by:([lg]t):(follows|\d{1,6})\b/,
                                'numFavs':/\bfavs:([lg]t):(\d{1,6})\b/,
                                'hasFollower':/(-?)\bfollowed-by:@([a-z0-9_]*)\b/i,
                                'doesFollow':/(-?)\bfollows:@([a-z0-9_]*)\b/i};

                            var doFilters = []; // the checks that must be executed

                            var cleanQ = q, j;

                            do {
                                j = 0;

                                for (i in filters) { 
                                    if (!filters[i].test(cleanQ)) continue;

                                    doFilters.push({'type':i, 
                                                    'match':filters[i].exec(cleanQ)});

                                    cleanQ = cleanQ.replace(filters[i], '');

                                    j++;
                                }
                            } while (j > 0);

                            if (doFilters.length == 0) // exit early
                                return $.proxy(Listy.findUsers, this.context)(this.raw);

                            var checks = [];

                            for (var i = 0, doLater = [], df; df = doFilters[i]; i++) { 
                                if (df['type'] == 'pFollowsback' ||
                                    df['type'] == 'hasFollower' ||
                                    df['type'] == 'doesFollow') {
                                    doLater.push(df);
                                    continue; // these require additional callbacks
                                }

                                if (df['type'] == 'numFavs') {
                                    checks.push($.proxy(function(details) { 
                                        var gt = (this.gtlt == 'gt') ? 1 : -1;
                                            
                                        return (details.favourites_count *gt > 
                                                this.num *gt);
                                    },
                                    {'num':parseInt(df['match'][2]),
                                     'gtlt':df['match'][1]}));
                                }

                                if (df['type'] == 'nFollowed') {
                                    checks.push($.proxy(function(details) { 
                                        var gt = (this.gtlt == 'gt') ? 1 : -1;

                                        var min = (this.min == 'follows') ? 
                                            details.friends_count : parseInt(this.min);

                                        return (details.followers_count *gt > min *gt);
                                    },{'min':df['match'][2],
                                       'gtlt':df['match'][1]}));
                                }
                            
                                if (df['type'] = 'nFollows') {
                                    checks.push($.proxy(function(details) { 
                                        var gt = (this.gtlt == 'gt') ? 1 : -1;

                                        var min = (this.min == 'followed-by') ? 
                                            details.followers_count : parseInt(this.min);

                                        return (details.friends_count *gt > min *gt);
                                    },{'min':df['match'][2],
                                       'gtlt':df['match'][1]}));
                                }
                            }

                            var found = Listy.parseUsersResponse(
                                this.raw, this.context.results_type);

                            var checked = {};

                            for (var i = 0,d, pass, filtered = [],u; u = found[i]; i++) {
                                if (!u || !u.name) continue;

                                if (checked[u.name.toLowerCase()]) continue;
                                else checked[u.name.toLowerCase()] = true;
                                
                                d = [].get('details:user:@'+u.name);

                                pass = true;

                                for (var j = 0, check; check = checks[j]; j++) {
                                    if (d && !check(d)) {
                                        pass = false;
                                        break;
                                    }
                                }

                                if (pass) filtered.push(u);
                            }

                            this.context.results_type = 'parsed';

                            if (!doLater.length) // exit: all done now
                                return $.proxy(Listy.findUsers, this.context)(filtered);

                            var dl = doLater[0];

                            if (dl['type']=='hasFollower' || dl['type']=='doesFollow') {
                                Listy.filters.socialTest(dl, filtered, this.context);

                                return; // TEMPORARY
                            } // end doesfollow/hasfollower

                            if (dl['type'] == 'pFollowsback')
                                Listy.filters.followsback(dl['match'], filtered, 
                                                          this.context);
                        }, {'context' : context, 'raw' : r});
                            
                        // if the details about the users are not cached, look em up.
                        if (users.length) {
                            ['Looking up ' + users.length + ' users'].store('doing');

                            var chunks = (function(arr, size) {
                                for (var x, i=0, c=-1, l=arr.length, n=[]; i < l; i++)
                                    (x = i % size) ? n[c][x] = arr[i] : n[++c]=[arr[i]];
 
                                return n; })(users, 100);

                            var combine = $.proxy(function(responses) { 
                                var details = [];

                                for (var i = 0, r; r = responses[i]; i++)
                                    for (var j = 0, d; d = r[j]; j++)
                                        details.push(d);

                                this.cbk(details);
                            }, {'cbk':cbk});

                            for (var i = 0, chunk; chunk = chunks[i]; i++)
                                Twitter.addRequest(
                                    {'api' : 'api.twitter.com/1/users/lookup.json',
                                     'args' : { 'screen_name' : chunk.join(',') }});

                            Twitter.executeRequests(combine);
                        }
                        else 
                            cbk([]);
                }
            };
        }
    };

    var twitter = {
        'subscribers-to-lists' : function(u, n) {
            var add = function(i, l) { 
                var api = 'api.twitter.com/1/'+l.full_name.slice(1)+
                          '/subscribers.json';
                Twitter.addRequest({'api':api});
            };

            $.each([].get('lists'), add);

            var cbk = function(r) {
                for (var i = 0, list, users = []; list = r[i]; i++) {
                    for (var j = 0, u; u = list.users[j]; j++) {
                        u.tweet_text = '(subscribed to ' +
                                       [].get('lists')[i].name+')';
                    }
                    users = $.merge(users, list.users);
                }

                return $.proxy(Listy.findUsers, {'results_type':'users',
                                                 'token_type':'none'})(users);
            }

            Twitter.executeRequests(cbk);
        },
        'blocked-by' : function(u, n) { 
            var req = {'api':'twitter.com/blocks/blocking.json',
                       'args':JSON.stringify({'page':n, 'count':100})};

            this.req('users', 'implicit_paged', req);
        },
        'dms-to' : function(u, n) {
            var req = {'api':'twitter.com/direct_messages.json',
                       'args':JSON.stringify({'max_id':n,'count':100})};

            this.req('dms', 'max_id', req, null, this.findFilters(u).filter);
        },
        'timeline' : function(u, n) {
            var req = {'api':'api.twitter.com/1/statuses/home_timeline.json',
                       'args':JSON.stringify({'max_id':n,'count':100})};

            this.req('tweets', 'max_id', req, null, this.findFilters(u).filter);
        },
        'mentions' : function(u, n) { 
            var req = {'api':'twitter.com/statuses/mentions.json',
                       'args':JSON.stringify({'max_id':n})};

            this.req('tweets', 'max_id', req, null, this.findFilters(u).filter);
        },
        'favorites' : function(u, n) { 
            var req = {'api':'twitter.com/favorites/'+this.findFilters(u).cleanQuery+
                       '.json', 'args':JSON.stringify({'page':n})};

            this.req('tweets','implicit_paged', req, null, this.findFilters(u).filter);
        },
        'follows' : function(u, n) {
            if (!n) n = -1;

            var filter = this.findFilters(u).filter || null;

            u = this.findFilters(u).cleanQuery;

            var user = [].get('details:user:@'+u);
            if (user) [{'total':user.followers_count}].merge('cursor');

            var req = {'api':'api.twitter.com/1/statuses/followers.json'};
            req.args = {'screen_name':u, 'cursor':n, 'suppress_response_codes':1};
            
            this.req('list_users','token', req, null, filter);

            if ([].get('user') && [].get('user').name.toLowerCase() == u.toLowerCase()) 
                (function(name) {
                var req = {
                    'args': {
                        'api':'api.twitter.com/1/followers/ids/'+name+'.json'
                    },
                    'full': true,
                    'cbk' : function(r) { 
                        if (!r || !r[0]) return;

                        for (var i = 0, follower; follower = r[i]; i++)
                            [true].store([].get('user').id+':follower:'+follower);
                    }
                }
                    
                Twitter.request(req);
            })(u);
        },
        'followed-by' : function(u, n) { 
            var filter = this.findFilters(u).filter || null;

            u = this.findFilters(u).cleanQuery;

            if ([].get('user') && [].get('user').name == u) ['Organize your follows by checking them and selecting a list from the dropdown.'].store('warning');

            var cache = [].get('list:members:@'+u+'::friends::');

            if (cache) // short circuit
                return this.req('cached_users', 'none', {'cache':cache}, null, filter);

            var user = [].get('details:user:@'+u);

            if (user) [{'total':user.friends_count}].merge('cursor');
            if (!n) n = -1;

            var req = {'api':'api.twitter.com/1/statuses/friends.json'};
            req.args = {'screen_name':u, 'cursor':n};

            this.req('list_users','token', req, null, filter);
        },
        'list' : function(id, n) { 
            var filter = this.findFilters(id).filter;

            id = this.findFilters(id).cleanQuery;

            var req = {'api':'api.twitter.com/1/'+id+'/members.json'};

            var info = [].get('list:@'+id);
            if (info) [{'total':info.member_count}].merge('cursor');

            var cache = [].get('list:members:@'+id);

            if (cache)
                return this.req('cached_users', 'none', {'cache':cache}, null, filter)

            if (n) req.args = JSON.stringify({'cursor':n});

            this.req('list_users', 'token', req, null, filter);
        },
        'user' : function(u, n) { 
            var req = {'api':'api.twitter.com/1/users/show.json',
                       'args':{'screen_name':u.replace(/[ ]*$/, '')}};

            this.req('single_user', 'none', req);
        },
        'search' : function(q, n) { 
            var url = 'http://search.twitter.com/search.json';
            var args = {'rpp':100, 'q':q};
            var filter = null;
            if (n) args.page = n;

            if (findFilters = this.findFilters(q)) {
                args.q = findFilters.cleanQuery;
                filter = findFilters.filter;
            }

            if (navigator && navigator.language == 'en-US') args.lang = 'en';
            
            var location = /\bnear:\w*\b/.exec(q);

            if (location && location.length) {
                var context = {"req":this.req, 'args':args, 'url':url};

                var cbk = $.proxy(function(r) { 
                    this.args.q = this.args.q.replace(/\bnear:\w*\b/, '');
                    this.args.geocode = r.join(',') + ',';

                    var within = /\bwithin:\w*\b/.exec(this.args.q);

                    if (within && within.length) { 
                        this.args.geocode += within[0].substring(7); 
                        this.args.q = this.args.q.replace(/\bwithin:\w*\b/, '');
                    }
                    else this.args.geocode += '25km';

                    this.req('search_tweets', 'explicit_paged', 
                             this.args, this.url, filter);
                }, context);

                $.getJSON('http://tinygeocoder.com/create-api.php?q='+
                          encodeURIComponent(location[0].substring(5)) +
                          '&callback=?', cbk);

                return;
            }

            this.req('search_tweets', 'explicit_paged', args, url, filter);
        },
        'http' : function(q, n) {
            $('#main').after('<div id="credit"><a target="_blank" href="http://backtype.com"><img src="http://s3.amazonaws.com/tweetbe.at/icons/backtype.gif"/></a></div>');

            var url = 'http://backtweets.com/search.json';
            var filter, args = {'q':q, 'itemsperpage':100, 'key':'247c929c177875b7b0e4'};

            if (n) args.page = n;

            if (findFilters = this.findFilters(q)) {
                args.q = findFilters.cleanQuery;
                filter = findFilters.filter;
            }

            this.req('bt_tweets', 'result_count', args, url, filter);
        },
        'experts' : function(q, n) { 
            var url = 'http://otter.topsy.com/experts.js';
            var args = {'q':this.findFilters(q).cleanQuery, 'perpage':30};

            if (n) args.page = n;

            this.req('otter', 'implicit_paged', args, url, this.findFilters(q).filter);
        },
        'bio':function(q,n) {
            var url = 'http://api.tlists.com/api/v2/users/search.json';
            var args = {'q':this.findFilters(q).cleanQuery, 'rpp':100};
            
            if (n) args.page = n;

            this.req('list_users','implicit_paged',args,url,this.findFilters(q).filter);
        },
        'location' : function(q, n) {
            var url = 'http://api.tweepsearch.com/search.json';
            var args = {'query':'location:'+this.findFilters(q).cleanQuery, 
                        'count':100, 'page':n};

            this.req('users','implicit_paged',args,url,this.findFilters(q).filter);
        },
        'subscribers' : function(id, n) {
            var filter = this.findFilters(id).filter;
            id = this.findFilters(id).cleanQuery;

            var info = [].get('list:@'+id);
            if (info) [{'total':info.subscriber_count}].merge('cursor');

            var req = {'api':'api.twitter.com/1/'+id+'/subscribers.json'};
            if (n) req.args = JSON.stringify({'cursor':n});

            this.req('list_users', 'token', req, null, filter);
        },
        'mentioned-to' : function(id, n) { 
            var req = {'api':'api.twitter.com/1/statuses/home_timeline.json',
                       'args':{'count':200}};
            this.req('mentioned-to', 'none', req, null, this.findFilters(id).filter);
        },
        'created-list' : function(q, n) { 
            var url = 'http://api.tlists.com/api/v2/lists/search.json';
            var args = {'q':this.findFilters(q).cleanQuery, 'rpp':100};

            if (n) args.page = n;

            this.req('lists', 'implicit_paged', args, url, this.findFilters(q).filter);
        },
        'suggested-for' : function(u) {
            var getSuggestions = $.proxy(function(r) { 
                if (!r || !r.length) return;

                var api = 'http://api.tlists.com/api/v2/users/suggest.json';

                r = r.slice(0, 300);

                return this.req('users', 'none', {'member_ids':r.join(',')}, api, 
                                this.filter);
            }, {'req' : this.req, 'filter' : this.findFilters(u).filter});

            if (/[a-z0-6\-]{1,16}\/[a-z0-9\-]{1,25}/gim.test(u)) { // if user/listName
                var users = [], listMembers = [].get('list:members:@' + u);

                if (!listMembers) return;

                for (var i = 0, u, details; u = listMembers[i]; i++) { // find ids
                    if (details = [].get('details:user:@'+u.screen_name))
                        users.push(details.id);
                }

                return getSuggestions(users); // short-circuit callback
            }

            Twitter.request({
                'cbk' : getSuggestions,
                'args' : {
                    'api' : 'api.twitter.com/1/friends/ids/' + 
                            this.findFilters(u).cleanQuery + '.json',
                    'args' : { ' cursor' : '-1' }
                }
            });
        },
        'talked-about' : function(id, n) {
            id = id.replace('/', '/lists/');

            var api = 'api.twitter.com/1/'+id+'/statuses.json';

            for (var i = 0; i < 2; i++)
                Twitter.addRequest({'api':api, 'args':{'per_page':200,'page':i + 1}});

            var cbk = function(r) { 
                for (var i = 0, results, tweets = [], tIds ={}; results = r[i]; i++) {
                    if (!results.length) continue;

                    for (var j = 0, result; result = results[j]; j++) {
                        if (tIds[result.id]) continue;

                        tweets.push(result);

                        tIds[result.id] = true;
                    }
                }

                $.proxy(Listy.findUsers, {'results_type':'talked-about',
                                          'token_type':'none'})(tweets); 
            };

            Twitter.executeRequests(cbk);
        },
        'latest' : function(id, n) {
            var filter = this.findFilters(id).filter || null;

            id = this.findFilters(id).cleanQuery;

            id = id.replace('/', '/lists/');

            var req = {'api':'api.twitter.com/1/'+id+'/statuses.json'};
            req.args = {'per_page':200};
            if (n) req.args = JSON.stringify({'max_id':n});

            this.req('tweets', 'max_id', req, null, filter);
        },
        'retweets-to' : function(id, n) {
            var req = {'api':'api.twitter.com/1/statuses/retweeted_to_me.json'};
            if (n) req.args = JSON.stringify({'max_id':n});

            this.req('retweets','retweet_max_id',req,null,this.findFilters(id).filter);
        },
        'listing' : function(name, n) {
            var filter = this.findFilters(name).filter;
            var cleanName = this.findFilters(name).cleanQuery;

            var req = {'api':'api.twitter.com/1/'+cleanName+'/lists/memberships.json'};
            
            var cursor = (n) ? n : -1;

            req.args = JSON.stringify({'cursor':n});

            this.req('listings', 'token', req, null, filter);
        }
    };

    var i, special = []; for (i in twitter) special.push(i);

    // check for special searches (non 'everyone search')
    for (var i = 0, s; s = special[i]; i++)
        if (q.substring(0,s.length + 1) == s+':') 
            return $.proxy(twitter[s], process)(q.substring(s.length+2), n);

    return $.proxy(twitter['search'], process)(q,n); // vanilla is search
}; // end find contacts

Listy.go = function(path) { 
    path = path.split('|');

    if (path[0] == '') path[0] = [].get('cursor').q;
    
    $.historyLoad(path.join('|'));
};

Listy.initLists = function() { 
    var me = [].get('user');

    if (!me) return;

    if (![].get('searches')) {
        var api = 'twitter.com/saved_searches.json'
        var cbk = function(r) { [r[0]].store('searches'); };
        Twitter.addRequest({'api':api}).executeRequests(cbk); 
    }

    var lists = [].get('lists');

    if (!lists) return (function() { 
        var cbk = function(r) { 
            var lists = (!r || !r.length || !r[0] || r[0].error || !r[0].lists) ? 
                [] : r[0].lists;

            if (r.error && r.error.note == 'rate limited') 
                ['List loading failed due to rate limit exceeded'].store('warning');

            [lists].store('lists'); Listy.initLists(); 
        };
        var api = 'api.twitter.com/1/'+me.name+'/lists.json';
        Twitter.addRequest({'api':api}).executeRequests(cbk); })();

    if (lists.length)
        [Listy.miniUser(lists[0].user)].store('details:user:@'+lists[0].user.screen_name, true);

    $.each(lists, function(i,n) { 
        [n].store('list:'+n.full_name, true); 

        var users;

        if (users = [].get('list:members:'+n.full_name))
            for (var i = 0, u; u = users[i]; i++)
                [[n.full_name]].merge('user:lists:'+u.screen_name);
    });

    var friends = [].get('list:members:@'+me.name+'::friends::'); 
    if (friends)
        for (var i = 0, u; u = friends[i]; i++)
            [['@'+me.name+'::friends::']].merge('user:lists:'+u.screen_name);

    $('#contacts').trigger('update');

    $('#lists').trigger('update');
}; // end init lists

Listy.init = function() { 
    $('#contacts > ul > div.welcome').trigger('update');

    if (window.Worker) {
        Listy.intersections = new Worker('js/intersection.js');
        Listy.intersections.onmessage = function(event) { 
            for (key in event.data) [event.data[key]].store(key); 
            $('#contacts').trigger('update_follow_back', [key]);

            if ([].get('cursor').preview) $('#preview').trigger('update');
        }
    }

    $.ajaxSetup({'url':'service/account.php'});

    $('body').ajaxStart(function() { 
        $(this).prepend('<div id="loading"></div>'); 
        $('#loading').bind('update', function() { 
            var label = ([].get('doing')) ? [].get('doing') : 'Loading';
            $(this).text(label + '...');
        });
        $('#loading').trigger('update');
    });
    $('body').ajaxStop(function() { $('#loading').remove(); [null].store('doing');});

    if ($.cookie('oauth_token')) {
        [{'name':$.cookie('user_name'), 'tok':$.cookie('oauth_token'),
          'id':$.cookie('user_id')}].store('user');

        var account = {};

        account[$.cookie('oauth_token')] = {
            'name'  : $.cookie('user_name'),
            'id'    : $.cookie('user_id'),
            'token' : $.cookie('oauth_token')};

        if (![].get('accounts')) [account].store('accounts', true);

        var first, accounts = [].get('accounts');

        for (i in accounts) { first = accounts[i].token; break; }

        $.getJSON('service/multiple.php?oauth_token=' + first, 
                  function(r) { [r].store('accounts', true); });

        $('#header > h1').click(function() { 
          $.historyLoad('followed-by:@' + [].get('user').name); });
    }

    if (![].get('user'))
        $.getJSON('service/oauth.php', function(r) { 
            if (r.error) location.reload(); 

            [r.tok].store('token'); 
        });
    else if (pageTracker) {
        pageTracker._trackPageview('/init/' + [].get('user').name);
//        new Image().src = 'http://is.gd/baOpT';
    }

    Store.binds['token'] = ['#login'];

    this.initLists();

    Store.binds['accounts']     = ['#accounts'];
    Store.binds['current']      = ['#contacts', '#main>div.selections'];
    Store.binds['cursor']       = ['#main>div.page', '#accounts',
                                   '#main>div.selections'];
    Store.binds['doing']        = ['#loading'];
    Store.binds['lists']        = ['#main > div.page'];
    Store.binds['user']         = ['#lists'];
    Store.binds['warning']      = ['#warning'];
    Store.binds['working-list'] = ['#main>div.page'];

    $('#header>form>span.go').click(function() { $(this).closest('form').submit();});

    $('#header>form>span.advanced').click(function() { 
        var q = $(this).closest('form').find('input').val();

        $('#filters').trigger('update', [q]); });

    $('#header>form').submit(function() { 
        $.historyLoad($(this).find('input:first-child').val()); 
        return false; });

    $.historyInit(function(path) { 
        $('#filters').removeClass('active').slideUp(200);

        [''].store('warning');

        path = path.split('|');

        if (pageTracker && typeof(path) != 'undefined') 
            pageTracker._trackPageview('/' + path.join('/'));

        $('#header>form input:first-child').val(path[0]);

        if (![].get('cursor') || [].get('cursor').q != path[0] ||
            ![].get('current').length) Listy.findContacts();

        [{'accounts':null}].merge('cursor');

        if (path[1]) {
            Listy.previewUser(path[1]);
        }
        else { 
            $('body').removeClass('preview');
            [{'preview':null}].merge('cursor');
        }

        $('#main > div.selections, #main > div.page, #accounts').trigger('update');
    }); // end history init

    if (![].get('cursor') && [].get('user'))
        Listy.go('followed-by:@'+[].get('user').name);

    $('#login').bind('update', function(e) { 
        if ([].get('user')) return $(this).html('');

        if ($(this).hasClass('authorizing')) {
            if ($.cookie('oauth_token')) {
                var account = {};

                if ([].get('accounts')) account = [].get('accounts');

                account[$.cookie('oauth_token')] = {
                    'name'  : $.cookie('user_name'),
                    'id'    : $.cookie('user_id'),
                    'token' : $.cookie('oauth_token')};

                [account].store('accounts', true);

                 return location.reload();
            }

            setTimeout(function() { $('#login').trigger('update'); }, 200);
        }

        var token = [].get('token');

        $('#contacts div.welcome').fadeIn(300);

        $(this).html("<a>Connect to my Twitter account</a>");

        $(this).children('a').click(function() { 
            var oauth = window.open('https://twitter.com/oauth/authorize' + 
                                    '?oauth_token=' + token,
                                    'oauth', "width=680,height=500"); 

            $('#login').addClass('authorizing').trigger('update');
        });
    });

    // trigger this whenever cursor data alters.
    $('#main > div.page').bind('update', function() { 
        var cursor = [].get('cursor');
        var user = [].get('user');

        if (JSON.stringify(cursor) == $(this).attr('data-cursor')) return;

        var current = [].get('current');

        if (!cursor) return;

        if (cursor.total == 0 && [].get('current').length == 0)
            $('#contacts > ul').html('<div class="none">No Users Found.</div>');
        
        var template = '';

        if (user) template += '<span class="actions"><span class="follow">Follow</span> <span class="unfollow">Unfollow</span> <span class="lists"></span> <span class="more">More Actions &#9662;</span> </span> <span class="cache"></span> <span class="cursor"> <a class="newer">Newer</a> <span class="current"></span> of <span class="total"></span> <a class="older">Older</a></span>';

        $(this).html(template);

        $(this).find('span.lists').html('<div><ol><li><input style="margin:0 3%; width:95%; display:none;" type="text"/></li></ol></div> <label>Add or Remove From Lists &#9662;</label>');

        $(this).find('span.more').html('<div><ol><li data-action="save_this_search">Save this search</li><li data-action="unsave_this_search">Unsave this search</li><li data-action="delete_this_list">Delete this list</li><li data-action="create_new_list">Create new list</li><li data-action="mark_as_seen">Mark as seen</li><li data-action="mark_as_unseen">Mark as unseen</li><li data-action="show_follow_back">Follow back %</li><li data-action="mark_as_spam">Report spam</li></ol></div> <label>More Actions &#9662;</label>');

        if ([].get('lists') && [].get('lists').length > 19) 
            $(this).find('span.more ol li[data-action=create_new_list]').hide();

        if (!(user && cursor.q && !cursor.preview && 
              new RegExp('^list:@'+user.name+'\/').test(cursor.q)))
            $(this).find('span.more ol li[data-action=delete_this_list]').hide();

        var isSaved = function(q) {
            var searches = [].get('searches');

            if (!searches) return false;

            for (var i = 0, search; search = searches[i]; i++)
                if (search.query == q) 
                    return true;

            return false;
        }

        if (!user || !cursor.q || cursor.preview || isSaved(cursor.q))
            $(this).find('span.more ol li[data-action=save_this_search]').hide();

        if (!user || !cursor.q || cursor.preview || !isSaved(cursor.q))
            $(this).find('span.more ol li[data-action=unsave_this_search]').hide();

        $(this).find('span.more').click(function(event) {
            if ($(this).is('.active') && $(event.target).is('label,span'))
                return $('div.page .active').removeClass('active');

            if (![].get('cursor').preview && $('#contacts>ul>li.checked').length ==
                $('#contacts>ul>li.checked.unseen').length)
                $(this).find('ol>li[data-action=mark_as_unseen]').hide();
            else $(this).find('ol>li[data-action=mark_as_unseen]').show();

            if (![].get('cursor').preview && 
                $('#contacts>ul>li.checked.unseen').length)
                $(this).find('ol>li[data-action=mark_as_seen]').show();
            else $(this).find('ol>li[data-action=mark_as_seen]').hide();

            event.stopPropagation();
            $('#main > div.page .active').removeClass('active');
            $(this).addClass('active');

            if (!$(event.target).is('li')) return;

            var selectedUsers = [], cursor = [].get('cursor');

            if (!cursor) return;

            if ($(event.target).attr('data-action') == 'delete_this_list') {
                var list = [].get('cursor').q.substring(6);

                if (!list) return;

                var deleteListCbk = function() { 
                    var user = [].get('user');
                    var getListsCbk = function(r) { [r[0].lists].store('lists'); 
                                            Listy.initLists(); };
                    var api = 'api.twitter.com/1/'+user.name+'/lists.json';
                    
                    Twitter.addRequest({'api':api}).executeRequests(getListsCbk);

                    ['List deleted successfully.'].store('warning');
                };

                var api = 'api.twitter.com/1/' + list.replace('/', '/lists/') + 
                    '.json';

                ['Deleting List'].store('doing');
            
                Twitter.addRequest({'api':api, 'post' : {'_method':'DELETE'}}).
                    executeRequests(deleteListCbk);

                return;
            }

            if ($(event.target).attr('data-action') == 'unsave_this_search') {
                $('#main > div.page>span.actions>span.more').removeClass('active');

                if (![].get('cursor').q) 
                    return ['No search to un-save'].store('warning');

                for (var i = 0, id, search; search = [].get('searches')[i]; i++) {
                    if (search.query == [].get('cursor').q) {
                        id = search.id;
                        break;
                    }
                }

                var request = {
                    'api' : 'api.twitter.com/1/saved_searches/destroy/'+id+'.json',
                    'post' : { '_method' : 'DELETE' }
                }

                var cbk = function(r) { 
                    ['Search removed from saved searches'].store('warning');

                    var searches = [].get('searches');

                    if (!searches || !r || !r.length) return;

                    for (var i = 0, id, search; search = searches[i]; i++) {
                          if (search.id == r[0].id) {
                            searches[i].deleted = true;
                            [searches].store('searches');
                            break;
                        }
                    }
                };

                ['Removing saved search'].store('doing');
                Twitter.addRequest(request).executeRequests(cbk);
            }

            if ($(event.target).attr('data-action') == 'save_this_search') {
                $('#main > div.page>span.actions>span.more').removeClass('active');

                if (![].get('cursor').q) 
                    return ['No search to save'].store('warning');

                var request = {
                    'api' : 'api.twitter.com/1/saved_searches/create.json',
                    'post' : {
                        'query' : [].get('cursor').q
                    }
                };

                var cbk = function(r) { 
                    if (!(r && r.length && r[0].query)) return;

                    ['Search saved to saved searches'].store('warning');

                    var searches = [].get('searches');

                    searches.push(r[0]);

                    [searches].store('searches');
                };

                ['Adding Saved Search'].store('doing');
                Twitter.addRequest(request).executeRequests(cbk);
            }

            if ($(event.target).attr('data-action') == 'create_new_list') {
                var list = prompt('List name?');

                if (!list || !list.length) return;

                var user = [].get('user');

                var addListCbk = function() { 
                    var user = [].get('user');
                    var getListsCbk = function(r) { [r[0].lists].store('lists'); 
                                            Listy.initLists(); };
                    var api = 'api.twitter.com/1/'+user.name+'/lists.json';
                    
                    Twitter.addRequest({'api':api}).executeRequests(getListsCbk);

                    ['List created successfully!'].store('warning');
                };

                var api = 'api.twitter.com/1/' + user.name + '/lists.json';
            
                Twitter.addRequest({'api':api, 'post' : {'name':list}}).
                    executeRequests(addListCbk);

                return;
            }

            if (cursor.preview) selectedUsers = [cursor.preview.slice(1)];
            else $('#contacts ul li input:checked').each(
                $.proxy(function(i, e) {
                    this.push($(e).closest('li').attr('rel'));
            }, selectedUsers));

            var getName = function(e) { 
                return (typeof(e) == 'string') ? e : 
                    $(e).closest('li').attr('rel');
            };

            var action = {
                'mark_as_seen' : function(i, e) { 
                    var name = this.getName(e);
                
                    [new Date()].store('seen:'+name, true);
                },
                'mark_as_unseen' : function(i, e) {
                    var name = this.getName(e);
                
                    [null].store('seen:'+name, true);
                },
                'mark_as_spam' : function(i, e) {
                    var name = this.getName(e);
                
                    [new Date()].store('is:blocked:'+name, false);

                    Twitter.addRequest({'api':'api.twitter.com/1/report_spam.json', 
                                        'post' : {'screen_name':name}});
                },
                'show_follow_back' : function(i, e) { 
                    var percent, name = this.getName(e);

                    if ([].get('follow_back:user:@'+name)) return;

                    var cbk = function(r) { 
                        if (!r || !r.length || !r[0] || !r[1]) return;
                        if (r[0].error || r[1].error) return;

                        var target = {'user'     :this.name,
                                      'friends'  :r[0], 
                                      'followers':r[1],
                                      'account'  :[].get('user').id};

                        target.friends = target.friends.ids || target.friends;
                        target.followers = target.followers.ids || target.followers;

                        if (!Listy.intersections) 
                            return ['Your browser does not support this feature'].
                                store('warning');

                        Listy.intersections.postMessage(target);

                        var u = [].get('details:user:@'+this.name);

                        if (!u && 
                            (target.followers.length === 0 || 
                             target.followers.length) &&
                            (target.friends.length === 0 || target.friends.length)) {

                            var ratio = {'followers':target.followers.length,
                                         'friends':target.friends.length};

                            if (ratio.followers > 4900) ratio.followers+= '+';
                            if (ratio.friends > 4900) ratio.friends+= '+';

                            [ratio].store('ratio:user:@'+this.name, false);
                        }
                    };
                    
                    cbk = $.proxy(cbk, {'name':name});

                    ['Calculating follow back percentage'].store('doing');

                    Twitter.addRequest({'api':"api.twitter.com/1/friends/ids/"+name+
                                ".json", 'args':{'cursor':'-1'}}).
                    addRequest({'api':'api.twitter.com/1/followers/ids/'+name+'.json',
                                'args' : {'cursor':'-1'}}).
                    executeRequests(cbk);
                }
            }[$(event.target).attr('data-action')];

            $.each(selectedUsers, $.proxy(action, {'getName':getName}));

            $('#contacts').trigger('update');
            if (cursor.preview) Listy.go('|'+cursor.preview.slice(1)); // refresh

            Twitter.executeRequests(function(r) { });

            $(this).removeClass('active');
        }); // end more action click
        
        var updateList = function(e) { 
            e.stopImmediatePropagation();

            if ($(this).is('input[type=checkbox]') && 
                $(this).attr('data-indeterminate')) {
                $(this).removeAttr('data-indeterminate').attr('checked', true);
            }

            var cursor = [].get('cursor');

            var selection = (cursor.preview) ? [cursor.preview] : 
                $('#contacts > ul > li > input:checked');

            if (selection.length == 0) return ['No users selected'].store('warning');

            $.each(selection, $.proxy(function(i, e) { // update selected's lists
                var name = (typeof(e) == 'string') ? e.slice(1) : 
                    $(e).closest('li').attr('rel');

                var list, method, request;

                if ($(this).is('input[type=checkbox]') && 
                    $(this).closest('li').attr('data-name')) {
                    list = $(this).closest('li').attr('data-name');

                    method = ($(this).attr('checked')) ? 'add' : 'delete';

                    if ($(this).attr('data-indeterminate')) method = 'add';

                    var doing = (method == 'add') ? 'Adding to list' : 
                        'Removing from list';

                    [doing].store('doing');

                    var post = {}, details = [].get('details:user:@'+name);

                    if (details) post.id = details.id;
                    else post['to_id:name'] = name;
                    
                    request = {'api' : 'api.twitter.com/1/'+list+'/members.json',
                               'post' : post};

                    if (method == 'delete') request['post']['_method'] = 'DELETE';
                } else if ($(this).is('span.follow, span.unfollow')) {
                    list = [].get('user').name + '::friends::';
                    method = ($(this).is('span.follow')) ? 'add' : 'delete';

                    var doing = (method == 'add') ? 'Following' : 'Unfollowing';
                    [doing].store('doing');

                    var date = new Date();
                    var dString = [date.getFullYear(),date.getMonth(),date.getDay()].join('/');
                    var tok = [].get('user').tok;

                    if (![].get('actions:' + tok) || ![].get('actions:'+tok)[dString]) 
                        [{'actions':{}}].store('actions:'+tok, true);

                    var actions = [].get('actions:'+tok);

                    if (!actions[dString]) actions[dString] = {'follows':0, 'unfollows':0};

                    if (method == 'add') actions[dString].follows++;
                    else actions[dString].unfollows++;

                    [actions].store('actions:'+tok, true);

                    request = {'post':{'screen_name':name}};

                    request.api = 'api.twitter.com/1/friendships/' + 
                        ((method == 'add') ? 'create' : 'destroy') + '.json';

                    request.args = {'screen_name' : name};
                }

                if (method == 'delete') {
                    var lists = [].get('user:lists:'+name);
                
                    if (lists) { // recalc user lists membership info
                        for (var i = 0, l, rLists = []; l = lists[i]; i++)
                            if ('@'+list != l) rLists.push(l);
                    
                        [rLists].store('user:lists:'+name);
                    }
                
                    var members = [].get('list:members:@'+list);

                    if (members) { // recalc list members
                        for (var i = 0, u, users = []; u = members[i]; i++)
                            if (u.screen_name != name) users.push(u);
                    
                        [users].store('list:members:@'+list, true);
                    }
                }
                else {
                    [['@'+list]].merge('user:lists:'+name, false);

                    if ([].get('list:members:@'+list))
                        [[{'screen_name':name}]].merge('list:members:@'+list);
                }
               
                Twitter.addRequest(request);
            }, this)); // end cycle through selected users

            if (cursor.preview) Listy.go('|'+cursor.preview.slice(1)); // refresh
            
            $('#contacts').trigger('update'); // the list labels may have changed
            $('#contacts').trigger('update_selections');
            
            Twitter.executeRequests(function(r) { 
                if (!r || !r.length || !r[0]) return;

                if (r[0].profile_image_url) {
                    var date = new Date(),
                        user = [].get('user'),
                        dString = [date.getFullYear(),date.getMonth(),date.getDay()];

                    dString = dString.join('/');

                    if ([].get('actions:'+user.tok) === null)
                        return [r.length + ' follow statuses changed'].store('warning');
                    
                    var todaysFollows = [].get('actions:' + user.tok)[dString].
                        unfollows + [].get('actions:'+user.tok)[dString].follows;

                    return [r.length + ' follow statuses changed (' + todaysFollows + 
                        ' total today)'].store('warning');
                }

                if (!r.length || !r[r.length - 1].full_name) return;
                    
                [r[r.length - 1]].store('list:'+r[r.length - 1].full_name); 
                ['&#8220;' + r[r.length -1].name + '&#8221; now has ' + 
                    r[r.length - 1].member_count + ' members.'].store('warning');
            });
        }; // end updateList

        $(this).find('span.follow, span.unfollow').click(updateList);

        $(this).find('span.lists').click($.proxy(function(e) { 
            var span = ($(e.target).is('span.lists')) ? e.target : 
                $(e.target).closest('span.lists');

            if ($(span).is('.active') && $(e.target).is('span,label'))
                return $(span).removeClass('.active');

            var lists = [].get('lists');

            var selectionsLists = [].get('selections:lists');

            var total = $('#contacts > ul > li input:checked').length;

            if (!selectionsLists) selectionsLists = {};

            if ([].get('cursor').preview) {
                selectionsLists = {};

                var userLists = [].get('user:lists:'+
                                       [].get('cursor').preview.substring(1));

                if (!userLists) userLists = [];
                
                for (var i = 0, l; l = userLists[i]; i++)
                    selectionsLists[l] = 1;

                total = 1;
            }
            
            if (lists) {
                for (var i = 0, h = '', l; l = lists[i]; i++) {
                    var checked = (selectionsLists[l.full_name] == total);
                    var indeterminate = (selectionsLists[l.full_name] && !checked);

                    h+='<li data-name="'+l.full_name.substring(1)+
                        '"><input type="checkbox" ' +
                        ((checked) ? 'checked="checked" ' : '') + 
                        ((indeterminate) ? 'data-indeterminate="true"' : '') +
                         '/> '+ l.name+'</li>';
                }
            }

            $(span).find('ol').html(h);
            $(span).find('ol li input[data-indeterminate=true]').
                attr('indeterminate', true);

            e.stopPropagation(); 
            $('#main > div.page .active').removeClass('active');
            $(span).addClass('active');

            var selectedUsers = [], cursor = [].get('cursor');

            if (!cursor) return;

            $(span).find('li input[type=checkbox]').click(this.updateList);
        }, {'updateList' : updateList})); // end click on list

        $('#main > a.back').text(cursor.q);

        var meta = { // this is for caching total result sets.
            'list':function(self, query) { // cache this entire list?
                var list = [].get('list:@'+query);

                if (!list) return; //we know nothing about this list...

                var user = [].get('user');
                var whose = (user && list.user.id == user.id) ? 'Your' :
                    list.user.name + "'s";

                $('#main > a.back').text(whose + ' List: ' + list.name);

                if (list.user.id != [].get('user').id) return; //not our list

                var cached = [].get('list:members:@'+query);

                $(self).find('span.cache').html('<span class="load">'+
                    ((cached) ? 'Refresh' : 'Load All') + '</span>');

                if (!cached) ['Want fast loading and labels? Click "Load All"'].
                                 store('warning');

                $(self).find('span.cache span').bind('click',list,function(e){ 
                    var req = {'api':'api.twitter.com/1'+e.data.uri+'/members.json', 
                               'context':e.data.full_name, 'full':true };

                    ['Caching List'].store('doing');

                    var c = function(r){ 
                        if (r.error || !r[0].data) return;

                        for (var i = 0, u, users = []; u = r[0].data[i]; i++) {
                            users.push({'screen_name':u.screen_name});
                            [[r[0].context]].merge('user:lists:'+u.screen_name);

                            [Listy.miniUser(u)].store('details:user:@'+u.screen_name, true);
                        }

                        [users].store('list:members:'+r[0].context, true); 

                        ['List successfully cached!'].store('warning');

                        Listy.findContacts();
                    }; // end cache list

                    ['Caching list - this can take a while, please hold on.'].
                        store('warning');
                    
                    Twitter.addRequest(req).executeRequests(c);
                });
            }, // end lists meta func
            'listing':function(self, q) {
                var name = ([].get('user') && q == [].get('user').name) ? 
                'you' : (([].get('details:user:@'+q)) ? 
                         [].get('details:user:@'+q).name : q);

                $('#main > a.back').text('Added '+name+' to a list');
            },
            'follows':function(self, q) {
                var name = ([].get('user') && q == [].get('user').name) ? 
                'you' : (([].get('details:user:@'+q)) ? 
                         [].get('details:user:@'+q).name : q);

                $('#main > a.back').text('Follows '+name);
            },
            'dms-to':function(self,q) {
                $('#main > a.back').text('Dms with you');
            },
            'timeline':function(self, q) {
                $('#main > a.back').text('Most recent tweets from people you follow');
            },
            'mentions':function(self,q) {
                $('#main > a.back').text('Mentions you');
            },
            'retweets-to':function(self,q) {
                $('#main > a.back').text('Users retweeted to you');
            },
            'user':function(self,q) {
                var user = [].get('details:user:@'+q);

                if (user) $('#main > a.back').text(user.name);
            },
            'subscribers':function(s, q) { 
                var list = [].get('list:@'+q);
                
                if (list) $('#main > a.back').text('Subscribed to '+list.name);
            },
            'location': function(self, q) {
                $('#main > a.back').text('Location: '+q);
            },
            'http' : function(self, q) {
                $('#main > a.back').text('Linking to '+q.slice(1));
            },
            'talked-about' : function(self, q) { 
                var label = q, user = [].get('user'), list = [].get('list:@'+q);

                if (user && list && user.id == list.user.id)
                    label = 'your ' + list.name + ' list';

                $('#main > a.back').text('Most mentioned in '+label);
            },
            'followed-by':function(self, q) { 
                var user = [].get('details:user:@'+q);

                if (user) $('#main > a.back').text(user.name+" follows");

                if (user && [].get('cursor').q == 'followed-by:@'+user.screen_name+
                    ' follows:@'+user.screen_name)
                    $('#main > a.back').text(user.name + '\'s mutual follows');

                if (![].get('user') || q.split(' ')[0] != [].get('user').name)
                    return;

                var you = [].get('user');

                $('#main > a.back').text('You follow');

                if ([].get('cursor').q == 'followed-by:@'+you.name+' follows:@'+you.name)
                    $('#main > a.back').text('Your mutual follows');

                if ([].get('cursor').q == 
                    'followed-by:@'+you.name+' -follows:@'+you.name)
                    $('#main > a.back').text('Doesn\'t follow me back');

                var cached = [].get('list:members:@'+you.name+'::friends::');

                $(self).find('span.cache').html('<span class="load">' +
                    ((cached) ? 'Refresh' : 'Load All') + '</span>');

                if (!cached) ['Want fast loading and labels? Click "Load All"'].
                                 store('warning');

                $(self).find('span.cache span').bind('click',function(e){ 
                    var c = function(r){ 
                        if (r.error) return;

                        var list = '@'+[].get('user').name+'::friends::';

                        for (var i = 0, u, users = []; u = r.data[i]; i++) {
                            users.push({'screen_name':u.screen_name});

                            if (![].get('user:lists:'+u.screen_name))
                                [[]].store('user:lists:'+u.screen_name);
                            
                            [[list]].merge('user:lists:'+u.screen_name);

                            [Listy.miniUser(u)].store('details:user:@'+u.screen_name,true);
                        }

                        [users].store('list:members:'+list, true);

                        ['Friend list successfully cached!'].store('warning');

                        Listy.findContacts();
                    }; // end cache friends callback

                    var username = [].get('user').name;

                    var request = { 
                        'args': {
                            'api':'api.twitter.com/1/statuses/friends.json',
                            'full':true,
                            'args':JSON.stringify({'screen_name':username})
                        },
                        'cbk': c
                    };

                    ['Caching Follows'].store('doing');
                    ['Caching your friends list - this can take a minute, please hold on.'].store('warning');

                    Twitter.request(request);
                }); // end cache click
            } // end friends meta function
        };            

        var special = [], i;
        for (i in meta) special.push(i); 

        // show cache result set button?
        for (var i = 0, s; s = special[i]; i++)
            if (cursor.q.substring(0,s.length + 1) == s+':') 
                meta[s](this, cursor.q.substring(s.length+2));

        (function() { //make filters nice
            var filters = {
                'near':/\bnear:([a-z_-]*)\b/i,
                'source':/\bsource:([^ ]*)\b/i,
                'within':/\bwithin:([0-9a-z]*)\b/i,
                'not-source':/\-\bsource:([_a-z0-9]*)\b/i,
                'follows-back-lt':/\bfollows[\-]?back:lt:(\d{1,3})\b/i,
                'follows-back-gt':/\bfollows[\-]?back:gt:(\d{1,3})\b/i,
                'follows-lt':/\bfollows:lt:(followed-by|\d{1,10})\b/i,
                'follows-gt':/\bfollows:gt:(followed-by|\d{1,10})\b/i,
                'followed-by-lt':/\bfollowed-by:lt:(follows|\d{1,10})\b/i,
                'followed-by-gt':/\bfollowed-by:gt:(follows|\d{1,10})\b/i,
                'not-follows':/[\-]\bfollows:@([a-z0-9_]{1,16})\b/i,
                'favs-gt':/\bfavs:gt:(\d{1,10})\b/i,
                'favs-lt':/\bfavs:lt:(\d{1,10})\b/i,
                'favorites':/^favorites:(@[a-z0-9_]{1,17})\b/i,
                'experts':/^experts:@([^ ]*)\b/i,
                'on-list':/^list:@([a-z0-9_\-\/]*)\b/i,
                'mentioned-to':/^mentioned-to:@([a-z0-9_]*)\b/i,
                'bio':/^bio:@([^ ]*)/i,
                'created-list':/^created-list:@([^ ]*)\b/i,
                'latest':/^latest:@([a-z0-9_]*)\/([^ ]*)\b/i
            };

            var labels = {
                'bio':'Bio: $1',
                'latest':'Last tweeted on $1\'s list: $2',
                'within':'(within $1)',
                'experts':'$1 experts',
                'near':'(near $1)',
                'source':'(via $1)',
                'not-source':'(not via $1)',
                'follows-back-lt':'(follows back less than $1%)',
                'follows-back-gt':'(follows back more than $1%)',
                'follows-lt':'(follows fewer than $1)',
                'follows-gt':'(follows more than $1)',
                'followed-by-lt':'(followed by fewer than $1)',
                'followed-by-gt':'(followed by more than $1)',
                'mentioned-to':'Most mentioned in your timeline',
                'on-list':'On list: $1',
                'not-follows':'(doesn\'t follow @$1)',
                'favs-lt':'(with fewer than $1 favorites)',
                'favs-gt':'(with more than $1 favorites)',
                'favorites':'Favorited by $1',
                'created-list':'Created a list named $1'
            };

            var label = $('#main > a.back').text();

            for (i in filters) {
                var matches = filters[i].exec(label);

                if (!matches) continue;

                var replaceWith = labels[i];

                label = label.replace(filters[i], replaceWith);
            }

            $('#main > a.back').text(label);
        })();

        cursor = [].get('cursor'); // may have changed...

        var status = 'start';

        if ($('body').hasClass('preview')) {
            status = (!cursor.token && $('#contacts > ul > li.selected').
                      is(':last-child')) ? 
                'end' : ((cursor.current == 1) ? 'start' : 'middle');

            if (status == 'end' && cursor.current == 1) status = 'complete';
        }
        else if (!cursor.token) status = 'complete';

        var range = ($('body').hasClass('preview')) ? 
            cursor.current : ((cursor.range) ? 1 + '-' + cursor.range : '...');

        if (cursor.range === 0) range = '0';

        var total = (cursor.total === 0) ? '0' : 
            ((cursor.total) ? cursor.total : 'Many');

        $(this).removeClass('start middle end complete').addClass(status);
        $(this).find('span.total').text(total);
        $(this).find('span.current').text(range);

        $('#main > a.back').click(function() { 
            var cursor = [].get('cursor');
            if (!cursor || !cursor.preview) return false;
                
            Listy.go([].get('cursor').q);
            if ([].get('pageYOffset')) window.scroll(0, [].get('pageYOffset'));

            return false; });

        $(this).find('a.older').click(function() { 
            if ($(this).closest('div.page').hasClass('end')) return false;

            var cursor = [].get('cursor');

            if ($('#contacts>ul>li:last').hasClass('selected'))
                return Listy.findContacts(cursor.token);

            if ($('body').hasClass('preview')) 
                return Listy.go('|'+$('#contacts > ul > li.selected').
                                removeClass('selected').next().
                                addClass('selected').attr('rel'));

            if ($('#contacts > ul > li:last').position())
                window.scroll(0, $('#contacts > ul > li:last').position().top);

            return Listy.findContacts(cursor.token);
        });

        $(this).find('a.newer').click(function() { 
            Listy.go('|'+$('#contacts > ul > li.selected').
                               removeClass('selected').prev().addClass('selected').
                               attr('rel'));
        });

        $(this).attr('data-cursor', JSON.stringify(cursor));
    }); //end update cursor

    $(document).click(function(e) {
        var target = $(e.target);

        if (target.is('div.page input.more') && $('#more_actions').length > 0)
            return true;

        if (target.is('#lists > a, #launcher, #launcher > a')) return true;
        if (target.is('input.more, #more_actions, #more_actions input')) return true;

        $('#launcher, #more_actions').remove();
        $('#lists > a.selected').removeClass('selected');
    });

    $(document).keypress(function(e) {
        if (e.target.type == 'search' || e.target.type == 'text' ||
            e.target.tagName == "TEXTAREA") return true;

        var cursor = [].get('cursor');

        var getSelections = function() { 
            var names = [];
            
            if ([].get('cursor').preview) 
                names.push([].get('cursor').preview.slice(1));
            else if (![].get('selections')) 
                return ['no users selected'].store('warning');
            else {
                for (name in [].get('selections')) names.push(name);
            }

            return names;
        };

        if (e.charCode == 117) { // 'u'
            $('#main > a.back').click();
        }

        if (e.charCode == 120) { 
            var current = [].get('current'), sel = {};

            if (!current || !current.length) return;

            var name = [].get('current')[cursor.selected - 1].name;

            sel[name] = new Date().getTime();

            if (![].get('selections')) [sel].store('selections');

            if ([].get('selections')[name]) sel[name] = null;

            [sel].merge('selections');
            
            $('#contacts').trigger('update');
        }

        if (e.charCode == 33) {
            $(getSelections()).each(function(i, name) { 
                [new Date()].store('is:blocked:'+name, false);

                Twitter.addRequest({'api' : 'twitter.com/report_spam.json', 
                                    'post' : {'screen_name':name}});
            });

            ['Reporting spam'].store('doing');
            Twitter.executeRequests();
            
            $('#contacts').trigger('update');
            if (cursor.preview) Listy.go('|'+cursor.preview.slice(1)); // refresh
        }

        if (e.charCode == 106) {
            var selected = [].get('cursor').selected;

            if (![].get('current')) return;

            if (selected == [].get('current').length)
                return $("#main>div.page:first a.older:visible").click();

            [{'selected':++selected}].merge('cursor');

            if (!$('body').hasClass('preview')) {
                var s = $('#contacts > ul > li.selected');
                
                if (s.position().top > window.innerHeight + window.scrollY - 70)

                    scroll(0, s.position().top - Math.round(window.innerHeight / 2));

                $('#contacts > ul > li.selected').removeClass('selected').next().
                    addClass('selected');
            }
            else {
                $("#main>div.page:first a.older:visible").click();
            }
        }

        if (e.charCode == 107) {
            var selected = [].get('cursor').selected;

            if (selected == 1) return;

            [{'selected':--selected}].merge('cursor');

            if (!$('body').hasClass('preview')) {
                var s = $('#contacts > ul > li.selected');
                
                if (s.position().top < window.scrollY + 30)
                    scroll(0, s.position().top - Math.round(window.innerHeight / 2));

                $('#contacts > ul > li.selected').removeClass('selected').prev().
                    addClass('selected');
            }
            else {
                $("#main>div.page:first:not('.start') a.newer").click();
            }
        }

        if (e.charCode == 111 && !$('body').hasClass('preview'))
            $('#contacts > ul > li.selected > a').click();

        return true;
    });

    $('#lists').ajaxSuccess(function(e, r, s) {
        if (!r) return;

        var limit = {
            "calls":r.getResponseHeader('X-RateLimit-Remaining'),
            "reset":r.getResponseHeader('X-RateLimit-Reset')
        };

        var type = r.getResponseHeader('X-RateLimit-Class');

        if (type && type == 'api_identified') limit.identified_calls = limit.calls;
        if (type && type == 'api') limit.api_calls = limit.calls;

        if (limit.calls === 0) 
            return $(this).find('a.api_units').html('api units depleted');

        if (!limit.calls) return;

        [limit].store('api_units', false);

        $('#lists > a[rel=":user"], #lists > a.api_units').
            attr('title', limit.calls + ' API units remaining');

        var warning = (limit.calls < 75) ? 'api units warning' : '';

        $(this).find('a.api_units').html(warning);
    });

    $('#filters').bind('update', function(e, q) { 
        if ($(this).is('.active')) return $(this).removeClass('active').slideUp(200);

        var parseQuery = function(q) { 
            var query = []; // a series of filters

            var base = q.split(' ')[0];

            var cleanQ = $.proxy(function(q) { 
                var filters = {
                    'follows-back':/\bfollows[\-]?back:([lg]t):(\d{1,3})\b/,
                    'follows':/\bfollows:([lg]t):(followed\-by|\d{1,6})\b/,
                    'followed-by':/\bfollowed-by:([lg]t):(follows|\d{1,6})\b/,
                    'favs':/\bfavs:([lg]t):(\d{1,6})\b/};

                do {
                    var j = 0;

                    for (i in filters) {
                        var matches = filters[i].exec(q);
                        
                        if (!matches) continue;

                        j++;
                        
                        q = q.replace(filters[i], '');
                        
                        var params = {'select.gtlt':matches[1],
                                      'input[type=text]':matches[2]};
                        
                        this.query.push({'type':i, 'params':params});
                    }
                } while (j > 0);

                return q;
            }, {'query':query})(q);

            var types = {'followed-by' : /^followed-by:@([a-z0-9\_\-]{1,16})/i,
                         'timeline' : /^timeline:@([a-z0-9\_]{1,16})/i,
                         'follows' : /^follows:@([a-z0-9\_\-]{1,16})/i,
                         'mentions' : /^mentions:@([a-z0-9\_\-]{1,16})/i,
                         'favorites' : /^favorites:@([a-z0-9\_\-]{1,16})/i,
                         'retweets-to' : /^retweets-to:@([a-z0-9\_\-]{1,16})/i,
                         'dms-to' : /^dms-to:@([a-z0-9\_\-]{1,16})/i,
                         'listing' : /^listing:@([a-z0-9\_\-]{1,16})/i,
                         'list' : /^list:@([a-z0-9\_\-\/]{1,50})/i,
                         'near' : /^near:([a-z]*)/i,
                         'user' : /^user:@([a-z0-9_]*)/i,
                         'talked-about' : /^talked-about:@([a-z\-0-9_\/]*)/i,
                         'created-list' : /^created-list:@([^ ]*)/i,
                         'latest' : /^latest:@([a-z0-9_\-\/]*)/i,
                         'mentioned-to' : /^mentioned-to:@([a-z0-9_]*)/i,
                         'subscribers' : /^subscribers:@([a-z0-9\/\-_]*)/i,
                         'location' : /^location:@([a-z\+]*)/i,
                         'experts' : /^experts:@([^ ]*)/i,
                         'suggested-for' : /^suggested\-[f]or:@([^ ]*)/i,
                         'latest' : /^latest:@([^ ]*)/i,
                         'bio' : /^bio:@([^ ]*)/i};

            for (i in types) {
                if (types[i].test(base)) {
                    query.unshift({'type':i, 
                        'params':[{'input[type=text]':types[i].exec(cleanQ)[1]}]});

                    return query;
                }
            }

            query.unshift({'type':'search','params':[{'input[type=search]':cleanQ}]});

            return query;
        };

        var filtersHtml = function(query) {
            var h = '';

            var expected = {'mentioned' : 'you',
                            'mentions' : 'you',
                            'retweets-to' : 'you',
                            'dms-to' : 'you'};

            var groups = {'Search' : ['search', 'location', 'near', 'user', 'experts', 
                                      'bio'],
                          'Social' : ['followed-by', 'follows', 'listing', 'timeline',
                                      'favorites', 'suggested-for'],
                          'Mentions' : ['mentions', 'retweets-to', 'dms-to', 
                                        'talked-about', 'mentioned-to'],
                          'Lists' : ['list', 'subscribers', 'created-list',
                                     'latest']};
                                        
            var types = {'search' : 'tweeted',
                         'experts' : 'experts on',
                         'bio' : 'with bio',
                         'latest' : 'last tweeted on list',
                         'followed-by' : 'followed by',
                         'follows' : 'who follow',
                         'mentions' : 'mentions you',
                         'favorites' : 'who are favorites of',
                         'retweets-to' : 'retweeted to you',
                         'dms-to' : 'who DM\'ed you',
                         'listing' : 'listing',
                         'list' : 'on list',
                         'latest' : 'last tweeted on list',
                         'near' : 'tweeting near',
                         'suggested-for' : 'suggested for',
                         'talked-about' : 'mentioned on list',
                         'mentioned-to' : 'most mentioned to you',
                         'timeline' : 'followed by me',
                         'subscribers' : 'subscribers to list',
                         'created-list' : 'created a list named',
                         'user' : 'with screen name',
                         'location' : 'located in'};

            h+= '<div><span>Users</span> <select>';

            for (i in groups) { 
                h+= '<optgroup label="' + i + '">';

                for (var j = 0, type, seleted; type = groups[i][j]; j++) {
                    selected = ((query[0].type == type) ? 'selected="selected" ' : '');

                    h+= '<option '+selected+'value="'+type+'">'+types[type]+'</option>';
                }

                h+= '</optgroup>';
            }

            for (i in query[0].params[0]) {
                if (i == 'input[type=text]') 
                    h+= ' <input type="text" style="width:200px;" value="' + 
                        query[0].params[0][i].replace(/\"/,'&quot;') + '"></div>';
                else if (i == 'input[type=search]')
                    h+= ' <input type="search" style="width:200px;" value="' + 
                        query[0].params[0][i].replace(/\"/,'&quot;') + '"></div>';
            }

            for (var j = 1, filter; filter = query[j]; j++)
                h+= this.filterBox(filter.type, filter.params);

            if (query.length < 2) h+= this.filterBox();

            h+= '<input type="submit" value="Find Users">';

            return h;
        };

        var filterBox = function(type, params) { 
            var h = '';
            
            type = type || 'followed-by';
            params = params || {};
            
            var options = {
                'followed-by':{'label':'Followed By'},
                'follows':{'label':'Follows'},
                'follows-back':{'label':'Follows Back %'},
                'favs':{'label':'Favorites'}
            };
            
            h+= '<div class="unbound">';
            h+= '<input type="button" value="-">';
            h+= '<input type="button" value="+">';
            h+= '<select>';
            
            for (i in options) {
                var selected = (i == type) ? "selected" : '';
                
                h+= '<option ' + selected + ' value="' + i + '">' + 
                    options[i].label + '</option>';
            }
            
            h+= '</select>';
            
            var gtlt = params['select.gtlt'];
            var ltSelected = (gtlt && gtlt == 'lt') ? 'selected' : '';
            var gtSelected = (gtlt && gtlt == 'gt') ? 'selected' : '';
            
            h+= '<select><option '+gtSelected+' value="gt">more than</option>' + 
            '<option ' + ltSelected + ' value="lt">less than</option></select>';
            
            var val = (params && params['input[type=text]']) ? 
            params['input[type=text]'] : '';
            
            h+= '<input type="text" placeholder="#" value="' + val + '">';
            h+= '</div>';
            
            return h;
        }; // end filterbox

        $('#filters').html($.proxy(filtersHtml,{'filterBox':filterBox})(parseQuery(q)));

        var addFilter = function(e) { 
            $(e.target).closest('div').after(this.filterBox());

        $('#filters > div > input[value="-"]').click(this.removeFilter);

            $('#filters > div.unbound > input[value="+"]').click(
                $.proxy(this.addFilter, this));
            
            $('#filters .unbound').removeClass('unbound');
        };

        var removeFilter = function() { 
            $(this).closest('div').remove(); 
            
            if ($('#filters > div').length < 2)
                $('#filters').removeClass('active').slideUp(200);
        };

        $('#filters > div > input[value="-"]').click(removeFilter);
        $('#filters > div > input[value="+"]').click($.proxy(addFilter, 
           {'filterBox':filterBox, 'addFilter':addFilter, 'removeFilter':removeFilter}));
        $('#filters .unbound').removeClass('unbound');

        $('#filters').submit(function() { 
            var query = {'str' : []};

            // go through all the inputs and construct the query string
            $(this).children('div').each($.proxy(function(index, e) { 
                var selects = $(e).find('select');
                var text = $(e).find('input[type=text], input[type=search]');

                if (selects.first().val() == 'search')
                    return this.str.push(text.val());

                if (selects.first().val() == 'near')
                    return this.str.push('near:'+text.val());

                if (text.length && text.val()) {
                    for (var i = 0, sel, t = ''; sel = $(e).find('select')[i]; i++)
                        t+= $(sel).val() + ':' + ((index == 0) ? '@' : '');
                      
                    return this.str.push(t + text.val());
                }
            }, query));

            Listy.go(query.str.join(' '));

             return false; 
        });

        $(this).addClass('active').slideDown(200);
    });

    $('#accounts').bind('update', function() { 
        if (![].get('cursor')) return;

        if (![].get('cursor').accounts)
            return $(this).slideUp(300, function() { 
                    $('#accounts').html(''); $('#main').fadeIn(200); } );

        var accounts = [].get('accounts');

        if (pageTracker)
            pageTracker._trackPageview('/accounts/' + [].get('user').name);

        var h = '';

        h+= '<h2>My Accounts</h2>';

        var numAccounts = 0, totalFollowers = 0;

        for (token in accounts) {
            var account = accounts[token].name;

            u = [].get('details:user:@'+account);

            if (u) totalFollowers += u.followers_count;
            else
                u = {'screen_name':account,
                     'name':account,
                     'followers_count':'0',
                     'friends_count':'0'};
            
            numAccounts++;

            u.token = token;

            var selected = (u.screen_name == [].get('user').name) ? 'selected' : '';

            h+= '<div class="account ' + selected + '" data-account="' + 
                token + '"><div>'+u.name+'</div>'     + 
                '<div>@' +u.screen_name+'</div></div>';

            var countBox = $.proxy(function(rel, name, val, classes) {
                var u = this.user;

                var classes = (classes) ? classes : [];

                classes.push('count');

                return '<div rel="' + rel + '" class="' + classes.join(' ') + '" ' +
                'data-account="' + u.token + '"><div>' + val + '</div>' + 
                name + '</div>';
            }, {'user':u});

            if (!u.followers_count) u.followers_count = 0;
            if (!u.friends_count) u.friends_count = 0;

            var numFollowers = (u.followers_count < 10000) ? u.followers_count :
                Math.round(u.followers_count / 1000) + 'k';
            var numFollowed = (u.friends_count < 10000) ? u.friends_count :
                Math.round(u.friends_count / 1000) + 'k';

            h+= countBox('followed-by:@'+u.screen_name, 'Following', numFollowed,
                ['following']);
            h+= countBox('follows:@'+u.screen_name, 'Followers', numFollowers,
                ['followers']);

            h+= '<div data-account="' +token+ '" class="stats">Details</div>';

            h+= countBox('mentions:@'+u.screen_name, 'Mentions', '', 
                         ['mentions', 'details']);
            h+= countBox('mentions:@'+u.screen_name, 'Since Last Mention', '',
                         ['lastMention', 'time', 'details']);
            h+= countBox('user:@'+u.screen_name+'|'+u.screen_name,'Since Last Tweet',
                         '', ['lastTweet', 'time', 'details']);
            h+= countBox('user:@'+u.screen_name+'|'+u.screen_name,'Post/Day',
                         '', ['tweetsPerDay','details']);

            h+= '<div class="breaker"></div>';
        }

        h+= '<div class="new account">Connect Another Account</div>';
        h+= '<div class="breaker"></div>';

        $(this).html(h);

        if (numAccounts > 1)
            $('#accounts > h2').append(' <span>('+totalFollowers+ 
                                       ' total followers over ' + numAccounts + 
                                       ' accounts)</span>');

        $('#accounts > div.new.account').click(function() { 
            var token = [].get('token');

            if (!token) return;

            var oauth = window.open('https://twitter.com/oauth/authorize' + 
                                    '?oauth_token=' + token,
                                    'oauth', "width=680,height=500"); 

            [null].store('token');

            $('#accounts > div.new.account').bind('update', function(e) { 
                e.stopPropagation();

                setTimeout(function() { 
                    $('#accounts > div.new.account').trigger('update'); }, 200);

                $(this).hide();

                if (!$.cookie('oauth_token')) {
                    // sometimes twitter deletes my oauth token! thx guys...
                    // re-construct oauth token from localStorage
                    if (![].get('user')) location.reload();

                    var options = {'path':'/', 'expires':365};

                    $.cookie('oauth_token', [].get('user').tok, options);
                    $.cookie('user_id', [].get('user').id, options);
                    $.cookie('user_name', [].get('user').name, options);

                    alert('Twitter Error: Reloading to recover.');
                    
                    return location.reload();
                }

                if ($.cookie('oauth_token') == [].get('user').tok) return;

                // token cookie change indicates new account
                [{'id':$.cookie('user_id'), 'name':$.cookie('user_name'),
                  'tok':$.cookie('oauth_token')}].store('user');

                var account = {};

                account[$.cookie('oauth_token')] = {
                    'name'  : $.cookie('user_name'),
                    'id'    : $.cookie('user_id'),
                    'token' : $.cookie('oauth_token')};

                [null].store('searches');
                [null].store('lists');

                Listy.initLists();

                [account].merge('accounts', true);

                $.getJSON('service/oauth.php', function(r) { 
                    if (!r.tok) return;
                        
                    [r.tok].store('token');
                        
                    $('#accounts > div.new.account').show();
                });

                var first;

                for (i in [].get('accounts')) { first = [].get('accounts')[i].token; break; }
                
                $.post('service/multiple.php?oauth_token=' + first, 
                       {'accounts':JSON.stringify([].get('accounts'))});

                $(this).show();
            });

            $('#accounts > div.new.account').trigger('update'); 
        });

        $('#accounts > div.stats').click(function() { 
            var account = [].get('accounts')[$(this).attr('data-account')];

            $(this).fadeOut();

            var lastTweets = $.proxy(function(r) {
                if (!r || !r.length) return;

                var age = (new Date().getTime()-Date.parse(r[0].created_at)) / 1000;
                var period = 'sec';

                if (age > 90) { 
                    age = age / 60; period = 'min';

                    if (age > 90) { 
                        age = age / 60; period = 'hrs';
                    
                        if (age > 36) { 
                            age = age / 24; period = 'days'; 

                            if (age > 10) { age = age / 7; period = 'wks'; }
                        }
                    }
                }

                age = Math.round(age);
                var u = r[0].user;

                if (!u.followers_count) u.followers_count = 0;
                if (!u.friends_count) u.friends_count = 0;

                var followers = (u.followers_count < 10000) ? u.followers_count :
                    Math.round(u.followers_count / 1000) + 'k';
                var following = (u.friends_count < 10000) ? u.friends_count :
                    Math.round(u.friends_count / 1000) + 'k';

                [r[0].user].store('details:user:@'+r[0].user.screen_name, true);

                $('#accounts > div[data-account='+this.account.token+
                  '].followers.count').fadeIn().find('div').text(followers);

                $('#accounts > div[data-account='+this.account.token+
                  '].following.count').fadeIn().find('div').text(following);

                $('#accounts > div[data-account='+this.account.token+
                  '].lastTweet.count').fadeIn().find('div').text(age + ' '+period);

                var days = (new Date().getTime() - Date.parse(r.pop().created_at));
                var days = days / 1000 / 60 / 60 / 24;
                var tweetsPerDay = Math.round((r.length + 1) / days);

                $('#accounts > div[data-account='+this.account.token+
                  '].tweetsPerDay.count').fadeIn().find('div').text(tweetsPerDay);
            }, {'account':account});

            var args = {'api':'api.twitter.com/1/statuses/user_timeline.json',
                        'args':{'screen_name':account.name}};
            Twitter.request({'cbk':lastTweets, 'args':args});

            var mentions = $.proxy(function(r) { 
                $('#accounts > div[data-account='+this.account.token+
                  '].mentions.count').fadeIn().find('div').text(r.results.length);

                if (!r.results.length) return;

                var last = r.results[0];
                var age = (new Date().getTime()-Date.parse(last.created_at)) / 1000;
                var period = 'sec';

                if (age > 90) { 
                    age = age / 60; period = 'min';

                    if (age > 90) { 
                        age = age / 60; period = 'hrs';
                    
                        if (age > 36) { age = age / 24; period = 'days'; }
                    }
                }

                var lastMention = Math.round(age) + ' ' + period;

                $('#accounts > div[data-account='+this.account.token+
                  '].lastMention.count').fadeIn().find('div').text(lastMention);
            }, {'account':account});

            $.getJSON('http://search.twitter.com/search.json?q=@' + account.name + 
                      '&result_type=recent&callback=?', mentions);
        });

        $('#accounts > div.account, #accounts > div.count').click(function() { 
            if ($(this).is('.new')) return;

            var account = $(this).attr('data-account');
            var details = [].get('accounts')[account];

            var options = {'path':'/', 'expires':365};

            $.cookie('oauth_token', details.token, options);
            $.cookie('user_id', details.id, options);
            $.cookie('user_name', details.name, options);

            [{'name':details.name, 'tok':details.token,
              'id':details.id}].store('user');

            [null].store('searches');
            [null].store('lists');
            
            Listy.initLists();

            var rel = 'followed-by:@' + details.name;

            if ($(this).attr('rel')) rel = $(this).attr('rel');

            Listy.go(rel);
        }); // end select account

        [''].store('warning');

        var token = [].get('token');

        if (token) $('#accounts > div.new.account').show();
       
        return $(this).slideDown(300, function() { $('#main').fadeOut(200); });
    });

    $('#preview').bind('update', function() { 
        var user = [].get('cursor').preview;

        user = [].get('details:user:'+user);

        if (!user) return; //nothing to update

        $('#preview > img').attr('src', user.profile_image_url);
        if (user.favourites_count)
            $('#preview > span.favorites').text(user.favourites_count + ' favs');
        if (user.description)
            $('#preview > p.description').text(
                user.description.replace(/ &amp; /g,'&'));
        if (typeof(user.statuses_count) != 'undefined')
            $('#preview > span.statuses').text(user.statuses_count + ' tweets');
        $('#preview > span.verified').text((user.verified) ? 'verified' : '');
        $('#preview > span.followers').text(user.followers_count + ' followers');
        $('#preview > span.protected').text((user['protected']) ? 'protected' : '');
        $('#preview > span.location').text(user.location);

        var a = (!user.url) ? '' :
            '<a href="'+user.url+'" target="_blank">'+user.url.slice(7, 45)+'</a>';

        var t = ' <a target="_blank" href="http://twitter.com/'+user.screen_name+
            '">(@'+user.screen_name+')</a> ';

        $('#preview > h1').html(user.name + t + a);

        $('#preview > span.following').text(user.friends_count + ' following');

        var age = (new Date().getTime()-Date.parse(user.created_at)) / 1000/60/60/24;

        $('#preview > span.created_at').text(Math.round(age / 30) + ' months');

        var followBack = [].get('follow_back:user:@'+user.screen_name);

        if (followBack)
            $('#preview > span.follow_back').text(
                'Follows back ' + followBack + ' of followers'); 
    });
};

$(function() {
    if (/chrome/.test(navigator.userAgent.toLowerCase()) && $.browser.safari)
        $.browser.safari = false;

    $('#contacts > ul > div.welcome').bind('update', function() { 
        if (typeof(localStorage) == 'undefined') 
            $(this).append('<p>Please use a browser with localStorage support.</p>');
        if (typeof(JSON) == 'undefined')
            $(this).append('<p>Please use a browser with JSON support.</p>');
        if (!window.Worker)
            $(this).append('<p>Please use a browser with Web Worker support.</p>');
    });

    $('#contacts').bind('update', function() { //#contactsUpdate
        var contactHtml = function(u, i) {
            if (u && u.details)
                [u.details.profile_image_url].store('user_image:'+u.name);
            
            var cls = 'u'+u.name +' '+(([].get('seen:'+u.name)) ? '' : 'unseen');

            if ([].get('selections') && [].get('selections')[u.name]) 
                cls+= ' checked';

            if (i + 1 == [].get('cursor').selected) cls+= ' selected';

            var listLabelsHtml = function(name, u) {
                var u, h = '', lists = [].get('user:lists:'+name);

                if ([].get('is:blocked:'+name)) 
                    h+='<label class="blocked">Blocked</label>';

                if (lists && lists.length) {
                    for (var i = 0, l; l = lists[i]; i++) {
                        if (l != '@'+ [].get('user').name + '::friends::') {
                            var lister = l.substring(1,[].get('user').name.length+1);

                            if ([].get('list:'+l) && lister == [].get('user').name) {
                                h+= '<label class="list">'+[].get('list:'+l).name+
                                    '</label>';
                            }
                        }
                        else 
                            h+= '<label class="friend">Following</label>';
                    }
                }

                if (u) {
                    var tweetText = u.tweet_text;

                    if (u.user && u.user.tweet_text) tweetText = u.user.tweet_text;
                }

                if (tweetText)
                    h+= '<label class="hit">'+tweetText.replace(/<(.|\n)*?>/g,'')+
                        '</label> ';

                if (u && u.source) {                    
                    var source = (u.source[0] == '&') ? 
                        u.source.match(/&gt;(.*)&lt;/)[1] : u.source; 
                    
                    h+= '<label class="source">'+source+'</label> ';
                }
                
                if (c = [].get('details:user:@'+name)) {
                    if (c.verified) 
                        h+='<label class="verified">&#10003;</label> ';
                    if (c.location) 
                        h+='<label class="location">'+c.location+'</label> ';
                    if (c['protected']) 
                        h+='<label class="protected">Protected</label> ';
                    if (c['statuses_count'] == 0) 
                        c+= '<label class="notweets">No Tweets</label> ';
                    if (c.description) 
                        h+= '<label class="description">'+
                            c.description.slice(0,100)+'</label> ';
                } else { 
                    if (u && u.location)
                        h+= '<label class="location">'+u.location+'</label>';
                    if (u && u.description)
                        h+= '<label class="description">'+u.description+'</label>';
                }
                
                return h;
            };

            var name = u.name, details = [].get('details:user:@'+u.name);

            if (details != null) name = details.name;

            if (!name) return '';

            var selections = [].get('selections');

            if (!selections) selections = {};

            return '<li rel="'+u.name+'" class="'+cls+'"><img width="9" height="9" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFdJREFUeNpiYKArYAYRzs7O9kpKSguA+ML9+/dfoitiQmI7APEFoIb5QMyPSxEMJADxA6DCenyKQOADSCGMw4JFcgII79279yM2RQuAuAEo+RDdWIAAAwDaGxf05bF+cwAAAABJRU5ErkJggg%3D%3D"/><input type="checkbox" ' + 
            ((selections[u.name]) ? 'checked="checked"' : '') + '/> <a> '+
            '<span class="name">'+name+'</span> '+
            listLabelsHtml(u.name, u.details) +' </a></li>'; 
        }; // end contactHtml

        var h = '', users = [].get('current');

        if (!users) return;

        for (var i = 0, u; u = users[i]; i++)
            h+= contactHtml(u, i);
        
        $('#contacts > ul').html(h);

        for (var i = 0, u; u = users[i]; i++)
            $('#contacts').trigger('update_follow_back', ['::@'+u.name]);

        $('#main > div.selections').trigger('update');
        
        $('#contacts > ul > li > a > label.hit > a').click(function() { 
            $(this).closest('li > a').click(); return false; });
        
        $('#contacts > ul > li > input[type=checkbox]').click(function(e) { 
            if (e.shiftKey) {
                $(this).closest('li').prevAll('li').each(function(i, e) {
                    if ($(this).find('input').attr('checked')) return false;

                    $(this).find('input').attr('checked', true);
                });

                $('#contacts').trigger('update_selections');
            }
        });

        $('#contacts > ul > li > input[type=checkbox]').click(function() {
            var select = {};

            select[$(this).closest('li').attr('rel')] = new Date().getTime();

            [select].merge('selections');
            $('#contacts').trigger('update_selections').trigger('update_selections');;
        });

        $('#contacts > ul > li').click(function(e) { 
            if ($(e.target).is('li')) $(this).find('a').click(); });
        
        $('#contacts > ul > li > a').click(function() { 
            [window.pageYOffset].store('pageYOffset');

            $('#contacts > ul > li.selected').removeClass('selected');
            $(this).closest('li').addClass('selected');
            [{'selected':$(this).closest('li').index() + 1}].merge('cursor');
            $.historyLoad([].get('cursor').q +'|'+ 
                          $(this).closest('li').attr('rel'));
        });
    }); // end #contacts update

    $('#warning').bind('update', function() { 
        $(this).html([].get('warning'));
    });

    $('#warning').click(function() { $(this).html(''); });

    $('#main > div.selections').bind('update', function() { 
        if (![].get('user')) return $(this).html('');

        var current = [].get('current');
        var cursor = [].get('cursor');

        var h = 'Select: <a class="all">All,</a> <a class="none">None</a>';

        if ($('#contacts > ul > li.unseen').length != $('#contacts > ul > li').length)
            h+= ', <a class="seen">Seen,</a> <a class="unseen">Unseen</a>';

        if ($('#contacts li > a > label.friend').length)
            h+= ', <a class="following">Following</a>, ' + 
                '<a class="not_following">Not Following</a>';

        var meta = {
            '_links' : function(rels) { // private, creates the html anchors
                for (var i = 0, anchors = [], rel; rel = rels[i]; i++)
                    if ([].get('cursor').q.toLowerCase() != rel[0].toLowerCase())
                        anchors.push('<a rel="' + rel[0] + '">' + rel[1] + '</a>');

                return anchors.join(', ');
            },
            'list' : function(self, id) {
                return this._links([['latest:@'+id, 'Sort by last tweet'],
                                    ['talked-about:@'+id, 'Most mentioned'],
                                    ['subscribers:@'+id, 'Subscribers']]);
            },
            'subscribers' : function(self, id) { 
                return this._links([['list:@'+id, 'View list members']]);
            },
            'latest' : function(self, id) {
                return this._links([['list:@'+id, 'Sort by last added'],
                                    ['subscribers:@'+id, 'View subscribers']]);
            },
            'timeline' : function(self, name) { 
                return this._links([['followed-by:@'+name, 'Sort by last added'],
                                    ['follows:@'+name, 'Followers'],
                                    ['Favorited:@'+name, 'Favorited']]);
            },
            'mentioned-to' : function(self, name) {
                return this._links([['followed-by:@'+name, 'Following'],
                                    ['follows:@'+name, 'Followers']]);
            },
            'followed-by' : function(self, q) { 
                var name = q;

                if (q.split(' ').length > 1) {
                    name = q.split(' ')[0];

                    return this._links([['followed-by:@'+name+' follows:@'+name,
                                         'Mutual follows'],
                                        ['followed-by:@'+name + ' -follows:@'+name,
                                         'Doesn\'t follow me back'],
                                        ['follows:@'+name+' -followed-by:@'+name,
                                         'I don\'t follow back']]);
                }

                var rels = [['timeline:@'+name, 'Sort by last tweet'],
                            ['mentioned-to:@'+name, 'Most mentioned']];

                if (name !== [].get('user').name) rels = [];

                rels.push(['followed-by:@'+name+ ' follows:@'+name, 'Mutual follows']);

                return this._links(rels);
            },
            'follows': function(self, name) { 
                return this._links([['followed-by:@'+name, 'Following'],
                                    ['favorites:@'+name, 'Favorited']]);
            },
            'favorites': function(self, name) {
                return this._links([['followed-by:@'+name, 'Following'],
                                    ['follows:@'+name, 'Followers']]);
            }
        };

        var i, extras = [];

        for (i in meta) extras.push(i);

        for (var i = 0, s; s = extras[i]; i++)
            if (cursor.q.substring(0,s.length + 1) == s+':') 
                h+='<span class="related">Related: '+
                    meta[s](this,cursor.q.substring(s.length+2))+'</span>';
        
        if ($('body').hasClass('preview') || !current || current.length == 0) 
            return $(this).html('').hide();

        $(this).html(h).show();

        $(this).find('span.related > a').click(function() {
            Listy.go($(this).attr('rel')); });

        $(this).find('a.all').click(function() { 
            $('#contacts > ul > li > input').attr('checked', true); 
            $('#contacts').trigger('update_selections').
                trigger('update_selections');
        });
        
        $(this).find('a.none').click(function(e) { 
            $('#contacts > ul > li > input').attr('checked', false);
            $('#contacts').trigger('update_selections').
                trigger('update_selections'); 
        });

        $(this).find('a.following').click(function() { 
            $('#contacts > ul > li > input:checked').attr('checked', false);
            $('#contacts > ul > li > a > label.friend').each(function(i,e) {
                $(this).closest('li').find('input').attr('checked', true);
            });
            $('#contacts').trigger('update_selections').trigger('update_selections');
        });

        $(this).find('a.not_following').click(function() { 
            $('#contacts > ul > li > input').attr('checked', true);
            $('#contacts > ul > li > a > label.friend').each(function(i,e) {
                $(this).closest('li').find('input').attr('checked', false);
            });
            $('#contacts').trigger('update_selections').trigger('update_selections');
        });
        
        $(this).find('a.unseen').click(function() { 
            $('#contacts > ul > li > input:checked').attr('checked', false);
            $('#contacts > ul > li.unseen > input').attr('checked', true); 
            $('#contacts').trigger('update_selections').
                trigger('update_selections'); 
        });
        
        $(this).find('a.seen').click(function() { 
            $('#contacts > ul > li > input:checked').attr('checked', false);
            $('#contacts > ul > li:not(.unseen) > input').attr('checked', true); 
            $('#contacts').trigger('update_selections').
                trigger('update_selections'); 
        });
    }); // end update div.selections

    $('#lists').bind('update', function() {
        var h = '', user = [].get('user');

        if (!user) return;

        h+= '<a rel=":user">@'+user.name+'</a>';

        h+= '<a rel=":lists">Lists</a> <a rel=":searches">Searches</a> ';

        h+= '<a class="api_units"></a>';

        h+= '<a rel=":accounts">Accounts</a>';

        h+= '<a class="feedback">Give Feedback</a>';
        
        $('#lists').html(h);

        $('#lists a.feedback').click(function() { 
            $("#feedback").remove();

            var h = '<div id="feedback"><form><label>I want to hear from you! Ideas, bugs, questions, anything.</label><div><textarea></textarea></div><input type="submit" value="Send Feedback"/> or <a>Cancel</a></form>';

            $('#warning').after(h);

            $('#feedback > form > a').click(function() { $('#feedback').remove(); });

            $("#feedback > form").submit(function() { 
                var feedback = $(this).find('textarea').val();

                var user = [].get('user');

                user = (!user) ? '-' : user.name;

                var args = {'feedback':user +' '+feedback};

                Twitter.request({'args':args, 'type':'POST'});

                $('#feedback').remove();
            
                ['Thanks for your feedback!'].store('warning');

                return false;
            });
        });

        $('#lists a.api_units').click(function() { 
            var limit = [].get('api_units');

            var date = new Date(limit.reset * 1000);
            var tod = 'AM', hours = date.getHours();
            var minutes = date.getMinutes();

            if (minutes < 10) minutes = '0' + minutes;
            
            if (hours > 12) { hours -= 12; tod = 'PM'; }
            if (hours == 0) hours = 12;
            
            [limit.calls + ' API units left until reset at ' +
             hours + ':' + minutes + ' ' + tod].store('warning');
        });
       
        $('#lists a').click(function() { // make nav at top work.
            var h = '', rel = $(this).attr('rel');

            if (!rel) return;

            $('#lists>a.selected').removeClass('selected');
            
            if (rel == ':accounts') {
                var cursor = [].get('cursor');

                $('#launcher').remove();

                if (cursor.accounts) {
                    $(this).removeClass('selected');
                    return [{'accounts':false}].merge('cursor');
                }
                
                $(this).addClass('selected');

                $.getJSON('service/oauth.php', function(r) { 
                    if (!r.tok) return;
                        
                    [r.tok].store('token');

                    $('#accounts > div.new.account').show();

                    var users = [], accounts = [].get('accounts');

                    for (token in accounts) users.push(accounts[token].name);
                    
                    Twitter.request({
                        'cbk' : function(r) { 
                            if (!r || !r.length) return;

                            for (var i = 0, mini = Listy.miniUser, u; u = r[i]; i++)
                                [mini(u)].store('details:user:@'+u.screen_name, true);
                                
                            $('#accounts').trigger('update');
                        },
                        'args' : {
                            'api' : 'api.twitter.com/1/users/lookup.json',
                            'args' : { 'screen_name' : users.join(',') }
                        }
                    });
                });

                return [{'accounts':(![].get('cursor').accounts)}].merge('cursor');
            }
            else [{'accounts':false}].merge('cursor');

            if ($('#launcher').attr('rel') != rel) $('#launcher').remove(); 

            if (rel[0] == ':') {    
                $(this).addClass('selected');

                if (rel == ':lists') {
                    if ([].get('lists'))
                        for (var h = '', i = 0, l; l = [].get('lists')[i]; i++) 
                            h+= '<a rel="list:'+l.full_name+'">'+l.name+'</a>';
                }
                else if (rel == ':searches') {
                    if ([].get('searches')) {
                        for (var h = '',safeQ,i = 0,s; s = [].get('searches')[i]; i++) {
                            if (s.deleted) continue;

                            safeQ = s.query.replace(new RegExp('"','gim'), '&quot;');
                            
                            h+= '<a title="'+safeQ+'" rel="'+safeQ+'">'+s
                                .name.replace(/:/g,': ')+'</a>';
                        }
                    }
                }
                else if (rel == ':user') {
                    var name = [].get('user').name;

                    h = '<a rel="followed-by:@'+name+'">Following <div></div></a>';
                    h+= '<a rel="follows:@'+name+'">Followers <div></div></a>';
                    h+= '<a rel="mentions:@'+name+'">mentions</a>';
                    h+= '<a rel="favorites:@'+name+'">favorited</a>';
                    h+= '<a rel="retweets-to:@'+name+'">retweets</a>';
                    h+= '<a rel="dms-to:@'+name+'">dms</a>';
                    h+= '<a rel="listing:@'+name+'">listings</a>';
                    h+= '<a class="logout">Logout</a>';
                }

                $('#launcher').remove();
                $('#header').after('<div rel="'+rel+'" id="launcher">'+h+'</div>');

                var details = [].get('details:user:@'+name);

                var menu = $('#launcher');

                if (details) {
                    menu.find('a[rel^=follows] div').text(details.followers_count);
                    menu.find('a[rel^=followed-by] div').text(details.friends_count);
                }

                $('#launcher > a.logout').click(function() { 
                    var account = null, accounts = [].get('accounts');
                    
                    var first;

                    for (i in accounts) { first = accounts[i].token; break; }

                    delete accounts[[].get('user').tok];

                    [accounts].store('accounts', true);

                    $.post('service/multiple.php?oauth_token=' + first, 
                           {'accounts':JSON.stringify(accounts)});

                    var details, options = {'path':'/'};

                    $.cookie('oauth_token', null, options);
                    $.cookie('user_id', null, options);
                    $.cookie('user_name', null, options);

                    for (a in [].get('accounts')) { // find another account to use
                        if (a) { 
                            account = a;
                            break;
                        }
                    }

                    if (!account) return location.reload();

                    details = [].get('accounts')[account];
                    options = {'path':'/', 'expires':365};

                    $.cookie('oauth_token', details.token, options);
                    $.cookie('user_id', details.id, options);
                    $.cookie('user_name', details.name, options);

                    [{'name':details.name, 'tok':details.token,
                      'id':details.id}].store('user');

                    [null].store('searches');
                    [null].store('lists');
            
                    Listy.initLists();

                    Listy.go('followed-by:@' + details.name);

                    $('#launcher').html('');
                });
                
                $('#launcher > a').click(function() { 
                    if (!$(this).attr('rel')) return false;

                    $.historyLoad($(this).attr('rel'));
                    $('#launcher').remove();
                    $('#lists > a.selected').removeClass('selected');
                });

                return;
            }

            $.historyLoad($(this).attr('rel'));
        }); // end list > a click
    }); // end update #lists

    $('#contacts').bind('update_follow_back', function(e, key) { 
        var u = key.split(':')[2].substring(1),
            row = $('#contacts>ul>li.u'+u),
            details = [].get('details:user:@'+u);

        if (!row.length) return;

        var friends_count = null, follows_count = null,
            follows_you = '', user = [].get('user'),
            ratio = [].get('ratio:user:@'+u),
            follow_back = [].get('follow_back:user:@'+u);

        if (user && ([].get(user.id+':followed-by:@'+u) || 
           (details && [].get(user.id+':follower:'+details.id))))
            follows_you = '<span class="follows_you">Follows me</span>';

        if (row.find('span.follows_you').length) follows_you = '';

        if (details) {
            friends_count = details.friends_count;
            follows_count = details.followers_count;
        } else if (ratio) {
            friends_count = ratio.friends;
            follows_count = ratio.followers;
            
            if (friends_count > 4800) friends_count += '+';
            if (follows_count > 4800) follows_count += '+';
        }
        
        follow_back = (!follow_back) ? '' : 
            '<span class="follow_back">Follows '+follow_back+
            ((friends_count === null) ? '' : 
             ' ('+friends_count + '/' + follows_count+')') + 
            '</span>';

        if (row.find('span.follow_back').length) return;
        
        row.find('span.name').after([follows_you, follow_back].join(''));
    });

    $('#contacts').bind('update_selections', function() { 
        $(this).find('ul li input[type=checkbox]').each(function() {
            var li = $(this).closest('li'), name = li.attr('rel');
            var selections = [].get('selections');

            if (!selections) selections = {};

            var s = {};

            if ($(this).is(':checked')) {
                if (selections[name]) return li.addClass('checked');

                s[name] = new Date().getTime();
            } else {
                if (!selections[name]) return li.removeClass('checked');

                s[name] = null;
            }

            return [s].merge('selections');
        });

        var name, updated = {}, current = [].get('selections');

        for (name in current)
            if (current[name]) updated[name] = current[name];
            
        [updated].store('selections');

        var userLists, lists = {};

        for (name in updated) { 
            var userLists = [].get('user:lists:'+name);

            if (userLists)
                for (var i = 0, l; l = userLists[i]; i++)
                    lists[l] = (!lists[l]) ? 1 : lists[l] + 1;
        }

        [lists].store('selections:lists');
    });

    $('body').click(function() { 
        $('#main .actions span.lists, #main .actions span.more').
            removeClass('active');
    });

    Listy.init();
});