// Constants:
MAX_SIMILAR = 3;
EMAIL = "mailto:gre7g.luterman" + "@gmail.com";

// Globals:
StoryXml = null;
KeywordList = new Array();
StoryTimes = {};
Chapters = {};
PrefetchList = new Array();
AvgVote = undefined;

// Sort by date (most recent first) compare function
function DateComparitor($A, $B) {
    return Number($($B).attr("updated")) - Number($($A).attr("updated"));
}

// Add a new item to the story index
function AddItem($StoryXml, $ItemTemplate, $Dest) {
    var $Story = $StoryXml.attr("id");
    var $Entry = $ItemTemplate.clone();
    var $Printable = $StoryXml.find("printable");
    var $Title = $Entry.find("div.title a");
    var $Status = $StoryXml.attr("status");

    // The template is hidden, so we must remove that
    $Entry.removeClass("hidden");

    // Is there a PDF available?
    if ($Printable.length) {
        // Yes, show the icon and link it up
        $Entry.find("a.printable").removeClass("hidden");
        $Entry.find("a.printable").attr("href", $Story + "/" +
            $Printable.attr("filename"));
    }

    // Set the story title and whether it is complete/adult
    $Title.attr("href", "small.php#!story=" + $Story);
    $Title.html($StoryXml.attr("name"));
    if ($Status != "complete") {
        $Entry.find("img.none_avail").removeClass("hidden");
        $Entry.find("span.incomplete").removeClass("hidden").html("(" + $Status + ")");
    }
    if ($StoryXml.attr("adult") == "true") {
        $Entry.find("span.adult").removeClass("hidden");
    }

    // Set the description
    $Entry.find("div.description").html($StoryXml.find("description").text());

    // Add the new item to the list
    $Dest.append($Entry);
}

function CollectKeywords($Index, $StoryXml) {
    // While we're here, let's collect up the keywords
    var $Keywords = $($StoryXml).find("tags").text().split(",");
    $($Keywords).each(function($Index, $Keyword) {
        if ($.inArray($Keyword, KeywordList) == -1) KeywordList.push($Keyword);
    });
}

// Add a keyword to the list of filters
function ShowKeyword($Keyword, $ItemTemplate, $List) {
    var $Item = $ItemTemplate.clone();
    var $Link = $Item.find("a");

    $Item.removeClass("hidden");
    $Link.click(function($Event) { OnFilter($Event, $Keyword); });
    $Link.html($Keyword);
    $List.append($Item);
}

// Handle the user clicking on a filter keyword
function OnFilter($Event, $Keyword) {
    $.cookie("story_filter", $Keyword, { expires: 7 });
    ShowIndex();
}

// Compare function for case-insensative sorting
function StringCompare($A, $B) {
    $A = $A.toUpperCase();
    $B = $B.toUpperCase();
    if ($A > $B) return 1;
    if ($A < $B) return -1;
    return 0;
}

// Show all stories and keywords
function ShowIndex() {
    var $ItemTemplate = $($.find("div.item:last"));
    var $Dest = $("div#item_list");
    var $SortedBy;
    var $NoFilter = $("div.no_filter");
    var $Filter = $.cookie("story_filter");

    // Tell the user how we're sorting
    if ($Filter) {
        $SortedBy = 'Stories with keyword "' + $Filter + '" (most recent on top)';
        $NoFilter.removeClass("hidden");
    } else {
        $SortedBy = "Listed by date (most recent on top)";
        $NoFilter.addClass("hidden");
    }
    $("div#sorted_by").html($SortedBy);

    // Sort stories by date and add them to the list
    var $Stories = StoryXml.find("file");
    if ($Filter) {
        $Stories = $Stories.filter(function($Index) {
            var $Keywords = $($Stories[$Index]).find("tags").text().split(",");
            return $.inArray($Filter, $Keywords) >= 0; });
    }
    $Stories = $Stories.sort(DateComparitor);
    $Dest.html("");
    $Stories.each(function($Index, $Xml) {
        AddItem($($Xml), $ItemTemplate, $Dest); });
}

// Display all previously-found keywords
function ShowKeywords() {
    var $ItemTemplate = $($.find("li.keyword"));
    var $List = $("ul#keyword_list");

    $.each(KeywordList.sort(StringCompare), function($X, $Keyword) {
        ShowKeyword($Keyword, $ItemTemplate, $List);
    });
    $("div.no_filter a").click(function($Event) { OnFilter($Event, ""); });
}

// Save fetched chapter into a hash to cache any future fetches
function CacheChapter($Path, $Html, $Display) {
    Chapters[$Path] = $Html;
    if ($Display) {
        $("div#story").html($Html);
    }

    // More to cache?
    var $Queued = PrefetchList.pop();
    if ($Queued && !Chapters[$Queued]) {
        $.get($Queued, function($Html) { CacheChapter($Queued, $Html, false); });
    }
}

// Display the current story
function ShowStory() {
    var $Params = $.deparam.fragment();
    var $ChapterNum = parseInt($Params["chapter"]) || 1;
    var $PrevChapterNum = $ChapterNum - 1;
    var $NextChapterNum = $ChapterNum + 1;
    var $Story = $Params["story"];
    var $File = StoryXml.find("file#" + $Story);
    var $StoryName = $File.attr("name");
    var $Chapter = $File.find("chapter#" + $ChapterNum);
    var $PrevChapter = $File.find("chapter#" + $PrevChapterNum);
    var $NextChapter = $File.find("chapter#" + $NextChapterNum);
    var $Path = $Story + "/" + $Chapter.attr("filename");
    var $Comments = $Chapter.attr("comments");

    // Scroll to top
    window.scroll(0, 0);

    // Set Title and Chapter
    document.title = $StoryName + ", Chapter " + $ChapterNum;
    $("div#title").text($StoryName);
    $("div#chapter").text("Chapter " + $ChapterNum);

    // Hide/show navigation and set links
    if ($PrevChapter.length) {
        $("div.prev,div.first").removeClass("hidden");
        $("div.first a").attr("href", "#!story=" + $Story + "&chapter=1");
        $("div.prev a").attr("href", "#!story=" + $Story + "&chapter=" +
          $PrevChapterNum);
        var $PrevChapter = $File.find("chapter#" + $PrevChapterNum);
        PrefetchList.push($Story + "/" + $PrevChapter.attr("filename"));
    } else {
        $("div.prev,div.first").addClass("hidden");
    }
    if ($NextChapter.length) {
        $("div.next,div.last").removeClass("hidden");
        $("div.last a").attr("href", "#!story=" + $Story + "&chapter=" +
          $File.find("chapter").length);
        $("div.next a").attr("href", "#!story=" + $Story + "&chapter=" +
          $NextChapterNum);
        var $NextChapter = $File.find("chapter#" + $NextChapterNum);
        PrefetchList.push($Story + "/" + $NextChapter.attr("filename"));
    } else {
        $("div.next,div.last").addClass("hidden");
    }
    $("a#comments").attr("href", $Comments);

    // Fetch the content
    if (Chapters[$Path]) {
        CacheChapter($Path, Chapters[$Path], true);
    } else {
        $.get($Path, function($Html) { CacheChapter($Path, $Html, true); });
    }

    ShowSimilar(FindSimilarStories($Story, $.cookie("story_filter")));

    $.get("cgi/vote.php?story=" + $Story + "&chapter=" + $ChapterNum, OnVoteAvg);
}

// Received vote average
function OnVoteAvg($Avg) {
    if (! $Avg) $Avg = 1.0 / 16;

    if ($Avg < 0) {
        AvgVote = $Avg;
        StarMouse(undefined, -AvgVote);
        $("div#rating img.star1").removeClass("hidden");
    } else {
        AvgVote = Math.ceil($Avg * 16);
        $("div#rating img#avg").attr("width", AvgVote);
        $("div#rating img#trans").attr("width",
            80 - AvgVote).css("background-position",
            (-AvgVote) + "px 0");
    }
}

// Navigate stories
function OnHashChange($Event) {
    if (StoryXml) ShowStory();
}

// Similar filter function
function SimilarFilter($StoryXml, $Filter, $Story) {
    var $DateStr = $StoryXml.attr("updated");
    var $Updated = new Date($DateStr.slice(0, 4), $DateStr.slice(4, 6),
        $DateStr.slice(6, 8));
    var $Keywords = $StoryXml.find("tags").text().split(",");
    var $ID = $StoryXml.attr("id");

    StoryTimes[$ID] = $Updated.getTime();
    if ($ID == $Story) return false;
    return !$Filter || ($.inArray($Filter, $Keywords) >= 0);
}

function SimilarDate($A, $B, $StoryDate) {
    var $DiffA = Math.abs(StoryTimes[$($A).attr("id")] - $StoryDate);
    var $DiffB = Math.abs(StoryTimes[$($B).attr("id")] - $StoryDate);
    return $DiffA > $DiffB;
}

// Return a list of similar stories
function FindSimilarStories($Story, $Filter)
{
    var $Stories = StoryXml.find("file");
    var $StoryDate;

    $Stories = $Stories.filter(function($Index) {
        return SimilarFilter($($Stories[$Index]), $Filter, $Story); });
    if ($Stories.length == 0) return FindSimilarStories($Story);

    $StoryDate = StoryTimes[$Story];
    $Stories = $Stories.sort(function($A, $B) {
      return SimilarDate($A, $B, $StoryDate);
    });
    return $Stories.slice(0, MAX_SIMILAR);
}

// Add a keyword to the list of filters
function ShowSimLink($StoryXml, $ItemTemplate, $List) {
    var $Item = $ItemTemplate.clone();
    var $Link = $Item.find("a");

    $Item.removeClass("hidden");
    $Link.attr("href", "#!story=" + $StoryXml.attr("id"));
    $Link.html($StoryXml.attr("name"));
    $List.append($Item);
}

// Show a list of similar stories
function ShowSimilar($Similar) {
    var $ItemTemplate = $($.find("li.other:last"));
    var $List = $("ul#other_list");

    $List.html("");
    $.each($Similar, function($X, $StoryXml) {
        ShowSimLink($($StoryXml), $ItemTemplate, $List);
    });
    $("div.no_filter a").click(function($Event) { OnFilter($Event, ""); });

}

// Mouse is over a star
function StarMouse($Event, $Stars) {
    if ($Stars == 0) {
        $("div#rating img#avg,img#trans").removeClass("hidden");
        $("div#rating img.star1").addClass("hidden");
    } else {
        for (var $i = 1; $i <= 5; $i++) {
            if ($i <= $Stars) {
                $("img#s" + $i).addClass("selected");
            } else {
                $("img#s" + $i).removeClass("selected");
            }
        }
        if ($Event) {
            TestOverAvg($Event);
        }
    }
}

// XML is loaded; display the story
function OnStoryXml($Xml) {
    StoryXml = $($Xml);
    ShowStory();
}

// Map mouse events to the stars
function SetStarEvent($i) {
    $("img#s" + $i).hover(function($Event) { StarMouse($Event, $i); },
        function($Event) { StarMouse($Event, 0); }).click(function($Event) {
        StarVote($i); });
}

// Vote for # of stars
function StarVote($NumStars) {
    var $Params = $.deparam.fragment();
    var $Chapter = parseInt($Params["chapter"]) || 1;
    var $Story = $Params["story"];
    $.get($VoteURL = "cgi/vote.php?story=" + $Story + "&chapter=" + $Chapter +
        "&vote=" + $NumStars);
    AvgVote = -$NumStars;
}

// Test whether the mouse is over the average number of stars
function TestOverAvg($Event) {
    var $temp=$("div#rating")[0];
    var $X = $Event.pageX - $temp.offsetLeft - 811;
    var $Y = $Event.pageY - $temp.offsetTop - 128;
    if (($X >= 0) && ($X < 80) && ($Y >= 0) && ($Y < 16)) {
        $("div#rating img#avg,img#trans").addClass("hidden");
        $("div#rating img.star1").removeClass("hidden");
    } else if (AvgVote == undefined) {
    } else if (AvgVote < 0) {
        StarMouse(undefined, -AvgVote);
    } else {
        $("div#rating img#avg,img#trans").removeClass("hidden");
        $("div#rating img.star1").addClass("hidden");
    }
}

// HTML is loaded; load the XML
function OnStoryReady() {
    // Enable hashbang
    $.param.fragment.ajaxCrawlable(true);

    // Give them a tracking cookie so we can keep their votes separate
    var $Track = $.cookie("track") || Math.floor(Math.random() * 1000000);
    $.cookie("track", $Track, { expires: 14 });

    // Track any changes to the URL
    $(window).bind("hashchange", OnHashChange);

    // Start loading the index of stories
    $.get("stories.xml?t=" + Number(new Date()), OnStoryXml);

    // Fix my contact e-mail
    $("a#mail").attr("href", EMAIL);

    // Attach mouse-over & click events to the vote stars
    for (var $i = 1; $i <= 5; $i++) {
        SetStarEvent($i);
        $("div#rating img#avg").hover(TestOverAvg, TestOverAvg);
        $("div#rating img#trans").hover(TestOverAvg, TestOverAvg);
    }
}

// XML is loaded; display the index
function OnIndexXml($Xml) {
    StoryXml = $($Xml);
    ShowIndex();
    StoryXml.find("file").each(CollectKeywords);
    ShowKeywords();
}

// HTML is loaded; load the XML
function OnIndexReady() {
    $.get("stories.xml?t=" + Number(new Date()), OnIndexXml);
    $("a#mail").attr("href", EMAIL);
}

