// picPopper 
// Copyright (C) 2008, Bjarni R. Einarsson, http://bre.klaki.net/
// All rights reserved.

var picPopper = {
  version: 'picPopper 1.0.4',
  homepage: 'http://bre.klaki.net/programs/picpopper/',

///////////////////////////////////////////////////////////////////////////////
//
// This is an easy-to-use Javascript tool for turning a static page of 
// thumbnails linking to images, into a user-friendly Web 2.0 style photo 
// album.
//
// See http://bre.klaki.net/programs/picpopper/ for details.
//
// Here are some potential TODOs, patches are welcome:
//   - Make enlarged images linkable via #anchors
//   - Fancy zooming/panning
//   - Ajaxy comments
//   - Ajaxy tagging of people
//   - More docs/samples, maybe some nice stylesheets for people to copy
//
///////////////////////////////////////////////////////////////////////////////
// License (trivially reworded BSD license):
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//    * The names of its contributors may not be used to endorse or promote
//      products derived from this software without specific prior written 
//      permission.
//
// THIS SOFTWARE IS PROVIDED BY BJARNI R. EINARSSON ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL BJARNI R. EINARSSON BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////////

  images: [],
  offset: 0,
  loading: 0,
  last: -1,
  bound: false,
  active: false,
  last_scroll_top: 0,
  default_css: 'http://bre.klaki.net/programs/picpopper/default.css',

  preloadImg: $('<img/>'),   // This is where we preload images
  bufferImg: $('<img/>'),    // This is where we buffer images before displaying
  popupShader: $('<div/>'),  // I use this to darken the page
  popupDivWrap: $('<div/>'), // This is the actual visible UI
  popupDiv: $('<div/>'),     // A helper div
  popupImg: $('<img/>'),     // This is image within the popup
  popupFull: $('<a/>'),      // This is image within the popup
  popupText: $('<p/>'),      // Any text accompanying the image
  loadingDiv: $('<div/>'),   // The little "loading" flag

  preload: function(index) {
    if (index < this.images.length) 
      this.preloadImg.attr('src', this.images[index].href);
  },

  loaded: function() {
    if (!this.active) return;
    this.loadingDiv.css({ 'display': 'none' });
    this.showPopup();

    var index = this.offset = this.loading;
    var full_x = this.bufferImg.width();
    var full_y = this.bufferImg.height();
    var win_y = 0.98*$(window).height();
    var img_x = Math.min(full_x, this.popupDivWrap.width()-12);
    var img_y = Math.min(full_y, win_y-60);
    var ratio = Math.min(img_x/full_x, img_y/full_y);
    var pad = Math.max(0, (win_y - 60 - img_y)/2);

    var title = this.images[index].title;
    title = title.replace(/<!--.*?-->/g,'')
                 .replace(/<(p|br|div)[^>]+>/g, ' ')
                 .replace(/<[^>]+>/g, ''); 

    var caption = this.images[index].innerHTML;
    caption = caption.replace(/<!--.*?-->/g,'')
                     .replace(/<(p|br|div)[^>]+>/g, ' ')
                     .replace(/<[^>]+>/g, ''); 

    this.popupText.html(caption || title);
    this.popupImg.attr('src', this.images[index].href)
                 .attr('title', title || caption)
                 .css({ 'margin-top': pad, 
                        'margin-bottom': pad, 
                        'width': ratio*full_x+'px', 
                        'height': ratio*full_y+'px' });
    this.popupFull.attr('href', this.images[index].href);
    this.preload(index + 1); 
  },

  setFixedTop: function(obj, top) {
    if (/MSIE /.test(navigator.userAgent)) {
      obj.msieTop = top;
      return obj.css({ 
        'position': 'absolute',
        'top': (document.body.scrollTop||document.documentElement.scrollTop) + top
      });
    } else {
      return obj.css({ 'position': 'fixed', 'top': top });
    }
  },

  msieFixElements: function() {
    var cur_top = (document.body.scrollTop||document.documentElement.scrollTop);
    if (cur_top != picPopper.last_scroll_top) {
      picPopper.setFixedTop(picPopper.loadingDiv, picPopper.loadingDiv.msieTop); 
      picPopper.setFixedTop(picPopper.popupShader, picPopper.popupShader.msieTop); 
      picPopper.setFixedTop(picPopper.popupDivWrap, picPopper.popupDivWrap.msieTop); 
      picPopper.last_scroll_top = cur_top;
    }
  },

  showImage: function(index) {
    this.active = true;
    this.loading = index;
    if (this.last < 0) {
      this.last = index-1;
      if (this.last < 0) this.last += this.images.length;
    }
    var loading_ofs = $(window).height()/2 - 5;
    this.setFixedTop(this.popupShader, 0).css({ 'display': 'block' });
    this.setFixedTop(this.loadingDiv, loading_ofs).css({ 'display': 'block' });
    this.bufferImg.attr('src', this.images[index]);
  },

  back: function(unused) {
    var index = picPopper.offset - 1;
    if (index < 0) index = picPopper.images.length-1; 
    picPopper.showImage(index);
    return false;
  },

  next: function(unused) {
    var index = picPopper.offset + 1;
    if (index >= picPopper.images.length) index = 0; 
    picPopper.showImage(index);
    return false;
  },

  showPopup: function() {
    picPopper.popupDivWrap.css({ 'display': 'block' });
    picPopper.scalePopup();
    if (!picPopper.bound) {
      $(document).bind('keydown', 'q', picPopper.hidePopup)
                 .bind('keydown', 'esc', picPopper.hidePopup)
                 .bind('keydown', 'left', picPopper.back)
                 .bind('keydown', 'right', picPopper.next)
                 .bind('keydown', 'up', picPopper.back)
                 .bind('keydown', 'down', picPopper.next);
      picPopper.bound = true;
    }
    return false;
  },

  hidePopup: function() {
    picPopper.popupShader.css({ 'display': 'none' });
    picPopper.popupDivWrap.css({ 'display': 'none' });
    picPopper.loadingDiv.css({ 'display': 'none' });
    picPopper.active = false;
    if (picPopper.bound) {
      $(document).unbind('keydown', 'q', picPopper.hidePopup)
                 .unbind('keydown', 'esc', picPopper.hidePopup)
                 .unbind('keydown', 'left', picPopper.back)
                 .unbind('keydown', 'right', picPopper.next)
                 .unbind('keydown', 'up', picPopper.back)
                 .unbind('keydown', 'down', picPopper.next);
      picPopper.bound = false;
    }
    this.last = -1;
    return false;
  },

  scalePopup: function() {
    var width = Math.min(0.98*$(window).width(), $(window).height()*1.35);
    var left = ($(window).width()-width)/2;
    this.setFixedTop(this.popupDivWrap, 0.01*$(window).height())
                .css({ 'left': left,
                       'width': width+'px' });
  },

  prepare: function(pattern) {
    if (!pattern) pattern = /^[^#]+\.(jpe?g|gif|png)(#|$)/i;

    // Load the hotkeys plugin, so we can set hotkeys
    if (!window.hotkeys || !jQuery.fn.__bind__)
      $.getScript("http://js-hotkeys.googlecode.com/files/jquery.hotkeys-0.7.8-packed.js");

    // Configure scratch div and imgs
    this.loadingDiv.attr('id', 'picPopperLoading')
                   .html('Loading...')
                   .css({ 'top': $(window).height()/2 - 5,
                          'left': $(window).width()/2 - 50,
                          'position': 'fixed',
                          'display': 'block',
                          'margin-left': 'auto',
                          'margin-right': 'auto',
                          'text-align': 'center',
                          'width': '100px',
                          'z-index': 11 });

    this.popupShader.attr('id', 'picPopperShader')
                    .css({ 'display': 'none', 
                           'position': 'fixed',
                           'top': 0, 'left': 0,
                           'width': '100%',
                           'height': '100%',
                           'z-index': 9 })
                    .click(picPopper.hidePopup);

    this.popupText.attr('id', 'picPopperCaption');
    this.popupDivWrap.append(this.popupDiv)
                     .attr('id', 'picPopperPopupWrapper')
                     .css({ 'display': 'none',
                            'text-align': 'center',
                            'z-index': 10 });
    this.popupDiv.attr('id', 'picPopperPopup')
                 .css({ 'position': 'relative',
                        'text-align': 'center' })
                 .append($('<p/>').html('back')
                                  .attr('title', 'Previous picture')
                                  .attr('id', 'picPopperBack')
                                  .click(picPopper.back))
                 .append($('<p/>').html('X')
                                  .attr('title', 'Close')
                                  .attr('id', 'picPopperClose')
                                  .click(picPopper.hidePopup))
                 .append($('<p/>').html('next')
                                  .attr('title', 'Next picture')
                                  .attr('id', 'picPopperNext')
                                  .click(picPopper.next))
                 .append(this.popupImg)
                 .append(this.popupText)
                 .append(this.popupFull.html('full size')
                                       .attr('id', 'picPopperFull')
                                       .attr('title', 'View/download full image'))
                 .append($('<a/>').html(this.version)
                                  .attr('id', 'picPopperBlurb')
                                  .attr('title', 'Get your own picPopper!')
                                  .attr('href', picPopper.homepage));

    this.popupImg.attr('id', 'picPopperImg')
                 .click(function(){ 
                   if (picPopper.offset != PicPopper.last) {
                     picPopper.next();
                   } else {
                     picPopper.hidePopup();
                   } 
                 });

    $('body').append(this.popupShader)
             .append(this.popupDivWrap)
             .append(this.bufferImg)
             .append(this.loadingDiv)
             .append(this.preloadImg);

    this.popupDivWrap.css({ 'display': 'none' });
    this.loadingDiv.css({ 'display': 'none' });
    this.preloadImg.hide();
    this.bufferImg.hide().load(function() { picPopper.loaded(); });

    // Scan DOM tree for hrefs matching the pattern, for each:
    //   - add an onClick handler invoking show() for that image
    //   - add to image index
    $('a').each(function(){
                  if (pattern.test(this.href)) {
                    var len = picPopper.images.length;
                    $(this).click(function(){
                                    picPopper.showImage(len);
                                    return false
                                  });
                    picPopper.images.push(this);
                  }
                });

    // Start loading the first image, just to make things snappy
    this.preload(0); 
  },
  
  loadDefaultCss: function() {
    var css_node = document.createElement('link');
    css_node.type = 'text/css';
    css_node.rel = 'stylesheet';
    css_node.media = 'screen';
    css_node.href = this.default_css;
    document.getElementsByTagName('head')[0].appendChild(css_node);
  }
}

// Backwards compatibility
var PicPopper = picPopper;

// Auto-load for use as a bookmarklet
if (document.body && document.body.picPopperAutoLoad) {
  picPopper.loadDefaultCss();
  picPopper.prepare();
  alert('Enabled picPopper for this page.');
}
