var GSessionListeners = [];
var GLoginListeners = [];
var GFilterListeners = [];
var GSession;
var GUser;

function filter(fun, list) {
    var result = [];
    for (i = 0; i < list.length; i++) {
        if (fun(list[i])) { result.push(list[i]); }
    }
    return result;
};

function map(fun, list) {
    var result = [list.length];
    for (i = list.length-1; i >= 0; i--) {
        result[i] = fun(list[i]);
    }
    return result;
};

function fire_listeners(listeners) {
    for (i in listeners) { listeners[i](); }
};

function clear_cookies() {
    var date = new Date();
    date.setTime(date.getTime()+(-1*24*60*60*1000));
    var expires = "; expires="+date.toGMTString();
    document.cookie = 'beer_cookie='+expires+'; path=/';
    document.cookie = 'beer_handler='+expires+'; path=/';
    document.cookie = 'beer_name='+expires+'; path=/';
};

function get_session(Name, Callback) {
    GSessionListeners[Name] = Callback;
    if (GSession !== undefined) { Callback(); }
};

function get_user(Name, Callback) {
    if (GUser) { Callback(); }
    else { GLoginListeners[Name] = Callback; }
};

function add_edit_button(Uri, Label, Fun, Class) {
    $('<a href="'+Uri+'" class="'+(Class || "edit")+'"><span>'+Label+'</span></a>')
        .click(Fun).prependTo('#help');
};

function set_hash_val(Key, Val) {
    var old = window.location.hash;
    if (old.length > 0) {
        var parts = old.substring(1).split(',');
        for (var i = parts.length-1; i >= 0; i--) {
            if (parts[i].split('=')[0] == Key) {
                parts[i] = Key+'='+Val;
                break;
            }
        }
        if (i == -1) { parts.push(Key+'='+Val); }

        window.location.replace('#'+parts.join(','));
    } else {
        window.location.replace('#'+Key+'='+Val);
    }
};

function get_hash_val(Key) {
    if (window.location.hash) {
        var parts = window.location.hash.substring(1).split(',');
        for (var i = parts.length-1; i >= 0; i--) {
            var split = parts[i].split('=');
            if (split[0] == Key) {
                return split[1];
            }
        }
    }
};

function do_on_enter(Fun) {
    return function(ev) {
        if (ev.keyCode == 10 || ev.keyCode == 13) { return Fun(); }
    }
};

function make_date(D) {
    var date = new Date();
    date.setUTCFullYear(D[0], D[1]-1, D[2]);
    if (D.length > 3) {
        date.setUTCHours(D[3], D[4], D[5]);
    }
    return date;
};

function format_date(date) {
    return [date.getFullYear(),(date.getMonth()+1),(date.getDate())].join('.');
};

function format_datetime(date) {
    var min = date.getMinutes();
    if (min < 10) { min = '0'+min; }
    return format_date(date)+', '+date.getHours()+":"+min;
};

function find_vote(beerId) {
    for (var i = GSession.votes.length-1; i >= 0; i--) {
        if (beerId == GSession.votes[i].id) {
            return GSession.votes[i].vote;
        }
    }
};

function find_score(beerId) {
    for (var i = GSession.scores.length-1; i >= 0; i--) {
        if (beerId == GSession.scores[i].id) {
            return GSession.scores[i].score;
        }
    }
};

function vote(o, Mark) {
    var link = $(o.target);

    var val;
    if (link.hasClass('picked')) { val='unvote' }
    else if (link.hasClass('like')) { val='like'; }
    else if (link.hasClass('shrug')) { val='shrug'; }
    else if (link.hasClass('dislike')) { val='dislike'; }
    else { return; }

    var id = link.attr('href').substr(6);
    $.ajax({url:'/session',
            type:'PUT',
            data:{cmd:'vote',
                  beer:id,
                  vote:val},
            success:function(data) {
                GSession=data;
                Mark(link, val);
            },
            error:function() { alert("Your session has expired"); },
            dataType:'json'
           });
};

function vote_fun(Mark) {
    return function(o) { vote(o, Mark); return false; };
};

function fetch(Type, Key, Callback) {
    $.getJSON(['/api',Type,Key].join('/'), [], Callback);
}

function SearchBox(Parent) {
    var box;

    var doSearch = function() {
        var search = box.find('#search_terms').val();
        window.location.href = '/search?q='+encodeURIComponent(search);
        return false;
    };

    var loadBox = function(data) {
        box = $(data).appendTo(Parent);

        if (search_terms) {
            box.find('#search_terms').val(search_terms);
        }

        box.find('#search_terms').keyup(do_on_enter(doSearch));

        box.find('#search_button').click(doSearch);
    };

    $.get('/'+stv+'/html/searchbox.html', [], loadBox);
};

function FilterBox(Parent) {
    var box;

    var setFilter = function(type, val) {
        var row = $('tr.'+type, box);
        if (val) {
            $('a.include', row).addClass('picked');
            $('a.exclude', row).removeClass('picked');
        }
        else {
            $('a.include', row).removeClass('picked');
            $('a.exclude', row).addClass('picked');
        }
    };

    var setSession = function() {
        if (GSession === null) { return; }

        setFilter('likes', GSession.filter.likes);
        setFilter('shrugs', GSession.filter.shrugs);
        setFilter('dislikes', GSession.filter.dislikes);
        setFilter('unvoted', GSession.filter.unvoted);
    };

    var modFilter = function(o) {
        o.stopPropagation();
        var link = $(o.target);
        if (link.get(0).tagName != 'A') { link = link.parent('A'); }
        if (link.hasClass('picked')) { return; }

        var val = link.hasClass('include');
        var type = link.parent().parent().attr('class');

        if (!GSession) {
            GSession = {filter:{}};
        }
        GSession.filter[type] = val;
        setFilter(type, val);

        $.ajax({url:'/session',
                type:'PUT',
                data:{cmd:'mod_filter',
                      type:type,
                      val:val},
                success:function(data) { GSession = data; },
                dataType:'json'
               });

        for (var i = 0; i < GFilterListeners.length; i++) {
            GFilterListeners[i]();
        }

        return false;
    };

    var loadBox = function(data) {
        box = $(data).appendTo(Parent);

        $('td.choice a', box).click(modFilter);

        get_session('filterbox', setSession);
    };

    $.get('/'+stv+'/html/filterbox.html', [], loadBox);
};

function TagBox(Parent) {
    var box;

    var loadTag = function(data) {
        $('a[href$=/tag/'+data.id+']', box).text(data.text);
    };

    var fillBox = function(data) {
        var content = '';
        for (var i = data.tags.length-1; i >= 0; i--) {
            content = '<a href="/tag/'+data.tags[i]+'">&nbsp;</a> '+content;
        }
        $('.boxbody .taglist', box).html(content);

        for (var i = data.tags.length-1; i >= 0; i--) {
            fetch('tag', data.tags[i], loadTag);
        }
    };

    var loadBox = function(data) {
        box = $(data).appendTo(Parent);

        $.getJSON('/api/tag?recent', [], fillBox);
    };

    $.get('/'+stv+'/html/tagbox.html', [], loadBox);
};

function LocalBox(Parent) {
    var box;

    var navToMap = function() {
        var address = $.trim($('#address', box).val());

        if (address) {
            window.location = '/local?address='+encodeURIComponent(address);
        } else {
            window.location = '/local';
        }

        return false;
    };

    var loadBox = function(data) {
        box = $(data).appendTo(Parent);

        if (address) {
            $('#address', box).val(address);
        }

        $('#address', box).keyup(do_on_enter(navToMap));

        $('a#map_button', box).click(navToMap);
    };

    $.get('/'+stv+'/html/localbox.html', [], loadBox);
};

function LinksBox(Parent) {
    $.get('/'+stv+'/html/linksbox.html', [],
          function(data) { $(data).appendTo(Parent); });
};

function LoginBox() {
    var box;

    var completeLogin = function() {
        box.remove();
        $.getJSON('/api/user/'+GSession.id, [],
                  function(data) {
                      GUser = data;
                      WelcomeBox();
                      fire_listeners(GLoginListeners);
                  });
    };

    var loginSuccess = function(data) {
        GSession = data;
        fire_listeners(GSessionListeners);
        completeLogin();
    };

    var loginError = function(data) {
        alert("Login failed");
        $('#password', box).val('').focus();
    };

    var login = function() {
        var username = $('#username', box).val();
        if (username.length == 0) { $('#username', box).focus(); return false; }
        var password = $('#password', box).val();
        if (password.length == 0) { $('#password', box).focus(); return false; }
        var remember = $('#remember', box).attr('checked') ? 'true' : 'false';

        $.ajax({url:'/session',
                type:'PUT',
                data:{cmd:'login',
                      username:username,
                      password:password,
                      remember:remember},
                success: loginSuccess,
                error: loginError,
                dataType:'json'
               });

        return false;
    };

    var setStatus = function(Subbox, String) {
        $(Subbox+' .status', box).text(String);
    };

    var signupResult = function(data) {
        if (data.status == "success") {
            $.getJSON('/session', [],
                      function(data) {
                          GSession = data;
                          fire_listeners(GSessionListeners);
                          completeLogin();
                      });
        }
        else if (data.reason == "inuse") {
            setStatus('#signupbox', 'That username has already been taken.  Please choose another.');
            $('#nusername', box).focus();
        }
        else {
            setStatus('#signupbox', 'An error occurred.  Please try signing up again later.');
        }
    };

    var forgotResult = function(data) {
        if (data.status == "success") {
            $('#forgotbox', box).hide();
            $('#resetsuccess', box).show();

            $('#resetsuccess a.ok', box).click(
                function() {
                    $('#resetsuccess', box).fadeOut('fast', function() { $('#loginbox', box).fadeIn('fast'); });
                });
        }
        else {
            setStatus('#forgotbox', 'That username and email cannot be found.  Please correct them and try again.');
            $('#forgotbox input.femail', box).focus();
        }
    };

    var signup = function() {
        var username = $('#nusername', box).val();
        if (username.length == 0) {
            setStatus('#signupbox', 'Please choose a username');
            $('#nusername', box).focus();
            return false;
        }
        var password = $('#npassword', box).val();
        if (password.length == 0) {
            setStatus('#signupbox', 'Please choose a password');
            $('#npassword', box).focus();
            return false;
        }
        var cpassword = $('#cpassword', box).val();
        if (cpassword.length == 0) {
            setStatus('#signupbox', 'Please confirm your chosen password');
            $('#cpassword', box).focus();
            return false;
        }

        if (cpassword != password) {
            setStatus('#signupbox', 'The passwords do not match.  Please type them again.');
            $('#cpassword', box).focus();
        }
        else if (!$('#verify21').attr('checked')) {
            setStatus('#signupbox', 'Please check the box to verify your age.');
        }
        else {
            setStatus('#signupbox', '');
            $.post('/signup',
                   {username:username,
                    password:password},
                   signupResult,
                   'json');
        }

        return false;
    };

    var forgot = function() {
        var username = $('#fusername', box).val();
        if (username.length == 0) { 
            setStatus('#forgotbox', 'Please enter your username');
            $('#fusername', box).focus();
            return false;
        }
        var email = $('#femail', box).val();
        if (email.length == 0) {
            setSatus('#forgotbox', 'Please enter your email');
            $('#femail', box).focus();
            return false;
        }

        setStatus('#forgotbox', '');
        $.post('/forgot',
               {username:username,
                email:email},
               forgotResult,
               'json');
    };

    var swap = function(h, s) {
        $(h, box).fadeOut('fast', function() { $(s, box).fadeIn('fast'); });
    };

    var showSignup = function() {
        swap('#loginbox', '#signupbox');
        return false;
    };

    var hideSignup = function() {
        swap('#signupbox', '#loginbox');
        return false;
    };

    var showForgot = function() {
        swap('#loginbox', '#forgotbox');
        return false;
    };

    var hideForgot = function() {
        swap('#forgotbox', '#loginbox');
        return false;
    };

    var loadBox = function(data) {
        box = $(data).appendTo('#header');

        $('#loginbox .submit', box).click(login);
        $('#loginbox input', box).keyup(do_on_enter(login));
        $('a.signup', box).click(showSignup);
        $('a.forgot', box).click(showForgot);
        $('#signupbox .cancel', box).click(hideSignup);
        $('#signupbox .submit', box).click(signup);
        $('#signupbox input', box).keyup(do_on_enter(signup));
        $('#forgotbox .cancel', box).click(hideForgot);
        $('#forgotbox .submit', box).click(forgot);
        $('#forgotbox input', box).keyup(do_on_enter(forgot));
        $('input', box).focus(function(o) { $(o.target).select(); });
    };

    $.get('/'+stv+'/html/loginbox.html', [], loadBox);
};

function WelcomeBox() {
    var box;

    var logout = function(o) {
        $.ajax({url:'/session',
                type:'DELETE'});
        clear_cookies();
        window.location = '/';
        return false;
    };

    var loadBox = function(data) {
        box = $(data).appendTo('#header');

        $('.logout', box).click(logout);

        $('a.brusername', box).attr('href', '/user/'+GUser.id).text(GUser.name);
    };

    $.get('/'+stv+'/html/welcomebox.html', [], loadBox);
};

function CommentBox(Parent, Options) {
    var box;
    if(!Options) { Options = {}; }

    var updatePreview = function(data) {
        $('tr.preview', box).show();
        $('div.preview_text', box).html(data);
    };

    var preview = function() {
        var text = $('textarea', box).val();

        $.post('/preview', {text:text}, updatePreview, 'html');

        return false;
    };

    var submit = function(o) {
        var uri = $(o.target).attr('href');
        var text = $('textarea', box).val();

        if (Options.method == 'PUT') {
            $.ajax({'url':uri,
                    'type':'PUT',
                    'contentType':'text/plain',
                    'data':text,
                    'dataType':'json',
                    'success':function(data) {
                        box.remove();
                        if (Options.success) { Options.success(data); }
                    },
                    'error':function(XHR, err) {
                        alert("Seems to have failed: "+err);
                    }
                   });
        }
        else {
            $.post(uri, {cmd:'comment',text:text},
                   function(data) {
                       window.location = '/comment/'+data.id;
                   },
                   'json');
        }

        return false;
    };

    var cancel = function() {
        box.remove();
        if (Options.cancel) {
            Options.cancel();
        }
        return false;
    };
        
    var loadBox = function(data) {
        box = $(data).appendTo(Parent);

        if (Options.text) {
            $('textarea', box).val(Options.text);
        }

        $('textarea', box).focus();

        $('a.cancel', box).click(cancel);

        $('a.preview', box).click(preview);

        $('a.submit', box).click(submit).attr('href', Options.target || data_uri);
    };

    $.get("/"+stv+"/html/commentbox.html", [], loadBox);
};

function BeerTable(Parent, Sorts) {
    var MAX = 15;
    var cpage = (parseInt(get_hash_val('bp')) || 0)-1;
    var csort = -1;
    var cdir;
    var beers = [];
    var bt;
    var likelihoods;
    var sortpager;
    var beertemp;

    var likelihoodLevel = function(n) {
        if      (n > 5) { return 'l5'; }
        else if (n > 4) { return 'l4'; }
        else if (n > 3) { return 'l3'; }
        else if (n > 2) { return 'l2'; }
        else if (n > 1) { return 'l1'; }
                          return 'l0';
    };

    var setLikelihood = function(line, id) {
        if (likelihoods[id]) {
            line.find('div.likelihoodmeter').addClass(likelihoodLevel(likelihoods[id]));
        } else {
            $.getJSON(data_uri+'/likelihood/'+id, [],
                      function(data) {
                          likelihoods[id] = data.likelihood;
                          setLikelihood(line, id);
                      });
        }
    };

    var clearLikelihood = function(elt) {
        elt.find('div.likelihoodmeter')
        .removeClass('l5')
        .removeClass('l4')
        .removeClass('l3')
        .removeClass('l2')
        .removeClass('l1')
        .removeClass('l0');
    };

    var submitLikelihood = function(o) {
        var t = $(o.target);
        if (t.eq(0).tagName != 'A') { t = t.parent(); }
        var confirm = t.hasClass('confirm');

        var beerid = t.attr('href').substring(t.attr('href').lastIndexOf('/')+1);
        var data =
            {cmd: t.hasClass('confirm') ? 'confirm' : 'deny',
             beer: beerid};

        $.post(data_uri+'/likelihood', data,
               function(data) {
                   likelihoods = data.likelihoods;
                   clearLikelihood(t.parent());
                   setLikelihood(t.parent(), beerid);
               },
               'json');
        return false;
    };

    var markVote = function(link, val) {
        link.parent().find('a').removeClass('picked');

        if (val != 'unvote') { link.addClass('picked'); }
    };

    var loadBeer = function(line, key) {
        var beer = beers[key];
        if (!beer) {
            fetch('beer', key,
                  function(data) {
                      beers[data.id] = data;
                      loadBeer(line, data.id);
                  });
            return;
        }

        line.find('.beername').attr('href', '/beer/'+beer.id).text(beer.name);
        line.find('.commentcount').text(beer.comments.length);

        line.find('.vote a').attr('href', '/beer/'+beer.id).click(vote_fun(markVote));

        fetch('brewery', beer.brewery,
              function(data) {
                  line.find('.breweryname').attr('href', '/brewery/'+data.id).text(data.name);
              });
        fetch('user', beer.user,
              function(data) {
                  line.find('.brusername').attr('href', '/user/'+data.id).text(data.name);
              });
        line.find('.submitdate').text(format_date(make_date(beer.date.datetime)));
        line.show();

        var taglist = line.find('.tags');
        for (var i = 0; i < beer.tags.length; i++) {
            fetch('tag', beer.tags[i],
                  function(data) {
                      taglist.append('<a href="/tag/'+data.id+'">'+data.text+'</a>');
                  });
        }

        if (likelihoods != null) {
            line.find('td.likelihood').addClass('active');
            line.find('td.likelihood a')
            .attr('href', data_uri+'/likelihood/'+key)
            .click(submitLikelihood);

            setLikelihood(line, key);
        }
    };

    var setSession = function() {
        if (GSession === null) { return; }

        var lines = $('tr.beer', bt);
        for (var i = 0; i < lines.length; i++) {
            var beerId = $('a.beername', lines[i]).attr('href');
            beerId = beerId.substr(beerId.lastIndexOf('/')+1);

            var vote = find_vote(beerId);
            if (vote) {
                $('.vote a.'+vote, lines[i]).addClass('picked');
            }

            var score = find_score(beerId);
            if (score != undefined) {
                $('td.score', lines[i]).text((5*score).toFixed(1));
            }
        }
    };

    var setUser = function () {
        $('td.likelihood', bt).addClass('clickable');
    };

    var filterFun = function(beer) {
        if (GSession) {
            if (GSession.filter.likes &&
                GSession.filter.shrugs &&
                GSession.filter.dislikes &&
                GSession.filter.unvoted) { return true; }

            var vote = find_vote(beer);
            if (!GSession.filter.likes && vote == "like") { return false; }
            if (!GSession.filter.shrugs && vote == "shrug") { return false; }
            if (!GSession.filter.dislikes && vote == "dislike") { return false; }
            if (!GSession.filter.unvoted && !vote) { return false; }
        }
        return true;
    };

    var loadPage = function(sortidx, dir, page) {
        if (!Sorts[sortidx].filteredIds) {
            Sorts[sortidx].filteredIds = filter(filterFun, Sorts[sortidx].ids);
        }

        var pages = Math.ceil(Sorts[sortidx].filteredIds.length/MAX);

        if (page < 0) { page = 0; }
        else if (page >= pages) { page = pages-1; }

        if (cpage >= 0 && cpage != page) { set_hash_val('bp', page+1); }

        csort = sortidx;
        cdir = dir;
        cpage = page;

        sortpager.setPageDisplay(cpage, pages);


        var hidden = Sorts[csort].ids.length - Sorts[csort].filteredIds.length;
        if (hidden != 0) {
            $('tr.filter', bt).show()
            .find('span.count').text(hidden).end()
            .find('span.total').text(Sorts[csort].ids.length+
                                     (Sorts[csort].ids.length == 1 ?
                                      ' beer' : ' beers'));
        }
        else {
            $('tr.filter', bt).hide();
        }

        if (!beertemp) {
            beertemp = $('tr.beer', bt);
            beertemp.remove();
        }

        var offset = MAX*cpage;

        var rev = (dir == 'reverse');
        var list = Sorts[csort].filteredIds;

        var beertable = $('table.beer tbody', bt).empty();

        if (list.length == 0) {
            $('tr.empty', bt).show();
        } else {
            $('tr.empty', bt).hide();
            for (var i = 0; i < MAX && i+offset < list.length; i++) {
                var idx = rev ? list.length-(1+i+offset) : i+offset;

                var line = beertemp.clone()
                .find('a.beername').attr('href', '/beer/'+list[idx]).end()
                .appendTo(beertable);

                loadBeer(line, list[idx]);
            }
        }

        get_session('beertable', setSession);
        get_user('beertable', setUser);
    };

    var loadSort = function(uri, dir, page, newids) {
        for (var sortidx = 0; sortidx < Sorts.length; sortidx++) {
            if (uri.indexOf(Sorts[sortidx].uri) >= 0) break;
        }

        if (newids) {
            //TODO: clear all cached ids?
            Sorts[sortidx].ids = newids;
            delete Sorts[sortidx].filteredIds;
            loadPage(sortidx, dir, page);
        }
        else if (Sorts[sortidx].ids) {
            loadPage(sortidx, dir, page);
        }
        else {
            if (Sorts[sortidx].fun) {
                Sorts[sortidx].ids = Sorts[sortidx].fun();
                loadPage(sortidx, dir, page);
            }
            else {
                $.getJSON(Sorts[sortidx].uri, [],
                          function(data) {
                              Sorts[sortidx].ids = data[Sorts[sortidx].field];
                              loadPage(sortidx, dir, page);
                          });
            }
        }
    };

    var filterUpdate = function() {
        for (var i = Sorts.length-1; i >= 0; i--) {
            Sorts[i].filteredIds = null;
        }

        var sortspec = sortpager.getSortspec();
        var page = sortpager.getPage();
        loadSort(sortspec.uri, sortspec.dir, page);
    };

    var loadTemplate = function(data) {
        bt = $(data).appendTo(Parent);

        sortpager = SortPager($('.sortpager', bt), Sorts, loadSort, cpage);

        GFilterListeners.push(filterUpdate);
    };

    var getTemplate = function() {
        $.get('/'+stv+'/html/beer_table.html', [], loadTemplate);
    };

    if (data_uri.indexOf('/api/place') == 0) {
        $.getJSON(data_uri+'/likelihood', [],
                  function(data) {
                      likelihoods = data.likelihoods;
                      getTemplate();
                  });
    } else {
        getTemplate();
    }

    return {loadSort:loadSort};
};

function BreweryTable (Parent, Breweries) {
    var MAX = 15;
    var pages = Math.ceil(Breweries.length/MAX);
    var cpage = (parseInt(get_hash_val('rp')) || 0)-1;
    var bt;
    var sortpager;
    var brewtemp;

    var loadBrewery = function(line, data) {
        if (!data.name) {
            fetch('brewery', data, function(d) { loadBrewery(line, d); });
            return;
        }
        
        line.find('.breweryname').attr('href', '/brewery/'+data.id).text(data.name);
        line.find('.brewerybeercount').text(data.beers.length+' beer'+(data.beers.length == 1 ? '':'s'));
        if (data.comments.length > 0)
            line.find('.brewerycommentcount').text(', '+data.comments.length+' comment'+(data.comments.length == 1 ? '':'s'));

        if (data.locations.length > 0
            && data.locations[0].location.length > 0)
            line.find('.breweryloc').text(data.locations[0].location);

        if (data.website)
            line.find('.breweryweb a').attr('href', data.website).text(data.website);

        line.show();
    };

    var loadPage = function(u, d, page) {
        if (page < 0) { page = 0; }
        else if (page >= pages) { page = pages-1; }

        if (cpage >= 0 && cpage != page) { set_hash_val('rp', page+1); }

        cpage = page;

        sortpager.setPageDisplay(cpage, pages);

        if (!brewtemp) {
            brewtemp = $('tr.brewery', bt);
            brewtemp.remove();
        }

        var offset = MAX*cpage;
        var brewlist = $('table.brewery', bt).empty();
        for (var i = 0; i < MAX && i+offset < Breweries.length; i++) {
            loadBrewery(brewtemp.clone().appendTo(brewlist),
                        Breweries[i+offset]);
        }
    };

    var loadTemplate = function(data) {
        bt = $(data).appendTo(Parent);

        sortpager = SortPager(bt.find('.sortpager'),
                              [{uri:data_uri}],
                              loadPage,
                              cpage);
    };

    $.get('/'+stv+'/html/brewery_table.html', [], loadTemplate);
};

function PlaceTable(Parent, Places) {
    var MAX = 15;
    var pages = Math.ceil(Places.length/MAX);
    var cpage = (parseInt(get_hash_val('pp')) || 0)-1;
    var likelihoods;
    var pt;
    var sortpager;
    var placetemp;

    var likelihoodLevel = function(n) {
        if      (n > 5) { return 'l5'; }
        else if (n > 4) { return 'l4'; }
        else if (n > 3) { return 'l3'; }
        else if (n > 2) { return 'l2'; }
        else if (n > 1) { return 'l1'; }
                          return 'l0';
    };

    var setLikelihood = function(line, id) {
        if (likelihoods[id]) {
            line.find('div.likelihoodmeter').addClass(likelihoodLevel(likelihoods[id]));
        } else {
            $.getJSON(data_uri+'/likelihood/'+id, [],
                      function(data) {
                          likelihoods[id] = data.likelihood;
                          setLikelihood(line, id);
                      });
        }
    };

    var clearLikelihood = function(elt) {
        elt.find('div.likelihoodmeter')
        .removeClass('l5')
        .removeClass('l4')
        .removeClass('l3')
        .removeClass('l2')
        .removeClass('l1')
        .removeClass('l0');
    };

    var submitLikelihood = function(o) {
        var t = $(o.target);
        if (t.eq(0).tagName != 'A') { t = t.parent(); }
        var confirm = t.hasClass('confirm');

        var placeid = t.attr('href').substring(t.attr('href').lastIndexOf('/')+1);
        var data =
            {cmd: t.hasClass('confirm') ? 'confirm' : 'deny',
             place: placeid};

        $.post(data_uri+'/likelihood', data,
               function(data) {
                   likelihoods = data.likelihoods;
                   clearLikelihood(t.parent());
                   setLikelihood(t.parent(), placeid);
               },
               'json');
        return false;
    };

    var loadPlace = function(line, data) {
        if (!data.name) {
            fetch('place', data, function(d) { loadPlace(line, d); });
            return;
        }

        line.find('.placename').attr('href', '/place/'+data.id).text(data.name);
        line.find('.placebeercount').text(data.beers.length+' beer'+(data.beers.length == 1 ? '':'s'));
        if (data.comments.length > 0)
            line.find('.placecommentcount').text(', '+data.comments.length+' comment'+(data.comments.length == 1 ? '':'s'));

        if (data.locations.length > 0
            && data.locations[0].location.length > 0)
            line.find('.placeloc').text(data.locations[0].location);

        if (data.website)
            line.find('.placeweb a').attr('href', data.website).text(data.website);

        line.show();

        if (likelihoods !== null) {
            line.find('td.likelihood').addClass('active');
            line.find('td.likelihood a')
            .attr('href', data_uri+'/likelihood/'+data.id)
            .click(submitLikelihood);

            setLikelihood(line, data.id);
        }
    };

    var setUser = function () {
        $('td.likelihood', pt).addClass('clickable');
    };

    var loadPage = function(u, d, page, NewPlaces) {
        if (NewPlaces) {
            Places = NewPlaces;
            pages = Math.ceil(Places.length/MAX);
            cpage = (parseInt(get_hash_val('pp')) || 0)-1;
        }

        if (page < 0) { page = 0; }
        else if (page >= pages) { page = pages-1; }

        if (cpage >= 0 && cpage != page) { set_hash_val('pp', page+1); }

        cpage = page;

        sortpager.setPageDisplay(cpage, pages);

        if (!placetemp) {
            placetemp = $('tr.place', pt);
            placetemp.remove();
        }

        var offset = MAX*cpage;
        var placelist = $('table.place tbody', pt).empty();
        
        if (Places.length == 0) {
            $('tr.empty', pt).show();
        } else {
            $('tr.empty', pt).hide();
            for (var i = 0; i < MAX && i+offset < Places.length; i++) {
                loadPlace(
                    placetemp.clone().appendTo(placelist),
                    Places[i+offset]);
            }
        }

        get_user('placetable', setUser);
    };

    var loadTemplate = function(data) {
        pt = $(data).appendTo(Parent);

        sortpager = SortPager(pt.find('.sortpager'),
                              [{uri:data_uri}],
                              loadPage,
                              cpage);
    };

    var getTemplate = function() {
        $.get('/'+stv+'/html/place_table.html', [], loadTemplate);
    };

    if (data_uri.indexOf('/api/beer') == 0) {
        $.getJSON(data_uri+'/likelihood', [],
                  function(data) {
                      likelihoods = data.likelihoods;
                      getTemplate();
                  });
    } else {
        likelihoods = null;
        getTemplate();
    }
    
    return {loadPage:loadPage};
};

function CommentTable(Parent, Comments) {
    var MAX = 15;
    var pages = Math.ceil(Comments.length/MAX);
    var ct;
    var sortpager;
    var commtemp;

    var pickPage = function() {
        var p = parseInt(get_hash_val('cp'))
        if (isNaN(p)) {
            var id = parseInt(get_hash_val('ci'));
            if (!isNaN(id)) {
                for (var i = Comments.length-1; i >= 0; i--) {
                    if (Comments[i] == id) break;
                }
                if (i >= 0) {
                    return Math.floor(i/MAX);
                } else {
                    return -1;
                }
            }
            else {
                return -1;
            }
        } else {
            return p-1;
        }
    };

    var cpage = pickPage();

    var showNewComment = function() {
        if (data_uri.indexOf("/api/user/") != 0) {
            $('.newcomment', ct).show();
        }
        return false;
    };

    var loggedInLine = function(line) {
        var actions = $('.actions a', line);

        for (var i = 0; i < actions.length; i++) {
            var a = $(actions[i]);
            if (a.hasClass('edit')) {
                for (var j = GUser.comments.length-1; j >= 0; j--) {
                    if ('/comment/'+GUser.comments[j] == a.attr('href')) {
                        a.show();
                        break;
                    }
                }
            }
            else {
                a.show();
            }
        }
    };

    var loggedIn = function() {
        var commentlines = $('.comments .comment', ct);
        for(var i = 0; i < commentlines.length; i++) {
            loggedInLine($(commentlines[i]));
        }
    };

    var edit = function(button) {
        var buttondiv = button.parent();
        var contentdiv = buttondiv.prev();
        buttondiv.hide();
        contentdiv.hide();
        $.get('/api'+button.attr('href'), [],
              function(data) {
                  CommentBox(buttondiv.parent(),
                             {text:data.text,
                              target:'/api'+button.attr('href'),
                              method:'PUT',
                              cancel:function() { contentdiv.show(); buttondiv.show(); },
                              success:function(data) {
                                  contentdiv.html(data.html).show();
                                  buttondiv.show();
                              }
                             });
              },
              'json');
    };

    var reply = function(button) {
        var buttondiv = button.parent();
        buttondiv.hide();
        $.get('/api'+button.attr('href'), [],
              function(data) {
                  CommentBox(buttondiv.parent(),
                             {text:'> '+buttondiv.parent().find('.details .brusername').text()+' said:\n>\n'+
                              '> '+data.text.replace(/\n/g, '\n> ')+'\n\n',
                              cancel:function() { buttondiv.show(); }
                             });
              },
              'json');
    };

    var submitReport = function(uri, rdiv, buttondiv) {
        var cmd = {cmd:'report', reasons: []};

        var reasons = rdiv.find('input:checked');
        if (reasons.length == 0) {
            rdiv.find('div.status').text('Please select at least one reason for reporting this beer.');
            return;
        }
        for (var i = reasons.length-1; i >= 0; i--) {
            cmd.reasons.push(reasons.eq(i).attr('name'));
        }
        cmd.reasons = cmd.reasons.join(',');

        cmd.extra = rdiv.find('textarea').val();

        $.ajax({url:'/api'+uri,
                type:'POST',
                data:cmd,
                success:function() {
                    alert('Your report has been submitted.  Thank you.');
                    rdiv.remove(); buttondiv.show();
                }
               });
    };

    var report = function(button) {
        var buttondiv = button.parent();
        buttondiv.hide();
        var rdiv = $('div.reportcomment').eq(0).clone()
        .appendTo(buttondiv.parent()).show();

        rdiv.find('a.cancel.button').click(
            function() { rdiv.remove(); buttondiv.show(); return false; });

        rdiv.find('a.report.button')
        .attr('href', button.attr('href'))
        .click(
            function() {
                submitReport(button.attr('href'), rdiv, buttondiv);
                return false;
            });
    };

    var commentAction = function(o) {
        o.stopPropagation();
        var button = $(o.target);
        if (button.get(0).tagName != 'A') { button = button.parent('A'); }

        if (button.hasClass('edit')) { edit(button); }
        if (button.hasClass('reply')) { reply(button); }
        if (button.hasClass('report')) { report(button); }
        return false;
    };

    var loadComment = function(line, data) {
        if (!data.user) {
            fetch('comment', data, function(d) { loadComment(line, d); });
            return;
        }

        if (data_uri.indexOf('/user') < 0) {
            fetch('user', data.user,
                  function(data) {
                      line.find('.brusername').text(data.name);
                  });

            line.find('.brusername').attr('href', '/user/'+data.user);
        } else {
            fetch(data.topic.type, data.topic.id,
                  function(t) {
                      line.find('.brusername').text(t.name || t.title);
                  });
            line.find('.brusername').addClass('topic')
            .attr('href', '/'+[data.topic.type,data.topic.id].join('/'));
        }

        line.find('.date').text(format_datetime(make_date(data.time.datetime)));

        line.find('.content').html(data.html);

        if (data.edit_time) {
            line.find('.editdate').text('(edited '+format_datetime(make_date(data.edit_time.datetime))+')');
        }
    };

    var loadPage = function(u, d, page) {
        if (page < 0) { page = 0; }
        else if (page >= pages) { page = pages-1; }

        if (cpage >= 0 && cpage != page) {
            set_hash_val('cp', page+1);
        }

        cpage = page;

        if (!commtemp) {
            commtemp = $('div.comment', ct);
            commtemp.remove();
        }

        if (Comments.length == 0) {
            sortpager.setPageDisplay(0, 1);
            $('div.empty', ct).show();
            return;
        }

        sortpager.setPageDisplay(cpage, pages);

        var offset = MAX*cpage;
        var commlist = $('div.comments', ct).empty();
        for (var i = 0; i < MAX && i+offset < Comments.length; i++) {
            loadComment(
                commtemp.clone()
                .find('.actions a').attr('href', '/comment/'+Comments[i+offset])
                .click(commentAction).end()
                .appendTo(commlist),
                Comments[i+offset]);
        }

        get_user('comments', loggedIn);
    };

    var loadTemplate = function(data) {
        ct = $(data).appendTo(Parent);

        $('.newcomment a', ct).click(
            function(o) {
                CommentBox($(o.target).parent());
            });

        get_user("newcomment", showNewComment);

        sortpager = SortPager(ct.find('.sortpager'),
                              [{uri:data_uri}],
                              loadPage,
                              cpage);
    };

    $.get('/'+stv+'/html/comment_table.html', [], loadTemplate);
};

function SortPager(Parent, Sorts, LoadFun, Start) {
    var sp;

    var setPageDisplay = function(page, total) {
        $('input[name=page]', sp).val(page+1);

        if (page == 0) {
            $('a[href$=#first]', sp).hide();
            $('a[href$=#prev]', sp).hide();
        }
        else {
            $('a[href$=#first]', sp).show();
            $('a[href$=#prev]', sp).show();
        }

        if (page >= total-1) {
            $('a[href$=#next]', sp).hide();
            $('a[href$=#last]', sp).hide();
        }
        else {
            $('a[href$=#next]', sp).show();
            $('a[href$=#last]', sp).show();
        }
    };

    var getPage = function() {
        return $('input[name=page]', sp).val()-1;
    };

    var getSortspec = function() {
        var selected = $('div.sorts a.active', sp);
        if (selected.length > 0) {
            return {uri:selected.attr('href'),
                    dir:selected.hasClass('rev') ? 'reverse' : 'forward'};
        } else {
            return {uri:Sorts[0].uri,
                    dir:'forward'};
        }
    };

    var changePage = function(o) {
        o.stopPropagation();
        var button = $(o.target);
        if (button.get(0).tagName != 'A') { button = button.parent('A'); }

        var target = 0;
        switch (button.attr('href').substring(
                  button.attr('href').indexOf('#'))) {
        case '#first': target = 0; break;
        case '#prev': target = getPage()-1; break;
        case '#next': target = getPage()+1; break;
        case '#last': target = Number.MAX_VALUE; break;
        }

        var sortspec = getSortspec();
        LoadFun(sortspec.uri, sortspec.dir, target);
        return false;
    };

    var changeSort = function(o) {
        o.stopPropagation(o);
        var link = $(o.target);
        if (link.get(0).tagName != 'A') { link = link.parent('A'); }

        if (link.hasClass('active')) {
            var dir;
            if (link.hasClass('rev')) { link.removeClass('rev'); dir = 'forward'; }
            else { link.addClass('rev'); dir = 'reverse'; }

            LoadFun(link.attr('href'), dir, getPage());
        }
        else {
            $('div.sorts a.active', sp).removeClass('active').removeClass('rev');
            link.addClass('active');
            LoadFun(link.attr('href'), 'forward', 0);
        }
        return false;
    };

    var loadTemplate = function(html) {
        sp = $(html).appendTo(Parent);

        var sorts = [];
        for (var i = Sorts.length-1; i >= 0; i--) {
            if ('label' in Sorts[i]) {
                sorts = '<a class="sortbutton'+(Sorts[i].current ? ' active' : '')+
                    '" href="'+Sorts[i].uri+'">'+Sorts[i].label+'</a> ' + sorts;
            }
        }
        if (sorts.length > 0) {
            $('div.sorts', sp).html('<span class="label">sort:</span> '+sorts);
            $('a.sortbutton', sp).click(changeSort);
        }

        $('.pages a', sp).click(changePage);

        var sortspec = getSortspec();
        LoadFun(sortspec.uri, sortspec.dir, Start || 0);
    };

    $.get('/'+stv+'/html/sortpager.html', [], loadTemplate);

    return {setPageDisplay:setPageDisplay,
            getSortspec:getSortspec,
            getPage:getPage};
};

function ViewTabber(Parent, Tabs) {
    var vt;
    var inited = false;

    var showTab = function(o) {
        href = $(o.target).attr('href');
        var ts = $('div.tabs a', vt);
        var vs = $('div.tabview', vt);
        for (var i = 0; i < ts.length; i++) {
            if (ts.eq(i).attr('href') == href) {
                ts.eq(i).addClass('selected');
                vs.eq(i).show();
                if (inited) set_hash_val('at', Tabs[i].name);
            }
            else {
                ts.eq(i).removeClass('selected');
                vs.eq(i).hide();
            }
        }
        return false;
    };

    var makeTabs = function() {
        var tabtemp = $('div.tabs a', vt).remove();
        var viewtemp = $('div.tabview', vt).remove();
        var currenttab = 0;
        var lasttab=get_hash_val('at');

        for (var i = 0; i < Tabs.length; i++) {
            tabtemp.clone().attr('href', '#tab-'+Tabs[i].name)
                           .text(Tabs[i].label)
                           .click(showTab)
                           .appendTo($('div.tabs', vt));
            var view = viewtemp.clone().attr('id', 'tab-'+Tabs[i].name).appendTo(vt);
            Tabs[i].createView(view);
            if (Tabs[i].name == lasttab) { currenttab = i; }
        }

        showTab({target:$('div.tabs a', vt).eq(currenttab)});
        inited=true;
    };

    var loadTemplate = function(data) {
        vt = $(data).appendTo(Parent);
        makeTabs();
    };

    $.get("/"+stv+"/html/viewtabber.html", [], loadTemplate);
};

var ReportForm = function(Uri, FormDiv) {
    var showForm = function() {
        FormDiv.show();
        return false;
    };

    var cancelReport = function() {
        FormDiv.hide();
        return false;
    };

    var submitReport = function() {
        var cmd = {cmd:'report', reasons: []};

        var reasons = FormDiv.find('input:checked');
        if (reasons.length == 0) {
            FormDiv.find('div.status').text('Please select at least one reason for reporting this beer.');
            return false;
        }
        for (var i = reasons.length-1; i >= 0; i--) {
            cmd.reasons.push(reasons.eq(i).attr('name'));
        }
        cmd.reasons = cmd.reasons.join(',');

        cmd.extra = FormDiv.find('textarea').val();

        $.ajax({url:Uri,
                type:'POST',
                data:cmd,
                success:function() {
                    alert('Your report has been submitted.  Thank you.');
                    FormDiv.hide();
                }
               });
    };

    $('<a href="'+Uri+'" class="report"><span>report</span></a>')
    .click(showForm).prependTo('#help');

    FormDiv.find('a.report.button').click(submitReport);
    FormDiv.find('a.cancel.button').click(cancelReport);
};

if (document.cookie.indexOf('beer_cookie=') >= 0) {
    $.ajax({url:'/session',
            type:'GET',
            success:
            function(data) {
                GSession = data;
                if (GSession.id) {
                    $.getJSON('/api/user/'+GSession.id, [],
                              function(data) {
                                  GUser = data;
                                  $(WelcomeBox);
                                  fire_listeners(GLoginListeners);
                              });
                }
                else {
                    $(LoginBox);
                }
                fire_listeners(GSessionListeners);
            },
            error:
            function() {
                GSession = null;
                clear_cookies();
                $(LoginBox);
                fire_listeners(GSessionListeners);
            },
            dataType:'json'
           });
} else {
    GSession = null;
    $(LoginBox);
    fire_listeners(GSessionListeners);
}

$(function() {
    SearchBox($('<div/>').appendTo('#right'));
    FilterBox($('<div/>').appendTo('#right'));
    TagBox($('<div/>').appendTo('#right'));
    LocalBox($('<div/>').appendTo('#right'));
    LinksBox($('<div/>').appendTo('#right'));
});
