var xhtmlns = "http://www.w3.org/1999/xhtml";
var xmlns = "http://www.w3.org/XML/1998/namespace";
var dotplanns = 'http://xml.laranja.org/xmlns/dotplan/plan';
var reportns = 'http://xml.laranja.org/xmlns/dotplan/report';

function err_logging(f) {
    return function() {
        try {
            f.apply(this, arguments);
        } catch(err) {
            if(err.fileName && err.lineNumber)
                logError(err + ' on ' + err.fileName + ':' + err.lineNumber);
            else
                logError(err);
            throw(err);
        }
    };
}

function doJSONRequest(url, data) {
    var r = getXMLHttpRequest();
    if (r.overrideMimeType) {
        r.overrideMimeType('text/javascript');
    }
    r.open("POST", url, true);
    var d = sendXMLHttpRequest(r, serializeJSON(data));
    d.addCallback(evalJSONRequest);
    return d;
}

function formObject(element) {
    // make an object of results from form contents
    var data = formContents(element);
    var result = {};
    for(var i = 0; i < data[0].length; i++)
        result[data[0][i]] = data[1][i];
    return result;
}


function Site() {
    this.index_d = loadJSONDoc('/entry/');
    this.subj_index_d = loadJSONDoc('/subject/');
    this.news_d = loadJSONDoc('/news-clippings/lalonews/');
    this.subjects = {};
    this.users = {};
    this.slugs = {};
    this.years = {};
    this.selected = null;
    this.post_name = '';
    this.comments_expanded = false;
    this.news = {};
    this.news_by_date = [];
    this.e = {comments: null};
    for(var e in this.elements)
        this.e[e] = null;
    this.copyright_notice = null;
    bindMethods(this);
    delete this.entry_link_re; // bindMethods replaces it with a function :-/
    addLoadEvent(this.build);
}

Site.prototype.Version = '2.0';
Site.prototype.entry_link_re = /^\/entry\/([a-z-]+)$/;

Site.prototype.elements = {
    main_box: 'main-box',
    loading: 'loading',
    post_title: 'post-title',
    attribution: 'attribution',
    who: 'attribution-who',
    when: 'attribution-when',
    badges: 'post-badges',
    nav: 'nav-wrapper',
    years: 'years-list',
    news: 'news-list'
};

Site.prototype.build = function() {
    logDebug('loaded');
    window.A = MochiKit.DOM.A; /* google reader breaks this */
    window.SMALL = MochiKit.DOM.createDOMFunc("small");
    window.EM = MochiKit.DOM.createDOMFunc("em");
    this.copyright_notice = $('copyright-notice').innerHTML;
    for(var i = 0; i < document.body.childNodes.length;) {
        var elem = document.body.childNodes[i];
        if((elem.tagName) && (elem.tagName.toLowerCase() == 'p')) i++;
        else if((elem.className) && (elem.className == 'reader-publisher-module')) i++;
        else removeElement(elem);
    }
    appendChildNodes(document.body,
                     H1({id: "main-title"}, 'Hysterical Raisins'),
                     SPAN({id: "loading"}, 'Loading...'),
                     IMG({id: "corner-ne", src: '/media/img/raisins/ne.png'}),
                     IMG({id: "corner-se", src: '/media/img/raisins/se.png'}),
                     IMG({id: "corner-sw", src: '/media/img/raisins/sw.png'}),
                     IMG({id: "corner-nw", src: '/media/img/raisins/nw.png'}),
                     DIV({id: "main-box"}),
                     H1({id: "post-title"}),
                     P({id: "post-badges"}),
                     P({id: "attribution", 'class': 'signature'},
                       SPAN({id: "attribution-who"}),
                       ', on ', SPAN({id: "attribution-when"}),
                       ' -- ', SPAN({id: "attribution-copyright"},
                                     'copyrights')),
                     DIV({id: "nav-wrapper"},
                         H3(null, 'All posts'),
                         UL({id: "years-list"})),
                     DIV({id: "music-player"}),
                     DIV({'class': 'reader-publisher-module',
                          title: 'My news clippings from the web'},
                         H3(null, 'LaloNews'),
                         UL({id: 'news-list'}))
                     );
    $('music-player').innerHTML = '<object width="132" height="158" data="http://www.jamendo.com/get/track/list/album/id/playerpage/?item_o=random&amp;n=all&amp;player_height=158&amp;player_refuid=167818&amp;player_type=coverinfos1&amp;player_width=132&amp;playercode_type=generic&amp;subset=user_star&amp;uid=167818"  type="text/html">&nbsp;</object>r a d i o l a l o';
    $('music-player').style.top = (getElementDimensions(document.body) / 2 - 79)
      + 'px';
    for(var e in this.elements)
        this.e[e] = $(this.elements[e]);
    connect(this.e.news.previousSibling, 'onclick', this, 'toggle_box');
    connect(this.e.nav.firstChild, 'onclick', this, 'toggle_box');
    connect($('attribution-copyright'), 'onclick', this,
              'display_copyright_notice');
    this.index_d.addCallbacks(this.load,
                              this.load_error);
    this.subj_index_d.addCallbacks(this.load_subjects,
                                   this.load_error);
    this.news_d.addCallbacks(this.load_news,
                             this.load_error);
};

Site.prototype.load = err_logging(function(data) {
    logDebug('index loaded');
    var preselect_re = new RegExp('^' + location.protocol + '//' +
                                  location.hostname +
                                  '(:\\d+)?/entry/(.+)/$');
    var preselect_match = document.referrer.match(preselect_re);
    for(var i = 0; i < data.users.length; i++)
        new User(data.users[i], this);
    for(var i = 0; i < data.entries.length; i++)
        new Entry(data.entries[i], this);

    this.e.years.innerHTML = '';
    for(var year in this.years) {
        var year_el = LI({'class': 'year'}, year, UL({'class': 'hidden'}));
        connect(year_el, 'onclick', this, 'expand_branch');
        this.e.years.appendChild(year_el);
        var months = year_el.lastChild;
        for(var month in this.years[year]) {
            var month_el = LI({"class": 'month'},
                              month, UL({"class": 'hidden'}));
            connect(month_el, 'onclick', this, 'expand_branch');
            months.appendChild(month_el);
            var entries = month_el.lastChild;
            for(var e = 0; e < this.years[year][month].length; e++) {
                var entry = this.years[year][month][e];
                var entry_el = LI({"class": 'entry'},
                                  SMALL(null, entry.day_time(), ': '),
                                  SPAN({"class": 'title'}, entry.title));
                connect(entry_el, 'onclick', this,
                        partial(this.open_entry, entry.slug));
                for(var n = 0; n < entry.badges.length; n++) {
                    cat = entry.badges[n];
                    badge = IMG({"class": 'badge', src: cat.icon,
                                 name: cat.name, title: cat.label});
                    entry_el.insertBefore(badge, entry_el.firstChild);
                    connect(badge, 'onclick', this, 'show_subject_info');
                }
                entry.menu_entry = entry_el;
                entries.appendChild(entry_el);
            }
        }
    }

    if(preselect_match)
        this.open_entry(preselect_match[2]);
    else
        this.open_entry(keys(this.slugs)[0]);
});

Site.prototype.load_subjects = err_logging(function(data) {
    logDebug('subjects loaded');
    for(var i = 0; i < data.length; i++) {
        this.subjects[data[i].name] = data[i];
    }
});

Site.prototype.load_news = err_logging(function(data) {
    logDebug('news feed loaded');
    this.news_by_date = data;
    for(var i = 0; i < data.length; i++) {
        var clip = data[i];
        this.news[clip.id] = clip;
        var clip_el = LI(null, clip.title);
        clip_el.clipping = clip.id;
        connect(clip_el, 'onclick', this, 'display_clipping');
        this.e.news.appendChild(clip_el);
    }
});

Site.prototype.load_error = function(err) {
    logFatal(err.message + ': ' + err.req.status + ' ' + err.req.statusText,
             err.req.responseText);
    this.e.main_box.innerHTML = 'Sorry, there was an error retrieving the ' +
    'entry index.  Try again in a few minutes.';
};

Site.prototype.close_entry = function() {
    if(!this.selected) return;
    removeElementClass(this.selected.menu_entry, 'current');
    this.e.main_box.innerHTML = '';
    replaceChildNodes(this.e.post_title);
    hideElement(this.e.attribution);
    replaceChildNodes(this.e.badges);
    addElementClass(this.e.loading, 'hidden');
};

Site.prototype.open_entry = function(slug, event) {
    this.close_entry();
    this.selected = this.slugs[slug];
    if(this.selected.body)
        this.display_entry();
    else {
        doSimpleXMLHttpRequest('/entry/' + slug + '/')
            .addCallback(partial(this.load_entry, this.selected));
        loadJSONDoc('/entry/' + slug + '/comments/')
            .addCallback(partial(this.load_entry_comments, this.selected));
    }
    if(event)
        event.stop();
};

Site.prototype.load_entry = err_logging(function(entry, data) {
    var bodies = data.responseXML.getElementsByTagNameNS(xhtmlns, 'body');
    if(bodies.length)
        entry.body = bodies[0].innerHTML;
    else
        entry.body = '';
    if(entry === this.selected)
        this.display_entry();
});

Site.prototype.display_entry = err_logging(function() {
    addElementClass(this.e.loading, 'hidden');
    removeElementClass(this.e.main_box, 'subject');
    this.e.main_box.innerHTML = this.selected.body;
    this.e.main_box.appendChild(A({"class": 'permalink',
                    href: '/entry/' + this.selected.slug},
            'link to this post'));
    document.title = this.selected.title + ' @ Blog @ Hysterical Raisins';
    this.e.main_box.focus();
    replaceChildNodes(this.e.post_title, this.selected.title);
    replaceChildNodes(this.e.who, this.selected.user.username);
    replaceChildNodes(this.e.when, this.selected.when);
    showElement(this.e.attribution);
    replaceChildNodes(this.e.badges);
    for(var n = 0; n < this.selected.badges.length; n++) {
        var cat = this.selected.badges[n];
        var badge = IMG({"class": 'badge', src: cat.icon, name: cat.name,
                         title: cat.label});
        connect(badge, 'onclick', this, 'show_subject_info');
        this.e.badges.appendChild(badge);
    }

    this.expand_branch(null, this.selected.menu_entry.parentNode
                                 .parentNode.parentNode.parentNode);
    this.expand_branch(null, this.selected.menu_entry.parentNode.parentNode);
    addElementClass(this.selected.menu_entry, 'current');

    var links = this.e.main_box.getElementsByTagName('a');
    for(var i = 0; i < links.length; i++) {
        var link = links[i];
        if(!link.href) continue; // anchor, not link
        if(link.host != location.host) continue;
        var match = link.pathname.match(this.entry_link_re);
        if(!match) continue;
        connect(link, 'onclick', this, partial(this.open_entry, match[1]));
    }

    var c = this.selected.comments;
    this.e.main_box.appendChild(DIV({id: 'comments',
                    'class': this.comments_expanded ? null : 'collapsed'},
                                    H3(null, c.length, ' comment',
                                       (c.length == 1) ? null : 's')));
    this.e.comments = $('comments');
    connect(this.e.comments.firstChild, 'onclick', this, 'toggle_box');
    for(var i = 0; i < c.length; i++) {
        var comment = c[i];
        this.e.comments.appendChild(DIV({'class': 'comment'}));
        this.e.comments.lastChild.innerHTML = comment.text;
        this.e.comments.lastChild.appendChild
            (P({'class': 'signature'},
               SPAN({'class': 'who'}, comment.who), ', on ',
               SPAN({'class': 'when'}, comment.when.replace('T', ' '))));
    }
    this.e.comments.appendChild
        (EM(null,
            STRONG(null, 'Note:'), ' comments are posted anonymously.  ' +
            'The handle someone chooses to use to sign their comment is in ' +
            'no way indication of their identity, and even if it looks like ' +
            'the username of an user of this site, it might not be the same ' +
            'person.  Also, two comments with the same signature may be ' +
            'from different people.  So take the signatures with a grain of ' +
            'salt.'));
    this.e.comments.appendChild
      (DIV({id: 'post-form'},
           A({href: 'http://docutils.sourceforge.net/docs/user/rst/quickstart.html',
              target: '_blank'}, 'help on reStructuredText markup'),
           LABEL(null, 'post yours'),
           TEXTAREA({id: 'post-form-text', rows: 7,
                     title: 'type your comment, using reStructuredText.'},
               this.selected.unsent_comment),
           P({'class': 'signature'},
             INPUT({id: 'post-form-who', size: 10, value: this.post_name,
                    title: 'type your name as you want it to appear'}),
             ', on ', SPAN({'class': 'when'}, 'the near future')),
           INPUT({type: 'button', value: 'post',
                  title: 'submit your comment'}),
           INPUT({type: 'button', value: 'refresh discussion',
                  title: 'check for new posts'})));
    connect('post-form-who', 'onchange', this, 'save_post_name');
    connect('post-form-text', 'onchange', this, 'save_post_text');
    connect(this.e.comments.lastChild.lastChild, 'onclick', this,
            function() {
                loadJSONDoc('/entry/' + this.selected.slug + '/comments/')
                    .addCallback(partial(this.load_entry_comments, this.selected));
            });
    connect(this.e.comments.lastChild.lastChild.previousSibling, 'onclick',
            this, 'post_comment');
});

Site.prototype.load_entry_comments = err_logging(function(entry, data) {
    entry.load_comments(data);
    if(entry === this.selected)
        this.display_entry();
});

Site.prototype.post_comment = function(event) {
    log('posting comment');
    if(!this.selected.unsent_comment) {
        alert('No content!');
        $('post-form-text').focus();
        return;
    }
    if(!this.post_name) {
        alert('Please sign your post!');
        $('post-form-who').focus();
        return;
    }
    var r = doJSONRequest('/entry/' + this.selected.slug + '/comments/',
                          {text: this.selected.unsent_comment,
                           who: this.post_name});
    r.addCallback(partial(function(entry, data) {
                entry.unsent_comment = null;
                return data;
            }, this.selected));
    r.addCallbacks(partial(this.load_entry_comments, this.selected),
                   function() {alert('Sorry, failed to post your comment. ' +
                                     'Try again in a few minutes.')});
};

Site.prototype.save_post_name = function(event) {
    this.post_name = event.src().value;
};

Site.prototype.save_post_text = function(event) {
    this.selected.unsent_comment = event.src().value;
};

Site.prototype.show_subject_info = function(event) {
    var subject = this.subjects[event.src().name];
    log('showing subject info for ' + repr(subject.title));
    replaceChildNodes(this.e.main_box);
    addElementClass(this.e.main_box, 'subject');
    if(subject.description.length) {
        var description_p = P(null);
        description_p.innerHTML = subject.description;
        this.e.main_box.appendChild(description_p);
    }
    if(subject.web.length || subject.wikipedia.length) {
        var links_p = P(null);
        if(subject.web.length)
            links_p.appendChild(A({href: subject.web}, subject.web));
        if(subject.web.length && subject.wikipedia.length)
            appendChildNodes(links_p, ' — ');
        if(subject.wikipedia.length)
            links_p.appendChild(A({href: subject.wikipedia},
                                  subject.wikipedia));
        this.e.main_box.appendChild(links_p);
    }

    post_index = UL({'class': 'linklist'});
    for(var year in this.years) {
        for(var month in this.years[year]) {
            for(var e = 0; e < this.years[year][month].length; e++) {
                var entry = this.years[year][month][e];
                var relevant = false;
                var entry_el = LI({"class": 'entry'},
                                  SMALL(null, entry.when, ' by ', entry.who,
                                        ': '),
                                  SPAN({"class": 'title'}, entry.title));
                connect(entry_el, 'onclick', this,
                        partial(this.open_entry, entry.slug));
                for(var n = 0; n < entry.badges.length; n++) {
                    cat = entry.badges[n];
                    if(cat.name == subject.name) relevant = true;
                    badge = IMG({"class": 'badge', src: cat.icon,
                                 name: cat.name, title: cat.label});
                    entry_el.insertBefore(badge, entry_el.firstChild);
                    connect(badge, 'onclick', this, 'show_subject_info');
                }
                if(relevant)
                    post_index.appendChild(entry_el);
            }
        }
    }
    if(post_index.childNodes.length)
        appendChildNodes(this.e.main_box,
                         H3(null, 'All posts on this subject'),
                         post_index);

    document.title = subject.title + ' @ Subjects @ Hysterical Raisins';
    this.e.main_box.focus();
    replaceChildNodes(this.e.post_title, subject.title);
    replaceChildNodes(this.e.who);
    replaceChildNodes(this.e.when);
    hideElement(this.e.attribution);
    replaceChildNodes(this.e.badges, EM(null, 'subject info: '),
                      IMG({"class": 'badge', src: subject.icon,
                           title: subject.label}));
    removeElementClass(this.selected.menu_entry, 'current');
    if(event)
        event.stop();
};

Site.prototype.display_clipping = function(event) {
    var clipping = this.news[event.src().clipping];
    log('showing clipping ' + repr(clipping.title));
    replaceChildNodes(this.e.main_box);
    addElementClass(this.e.main_box, 'clipping');
    if(clipping.content.length) {
        var content_p = P(null);
        content_p.innerHTML = clipping.content;
        this.e.main_box.appendChild(content_p);
    }
    this.e.main_box.appendChild
      (P({'class': 'credits'}, 'by ',
         EM(null, clipping.author), ' from ',
         A({href: clipping.source_link}, clipping.source), ' on ',
         clipping.date.replace(/T(..:..).*$/, ' $1'), ' -- ',
         A({href: clipping.link}, 'original article')));
    if(clipping.writeup.length) {
        var writeup_p = P({'class': 'writeup'});
        writeup_p.innerHTML = clipping.writeup;
        this.e.main_box.appendChild(writeup_p);
    }

    document.title = clipping.title + ' @ Clippings @ Hysterical Raisins';
    this.e.main_box.focus();
    replaceChildNodes(this.e.post_title, clipping.title);
    replaceChildNodes(this.e.who);
    replaceChildNodes(this.e.when);
    hideElement(this.e.attribution);
    replaceChildNodes(this.e.badges, EM(null, 'LaloNews: '));
    removeElementClass(this.selected.menu_entry, 'current');
    if(event)
        event.stop();
};

Site.prototype.display_copyright_notice = function(event) {
    replaceChildNodes(this.e.main_box);
    addElementClass(this.e.main_box, 'notice');
    this.e.main_box.innerHTML = this.copyright_notice;

    document.title = 'Copyright notice @ Hysterical Raisins';
    this.e.main_box.focus();
    replaceChildNodes(this.e.post_title, 'Copyright notice');
    replaceChildNodes(this.e.who);
    replaceChildNodes(this.e.when);
    hideElement(this.e.attribution);
    replaceChildNodes(this.e.badges);
    removeElementClass(this.selected.menu_entry, 'current');
    if(event)
        event.stop();
};

Site.prototype.expand_branch = function(event, branch) {
    branch = branch || event.src();
    for(var i = 0; i < branch.parentNode.childNodes.length; i++) {
        var node = branch.parentNode.childNodes[i];
        node.lastChild.className = 'hidden';
    }
    branch.lastChild.className = '';
    if(event)
        event.stopPropagation();
};

Site.prototype.toggle_box = function(event) {
    toggleElementClass('collapsed', event.src().parentNode);
    this.comments_expanded = !hasElementClass(this.e.comments, 'collapsed');
};
var site = new Site;


function User(data, site) {
    update(this, data);
    this.posts = [];
    site.users[this.username] = this;
}


function setdefault_object(obj, name, value) {
    var tmp = {};
    tmp[name] = value;
    setdefault(obj, tmp);
    return obj[name];
}


function Entry(data, site) {
    update(this, data);
    this.date = isoTimestamp(this.when);
    this.when = this.when.replace('T', ' ');
    this.user = site.users[this.who];
    this.user.posts.push(this);
    this.body = null;
    this.comments = [];
    this.unsent_comment = null;
    site.slugs[this.slug] = this;
    var year = this.date.getFullYear();
    var month = this.date.getMonth() + 1;
    setdefault_object(site.years, year, {});
    setdefault_object(site.years[year], month, []);
    site.years[year][month].push(this);
}

Entry.prototype.load_comments = function(comments) {
    this.comments = comments;
    for(var i = 0; i < comments.length; i++) {
        var c = this.comments[i];
        c.date = isoTimestamp(c.when);
        c.when = toISOTimestamp(c.date);
    }
};

Entry.prototype.number_formatter = numberFormatter('00');

Entry.prototype.day_time = function() {
    return this.date.getDate() + ', ' +
           this.number_formatter(this.date.getHours()) + ':' +
           this.number_formatter(this.date.getMinutes());
}
