/* javascript for http://leifjohnson.net */

/* Map a time in UTC into the local time zone.
 */
function mapTime(when) {
  var now = new Date();
  return new Date(when.getTime() - now.getTimezoneOffset() * 60000);
};


TIME_SPANS = [["y", 365 * 24 * 60 * 60 * 1000, 1],
              ["mo", 30 * 24 * 60 * 60 * 1000, 2],
              ["w",   7 * 24 * 60 * 60 * 1000, 4],
              ["d",       24 * 60 * 60 * 1000, 3],
              ["h",            60 * 60 * 1000, 2],
              ["m",                 60 * 1000, 2],
              ["s",                      1000, 0]];

/* Convert a Date object into a string representing the amount of time that has
 * passed in the local time zone since then. Returns a human-readable string
 * representing the approximate time delta between then and now.
 *
 * then: A Date in the local time zone.
 */
function timeSince(then) {
  var now = new Date();
  var delta = now.getTime() - then.getTime();
  var ago = [];
  for (var i = 0; i < TIME_SPANS.length; i++) {
    var abbr = TIME_SPANS[i][0];
    var span = TIME_SPANS[i][1];
    var cutoff = TIME_SPANS[i][2];

    if (delta < span)
      continue;

    var quotient = Math.round(delta / span, 0);
    if (quotient < cutoff)
      continue;

    ago.push(quotient + abbr);
    if (ago.length == 1)
      break;

    delta = delta % span;
  }
  return ago.join(", ");
};


TWEET_TIME_RE =
  /^... ([a-zA-Z]{3}) (\d\d) (\d\d):(\d\d):(\d\d) \+0000 (\d{4})$/;

MONTHS = {
  Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5,
  Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11
};

/* Show a single tweet from Twitter. */
var Tweet = new Class({
 initialize: function(tweet) {
   this.tweet = tweet;

   this.createdAt = mapTime(this.parseTime(tweet.created_at));
   this.since = new Element("span", {"class": "time-since"});
   this.html = new Element("div", {"class": "tweet"}).appendText(tweet.text);
   this.since.injectInside(this.html);
   this.refresh();
 },

 parseTime: function(when) {
   var parts = TWEET_TIME_RE.exec(when);

   var then = new Date();
   then.setFullYear(parts[6]);
   then.setMonth(MONTHS[parts[1]]);
   then.setDate(parts[2]);
   then.setHours(parts[3]);
   then.setMinutes(parts[4]);
   then.setSeconds(parts[5]);
   return then;
 },

 refresh: function() {
   this.since.innerHTML = timeSince(this.createdAt) + " ago";
   this.refresh.bind(this).delay(100000);
 }
});


/* Load and navigate through tweets from Twitter. */
var Tweets = new Class({
 initialize: function() {
   this.max = null;
   this.index = null;
   this.tweets = [];

   this.focalTweet = $("focal-tweet");

   this.prevButton = $$("#tweets nav span.prev")[0];
   this.prevButton.addEvent("click", this.prev.bind(this));

   this.nextButton = $$("#tweets nav span.next")[0];
   this.nextButton.addEvent("click", this.next.bind(this));

   this.focalTweet.innerHTML = "<span class=\"tweet quiet\">Loading...</span>";

   new Request.JSON({
     url: "/tweets.json",
     onComplete: this.loaded.bind(this)
   }).get();
 },

 loaded: function(json) {
   this.tweets = json.tweets.map(function(e) { return new Tweet(e); });
   this.max = this.tweets.length - 1;
   this.index = (this.max > -1) ? 0 : null;
   this.refresh();
   $$("#tweets nav")[0].setStyle("display", "block");
 },

 prev: function() {
   if (this.index === null)
     return false;
   if (this.index == this.max)
     return false;
   this.index++;
   this.refresh();
   return false;
 },

 next: function() {
   if (this.index === null)
     return false;
   if (this.index == 0)
     return false;
   this.index--;
   this.refresh();
   return false;
 },

 refresh: function() {
   if (this.index == this.max)
     this.prevButton.addClass("hidden");
   else
     this.prevButton.removeClass("hidden");

   if (this.index == 0)
     this.nextButton.addClass("hidden");
   else
     this.nextButton.removeClass("hidden");

   this.focalTweet.innerHTML = "";
   if (this.index !== null)
     this.focalTweet.grab(this.tweets[this.index].html);
 }
});


/* Show the information sections in a fun way ! */
var Bubble = new Class({
 initialize: function(name) {
   this.header = $("h-" + name);
   this.section = $("s-" + name);
   this.href = "/" + name + "/";
   this.fadeDelay = 250;

   var coords = this.header.getCoordinates();
   this.location = {x: coords.left - 200, y: coords.top - 12};
   this.width = this.header.getSize().x + 25;
   this.bubbleWidth = 0;
   this.left = 187;
   if (name == "about") {
     this.location.x = coords.left - 40;
     this.bubbleWidth = 670;
     this.left = 25;
   }
 },

 deactivate: function(completer) {
   var motion = new Fx.Tween($("bubble"),
       {duration: this.fadeDelay,
        onComplete: function() {
          this.section.setStyle("display", "none");
          $("diorama-overlay").grab(this.section);
          $("bubble-viewer").empty();
          $("bubble").setStyle("width", 396);
          completer();
        }.bind(this)});
   motion.start("opacity", 0);
 },

 activate: function() {
   $("bubble").setPosition(this.location);
   if (this.bubbleWidth > 0) $("bubble").setStyle("width", this.bubbleWidth);
   $("bubble-header").setStyles({width: this.width, left: this.left});
   $("bubble-viewer").grab(this.section);
   this.section.setStyle("display", "block");

   var motion = new Fx.Tween($("bubble"), {duration: this.fadeDelay});
   motion.start("opacity", 1);
 }
});


var Bubbles = new Class({
 initialize: function() {
   this.bubbles = {};

   $$("section").each(function(element) {
     var name = element.id.replace(/^s-/, "");
     var header = $("h-" + name);
     var bubble = new Bubble(name);
     header.addEvent("click", function() { this.activate(name); }.bind(this));
     this.bubbles[name] = bubble;
   }.bind(this));

   this.activeBubble = null;
   this.activate("writing");
 },

 activate: function(name) {
   $("bubble").setStyle("display", "block");
   bubble = this.bubbles[name];
   if (name == this.activeBubble && name != "about") {
     location.href = bubble.href;
   } else if (this.activeBubble === null) {
     bubble.activate();
   } else {
     this.bubbles[this.activeBubble].deactivate(function() { bubble.activate(); });
   }
   this.activeBubble = name;
 }
});


/* Create a diorama-rama ! */
var DioramaRama = new Class({
 initialize: function(initial) {
   this.current = initial;
   this.dioramas = [];
   this.order = {};

   this._hidePrev();
   this._hideNext();

   var index = 0;
   var elements = $$("#diorama-rama .diorama");
   elements.reverse();
   elements.each(function(element) {
     var diorama = new Diorama(element);
     this.dioramas.push(diorama);
     this.order[diorama.id] = index++;
     diorama.hideLeft();
     if (diorama.id == initial) diorama.show();
   }.bind(this));

   if (index > 0) {
     this._show(index-1);
     if (this.order[this.current] > 0)
       this._showPrev();
     if (this.order[this.current] < this.dioramas.length - 1)
       this._showNext();

     var buttons = $$("nav SPAN");
     buttons[0].addEvent("click", this._prev.bind(this));
     buttons[1].addEvent("click", this._next.bind(this));
   }

   var x = window.getSize().x;
   $$(".cloud").each(function (cloud) {
     cloud.setStyle("left", Math.round(x * Math.random()));
     cloud.setProperty("speed", Math.round(4 * Math.random()));
     this._moveCloud(cloud);
   }.bind(this));

   this.resize();
 },

 resize: function() {
   $("diorama-rama").setStyle("width", window.getWidth());
   this.dioramas.each(function(diorama) { diorama.reposition(); });
 },

 _moveCloud: function(cloud) {
   var x = cloud.getStyle("left").toInt();
   var dx = parseInt(cloud.getProperty("speed")) * Math.random();
   cloud.setStyle("left", x + Math.round(dx));
   if (x > window.getSize().x + 10) {
     cloud.setStyle("left", -cloud.getSize().x - Math.round(10 * Math.random()));
     cloud.setStyle("top", -10 + Math.round(100 * Math.random()));
     cloud.setProperty("speed", Math.round(4 * Math.random()));
   }
   setTimeout(function() { this._moveCloud(cloud); }.bind(this),
              4000 + Math.round(2000 * Math.random()));
 },

 _moveCelestials: function(then) {
   var there = this._getSunPosition(then);
   var motion = new Fx.Morph("sun", {duration: 2000, transition: "elastic:in:out"});
   motion.start({left: there[0], top: there[1]});
 },

 _getSunPosition: function(then) {
   // We want the sun to be at its starting point (theta == 0, bottom-left of
   // the visible screen area) around 6am (sunrise). During the day the sun
   // transits up to the top-center and back down to the bottom-right ; then at
   // night the sun transits underneath the visible screen area to return to the
   // bottom-left by 6am. Here we calculate the sin and cos of the angle that
   // the sun has transited through and use those proportions to move the sun
   // relative to the bottom-center of the window.
   var transit = (Math.round(then - 86400 / 4) % 86400) / 86400.0;
   var theta = Math.PI * 2 * transit;
   var size = window.getSize();
   var rx = size.x / 2;
   var ry = size.y;
   return [rx * (1 - Math.cos(theta)), ry * (1 - Math.sin(theta))];
 },

 _nav: function(which, visible) {
   var styles = {visibility: "hidden", cursor: "normal"};
   if (visible) styles = {visibility: "visible", cursor: "pointer"};
   /*$$("#nav SPAN")[which].setStyles(styles);*/
 },

 _showPrev: function() { this._nav(0, true); },
 _showNext: function() { this._nav(1, true); },
 _hidePrev: function() { this._nav(0, false); },
 _hideNext: function() { this._nav(1, false); },

 _prev: function() {
   var index = this.order[this.current];
   if (index <= 0)
     return;
   this._show(index - 1);
   this.dioramas[index].hideRight();
   if (index - 1 == 0)
     this._hidePrev();
   if (index == this.dioramas.length - 1)
     this._showNext();
 },

 _next: function() {
   var index = this.order[this.current];
   if (index >= this.dioramas.length - 1)
     return;
   this._show(index + 1);
   this.dioramas[index].hideLeft();
   if (index == 0)
     this._showPrev();
   if (index + 1 == this.dioramas.length - 1)
     this._hideNext();
 },

 _show: function(index) {
   var diorama = this.dioramas[index];
   this.current = diorama.id;
   diorama.show();
   var then = diorama.getTimestamp();
   this._moveCelestials(then);
   /*$("stratosphere").setStyle("color", SKY_FG_COLORS[Math.round(then / 3600, 0) % 24]);*/
 }
});


var DIORAMA_DATA = {
  idaho: {
    tz: -8,
    foo: 0
  },
  cackalack: {
    tz: -5,
    foo: 0
  },
  france: {
    tz: 0,
    foo: 0
  },
  austria: {
    tz: 0,
    foo: 0
  },
  california: {
    tz: -8,
    foo: 0
  },
  texas: {
    tz: -6,
    foo: 0
  }
};


SKY_COLORS = [
  "#002", "#002", "#002", "#004", "#006", "#007", //  0 -  5
  "#009", "#ebf", "#fdd", "#bdf", "#bdf", "#bdf", //  6 - 11
  "#bdf", "#bdf", "#bdf", "#bdf", "#bdf", "#bdf", // 12 - 17
  "#f96", "#309", "#009", "#006", "#004", "#002"  // 18 - 23
];

SKY_FG_COLORS = [
  "#ffb", "#ffb", "#ffb", "#ffb", "#ffb", "#ffb", //  0 -  5
  "#ffb", "#000", "#000", "#000", "#000", "#000", //  6 - 11
  "#000", "#000", "#000", "#000", "#000", "#000", // 12 - 17
  "#000", "#ffb", "#ffb", "#ffb", "#ffb", "#ffb"  // 18 - 23
];


var Diorama = new Class({
 initialize: function(element) {
   this.id = element.id;
   this.html = element;
   this.position = null;

   this.sky = element.getElementsByClassName("sky")[0];
   this.updateSky();
 },

 _raise: function() { this.html.setStyle("z-index", 1); },
 _lower: function() { this.html.setStyle("z-index", 0); },
 _width: function() { return Math.round(1.1 * window.getWidth(), 0); },

 _options: function(completer) {
   var options = {duration: 1200, transition: "back:out"};
   if (completer) options["onComplete"] = completer.bind(this);
   return options;
 },

 _shift: function(position) {
   this.position = position;
   var completer = this._raise;
   var width = this._width();
   if (position == "center") {
     completer = this._lower;
     width = 0;
   } else if (position == "left") {
     width = -width;
   }
   var motion = new Fx.Tween(this.html, this._options(completer));
   motion.start("left", width);
 },

 hideLeft: function() { this._shift("left"); },
 hideRight: function() { this._shift("right"); },
 show: function() { this._shift("center"); },

 reposition: function() {
   if (this.position == "center") {
     this.updateSky();
   } else {
     var factor = (this.position == "left") ? -1.1 : 1.1;
     this.html.setStyle("left", factor * window.getWidth());
   }
 },

 getTimestamp: function() {
   return $time() / 1000 + 3600 * DIORAMA_DATA[this.id].tz;
 },

 updateSky: function() {
   /*this.sky.setStyle("background", SKY_COLORS[Math.round(this.getTimestamp() / 3600, 0) % 24]);*/
 }
});
