google.load("jquery", "1.3.2");
google.load("feeds", "1");
google.setOnLoadCallback(function() {

// Process the Planet data and make some content!
let [feedsData, title, columns, schedule, resync, oldDays] = Planet;
document.title = title;

// Parse hidden stuff from hash
let hidden = document.location.hash;
let match;

// Hidden arg to specify number of columns
if (match = hidden.match(/C(\d+)/))
  columns = parseInt(match[1]) || columns;
columns = columns || 1;

// Hidden arg to specify the scheduler
if (match = hidden.match(/S([a-z_]+)/))
  schedule = match[1];
schedule = schedule || "sticky";

// Hidden arg to specify resync number
if (match = hidden.match(/R(\d+)/))
  resync = parseInt(match[1]) || resync;
resync = resync || 5;
// Resync less often with more columns
resync = resync * columns;

// Hidden arg to specify number of days
if (match = hidden.match(/O(\d+)/))
  oldDays = parseInt(match[1]) || oldDays;
oldDays = oldDays || 5;

// Determine how wide things should be
let winWidth = $(window).width() - 15;
let colWidth = Math.floor(winWidth / columns);

let colorCache = {};
/**
 * Pick a background color based on the feed type and name
 *
 * @param type
 *        Type of the feed (main color contributor)
 * @param name
 *        Name of the feed (slight color adjustment)
 */
function getColor(type, name) {
  let key = [type, name].toString();
  if (colorCache[key])
    return colorCache[key];

  let colors = [0, 0, 0];
  // Figure out the main colors based on the type
  for (let i = type.length; --i >= 0; )
    colors[i % 3] += (type.charCodeAt(i) - 65) * 17;

  let sub = [0, 0, 0];
  // Do some slight changes based on the name
  for (let i = name.length; --i >= 0; )
    sub[i % 3] += (name.charCodeAt(i) - 65) * 11;

  // Calculate the new color and bounds check
  for (let i = 0; i < 3; i++)
    colors[i] = Math.abs((colors[i] - (sub[i] % 96)) % 256);

  return colorCache[key] = colors;
}

let scheduleTimer = null;
function runScheduler() {
  if (scheduleTimer != null)
    return;
  scheduleTimer = setTimeout(doSchedule, 100);
}

function doSchedule() {
  scheduleTimer = null;

  // Initialize the heights and last entry of the columns
  let heights = [];
  let lastEntry = [];
  let stickyEntry = [];
  let stickyType = [];
  for (let i = columns; --i >= 0; ) {
    heights.push(3);
    lastEntry.push(null);
    stickyEntry.push([]);
    stickyType.push([]);
  }

  // Keep track of how many entries we've handled so far
  let numHandled = 0;

  // Schedule the entries in DOM order
  $(".entry").each(function() {
    let currEntry = $(this);

    let schedCol = 0;
    let schedHeight = Infinity;

    // Pick which scheduler logic to use (default to early)
    let useCol = function(col) heights[col] < schedHeight;
    switch (schedule) {
      case "sticky": {
        // Find the column that previously showed this feed index
        let stickyCol = stickyEntry.reduce(function(prev, curr, index)
          curr.indexOf(currEntry.data("feedIndex")) != -1 ? index : prev, -1);

        // If we found a match, use it, otherwise just do early
        if (stickyCol != -1)
          useCol = function(col) col == stickyCol;
        break;
      }
      case "sticky_type": {
        // Find the column that previously showed this feed type
        let stickyCol = stickyType.reduce(function(prev, curr, index)
          curr.indexOf(currEntry.data("feedType")) != -1 ? index : prev, -1);

        // If we found a match, use it, otherwise just do early
        if (stickyCol != -1)
          useCol = function(col) col == stickyCol;
        break;
      }
      case "follow": {
        // Find the column with the same feed index
        let lastCol = lastEntry.reduce(function(prev, curr, index) curr &&
          curr.data("feedIndex") == currEntry.data("feedIndex") ? index : prev, -1);

        // If we found a match, use it, otherwise just do early
        if (lastCol != -1)
          useCol = function(col) col == lastCol;
        break;
      }
      case "follow_type": {
        // Find the column with the same feed type
        let lastCol = lastEntry.reduce(function(prev, curr, index) curr &&
          curr.data("feedType") == currEntry.data("feedType") ? index : prev, -1);

        // If we found a match, use it, otherwise just do early
        if (lastCol != -1)
          useCol = function(col) col == lastCol;
        break;
      }
      case "early":
        // Nothing to do -- default logic is to do early
        break;
    }

    // Find which column to use based on the scheduler logic
    for (let i = 0; i < columns; i++) {
      if (useCol(i)) {
        schedHeight = heights[i];
        schedCol = i;
      }
    }

    // Position the entry according to the height/col we found
    $(this).css({
      left: (schedCol * colWidth) + 3 + "px",
      top: schedHeight + "px",
    }).show();

    // Save the height and the last entry for the column
    heights[schedCol] += $(this).height() + 5;
    lastEntry[schedCol] = $(this);
    stickyEntry[schedCol].push($(this).data("feedIndex"));
    stickyType[schedCol].push($(this).data("feedType"));

    // If we've shown a certain number of entries, resync the heights
    if (++numHandled % resync == 0) {
      let maxHeight = Math.max.apply({}, heights) + 20;
      for (let i = heights.length; --i >= 0; )
        heights[i] = maxHeight;
    }
  });
}

// Use each feed's data to make stuff
$.each(feedsData, function(feedIndex) {
  let [type, name, pageUrl, feedUrl, process] = this;
  feedUrl = pageUrl + feedUrl;

  // Fetch the feed and process it if it loads
  let feed = new google.feeds.Feed(feedUrl);
  feed.setNumEntries(50);
  feed.load(function(result) {
    if (result.error)
      return;

    // Process each entry in the feed
    $.each(result.feed.entries, function() {
      // Use the callback to process the entry if provided
      if (process)
        process(this);

      // Don't show stuff older than some days
      let pubDate = new Date(this.publishedDate);
      if (Date.now() - pubDate > oldDays * 24 * 60 * 60 * 1000)
        return;

      // Figure out where to insert the new entry; default at end of body
      let action = "appendTo";
      let target = document.body;
      $(".entry").each(function() {
        // If the entry's publish date is newer than this, insert before
        if (pubDate > $(this).data("pubDate")) {
          action = "insertBefore";
          target = this;
          return false;
        }
      });

      let bgColor = "rgba(" + getColor(type, name) + ", .3)";
      let borderColor = "rgba(" + getColor(type, name) + ", .5)";

      let title = $("<div/>").
        css({
          background: bgColor,
          fontSize: "1em",
          margin: 0,
          padding: "3px 5px",
        }).
        css("-moz-border-radius", "10px").
        append($("<a/>").attr("href", this.link).html(this.title)).
        append(" @ ").
        append($("<a/>").attr("href", pageUrl).text(name)).
        append(" # ").
        append(pubDate.toLocaleString().replace(/\s*\d{4}\s*/, ""));

      let content = $("<div/>");
      if (this.content) {
        content.
          css({
            borderTop: "1px dashed rgba(0, 0, 0, .5)",
            padding: "3px 5px",
          }).
          html(this.content);

        // Unround the corner when there's a content thing below
        title.css("-moz-border-radius-bottomleft", 0).
          css("-moz-border-radius-bottomright", 0);
      }

      let entry = $("<div/>").
        attr("class", "entry").
        css({
          backgroundColor: bgColor,
          border: "solid 1px " + borderColor,
          position: "absolute",
          overflow: "hidden",
          width: colWidth - 5 + "px",
        }).
        css("-moz-border-radius", "10px").
        data("feedIndex", feedIndex).
        data("feedType", type).
        data("pubDate", pubDate).
        append(title).
        append(content);

      // Place the entry where we determined earlier
      entry[action](target).hide();

      // Fix up the schedule with the new entry
      runScheduler();
    });
  });
});

});
