(function($) {
  // If the element(s) identified by otherSelector are longer than the element
  // (and it's positioned absolutely)
  // the element will scroll as slow as needed to align to the bottom of the
  // Kind of similar to parallax effect, but vertical
  $.fn.alignScrollTo = function(otherSelector) {
    var other = $(otherSelector);
    
    return this.each(function() {
      var $elem = $(this);
      var initialOffset = $elem.offset().top;
      if(shouldAlign()) setPosition();
      
      // Enable jumping to local anchors within a block that scrolls slower than normal
      $elem.find('a[href^="#"]').live('click', scrollToLocalAnchors);
      $(window).scroll(handleScrollEvent);
      $(window).resize(removePositionWhenTooNarrow);
      
      function shouldAlign() {
        var isSmallerThanOther = $elem.height() < other.height();
        var isPositioned = $elem.css('position') !== 'static';
        return isPositioned && isSmallerThanOther;
      }
      
      function setPosition() {
        $elem.css({
          position: 'fixed',
          top: initialOffset
        });
      };
      
      function handleScrollEvent() {
        if(shouldAlign()) {
          setPosition();
          var scroll = $(window).scrollTop();
          $elem.css('top', initialOffset + scroll/factor()*-1+'px');
        }
      }
      
      function factor() {return other.height() / $elem.height();}
      
      function scrollToLocalAnchors(e) {
        var anchor = $(this).attr('href');
        var initialTargetOffset = $(anchor).offset().top;
        var localFactor = factor() > 1 ? factor() : 1;
        $('html, body').animate({scrollTop: initialTargetOffset * localFactor});
        location.hash = anchor;
        e.preventDefault();
      }
      
      function removePositionWhenTooNarrow () {
        // Have to calculate right edge every time since resizing might have
        // activated media queries that modify the width
        var rightEdge = $elem.offset().left + $elem.width();
        if($(window).width() < rightEdge) {
          // HACK: We reset the entire dynamic style property. This way we lose
          // the top pos in some cases, but that is corrected once the user scrolls
          // Ideally, we'd only remove the `position` value from the @style attr
          // but there's no easy way to do that. Can't simply set “position”
          // to something since that would override rules from the stylesheet
          $elem.attr('style', '');
        }
      }
    });
  };
  
  // This will allow a series of images to be skimmed through with the mouse
  // It will hide all images but the first
  // Expects a container with images as children or descendants (e.g. a ul/ol)
  $.fn.skimImages = function() {
    return this.each(function() {
      var container = $(this);
      container.children().not(':first-child').hide();
      var handler = function(e) {
        // Deal multi-touch capable touch events (e.g. on iOS) contain a 
        // TouchList with several events, we only take the first one
        e = e.clientX ? e : e.originalEvent.touches.item(0);
        var leftEdge = container.offset().left;
        // on DOMready the image might not be loaded yet and the dimensions
        // might be wrong // so we ask for widht on every move/touch which
        // is expensive
        var threshold = container.width() / container.children().length;
        var pos = e.clientX - leftEdge;
        // touch events are fired even when finger leaves area of element
        // (and it isn't lifted)
        if(pos < container.width()) {
          $('#pos').html(pos);
          var index = Math.floor(pos/threshold);
          container.children().hide().eq(index).show();
        }
      };
      
      container.bind('touchmove', handler);
      container.mousemove(handler);
    });
  };
  
  // Wraps elements in a div with next sibling
  // useful e.g. for blockquotes and citations (p elements)
  $.fn.wrapWithNext = function() {
    return this.each(function() {
      $(this).add($(this).next()).wrapAll('<div></div>');
    });
  };
  
  $.fn.scrollIntoView = function() {
    return this.each(function() {
      var newPosition = $(this).position().top - parseInt($(this).css('margin-bottom'), 10);
      $('html, body').animate({scrollTop: newPosition});
    });
  };
  
  $.fn.expand = function() {
    return this.each(function() {
      $(this).siblings().removeClass('expanded');
      $(this).addClass('expanded');
    });
  };
  
  $.fn.expandable = function() {
    var images = this;
    images.css('cursor', 'pointer');
    return images.live('click', function() {
      images.not($(this)).each(function() {
        $(this).removeClass('expanded');
      });
      $(this).toggleClass('expanded');
      // TODO: only scroll as far as needed
      // and only if the bottom edge of image would otherwise be hidden
      if($(this).hasClass('expanded')) {
        $(this).data('oldscrollTop', $(window).scrollTop());
        $(window).scrollTop($(this).offset().top);
      } else {
        var oldScrollTop = $(this).data('oldscrollTop');
        $(window).scrollTop(oldScrollTop);
        $(this).data('oldscrollTop', undefined);
      }
    });
  };
  
  $.setTitle = function(localPart, separator) {
    separator = separator || ' – ';//space-endash-space as default
    var globalPart = document.title.split(separator)[1];
    document.title = [localPart, globalPart].join(separator);
  };
  
  // Adds a class of “no-data-uri” to the html element unless
  // http://weston.ruter.net/2009/05/07/detecting-support-for-data-uris/
  $.markDataURISupport = function() {
    var data = new Image();
    data.onload = data.onerror = function(){
      if(this.width != 1 || this.height != 1)
        document.documentElement.className += ' no-data-uri';
    };
    // Tiniest GIF ever: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
    data.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
  };
}(jQuery));

$(function() {
  $.markDataURISupport();
  
  $('body.project #content>img, body.project #content>.group, #content.news img').expandable();
  
  $('body aside').alignScrollTo('#content');
  
  $('ol.group').skimImages();
  
  var uppercase_selectors = 'h1,h2,h3,h5,h6, nav a, blockquote + p, aside .tags, .article h4, .article figcaption p';
  $(uppercase_selectors).each(function() {
    var markup = $(this).html().replace('dRMM', '<em class="logo">$&</em>');
    $(this).html(markup);
  });
  
  $('.person.expanded').scrollIntoView();
  
  // Progressive enhancement on browsers that support HTML5 History API
  if(window.history.pushState) {
    // Add the state property to jQuery's event object so we can use it in
    // $(window).bind('popstate')
    $.event.props.push('state');
    
    if($('.person.expanded').length) {
      $('body').data('initialPersonId', $('.person.expanded').attr('id'));
    }
    $('body').data('initialTitle', document.title);
    
    $('.person header a').live('click', function(e) {
      var selected = $(this).parents('article'),
          href = $(this).attr('href'),
          title = $(this).find('h2').text();
      var state = {
        currentPersonId: selected.attr('id'),
        title: title
      };
      history.pushState(state, title, href);
      selected.expand();
      $.setTitle(title);
      // Track pageview in Google Analytics
      if (window._gaq ) _gaq.push(['_trackPageview']);
      e.preventDefault();
    });
    
    $(window).bind('popstate', function(e) {
      if(e.state && e.state.currentPersonId) {
        $('#'+e.state.currentPersonId).expand();
      } else if($('body').data('initialPersonId')) {
        $('#'+$('body').data('initialPersonId')).expand();
      } else {
        $('.expanded').removeClass('expanded');
      }
      if(e.state && e.state.title) {
        $.setTitle(e.state.title);
      } else if($('body').data('initialTitle')) {
        document.title = $('body').data('initialTitle');
      }
    });
  }//if pushstate
  
});//document.ready

function loadMasonry() {
  if($.fn.masonry) {
    // apply jquery.masonry and, in a callback fired when layout is complete,
    // add a class of “lowest” to the elements whose bottom edges are lowest
    $('.projects #content, .awards #content, .news #content>section, body.testimonials article>section')
      .masonry({}, function() {
        // +this+ is a jQuery collection of the children of the masoned element
        var columnCount = parseInt(this.parent().width() / this.outerWidth(), 10);
        var bottomEdge = function(el) {return parseInt($(el).offset().top + $(el).height(), 10);};
        // get the +columnCount+ number of items closest to the bottom edge of the container
        this
         // we're sorting integers so we can substract them to get a comparator result of n>0-n
          .sort(function(a, b) { return bottomEdge(a) - bottomEdge(b); })
          .slice(columnCount * -1).addClass('lowest');
      });
  }
}

$(document).ready(function() {
  $('body.testimonials').find('blockquote').wrapWithNext().end();
  loadMasonry();
});

// HACK: despite explicitly specifying the dimensions of all inline images
// and inlining fonts in css so they're loaded (and their metrics are known)
// onDOMReady, the calculations are still a bit off on a few pages.
// recommeded workaround according to http://desandro.com/demo/masonry/docs/
// is to load onLoad but that is impractical as we're loading a lot of images
// so we simply load masonry a second time and accept slight re-adjustments
$(window).load(function() {  loadMasonry(); });

