/**
 * jQuery Lightbox Plugin (balupton edition) - Lightboxes for jQuery
 * Copyright (C) 2008 Benjamin Arthur Lupton
 * http://jquery.com/plugins/project/jquerylightbox_bal
 *
 * This file is part of jQuery Lightbox (balupton edition).
 * 
 * jQuery Lightbox (balupton edition) is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * jQuery Lightbox (balupton edition) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with jQuery Lightbox (balupton edition).  If not, see <http://www.gnu.org/licenses/>.
 *
 * @name jquery_lightbox: jquery.lightbox.js
 * @package jQuery Lightbox Plugin (balupton edition)
 * @version 1.3.7-final
 * @date April 25, 2009
 * @category jQuery plugin
 * @author Benjamin "balupton" Lupton {@link http://www.balupton.com}
 * @copyright (c) 2008 Benjamin Arthur Lupton {@link http://www.balupton.com}
 * @license GNU Affero General Public License - {@link http://www.gnu.org/licenses/agpl.html}
 * @example Visit {@link http://jquery.com/plugins/project/jquerylightbox_bal} for more information.
 */

// Start of our jQuery Plugin
(function($)
{ // Create our Plugin function, with $ as the argument (we pass the jQuery object over later)
  // More info: http://docs.jquery.com/Plugins/Authoring#Custom_Alias
  
  // Debug
  if ( typeof $.log === 'undefined' ) {
    if ( !$.browser.safari && typeof window.console !== 'undefined' && typeof window.console.log === 'function' )
    { // Use window.console
      $.log = function(){
        var args = [];
          for(var i = 0; i < arguments.length; i++) {
              args.push(arguments[i]);
          }
          window.console.log.apply(window.console, args);
      }
      $.console = {
        log:  $.log,
        debug:  window.console.debug  || $.log,
        warn: window.console.warn   || $.log,
        error:  window.console.error  || $.log,
        trace:  window.console.trace  || $.log
      }
    }
    else
    { // Don't use anything
      $.log = function ( ) { };
      $.console = {
        log:  $.log,
        debug:  $.log,
        warn: $.log,
        error:  alert,
        trace:  $.log
      };
    }
  }
  
  // Pre-Req
  $.params_to_json = $.params_to_json || function ( params )
  { // Turns a params string or url into an array of params
    // Adjust
    params = String(params);
    // Remove url if need be
    params = params.substring(params.indexOf('?')+1);
    // params = params.substring(params.indexOf('#')+1);
    // Change + to %20, the %20 is fixed up later with the decode
    params = params.replace(/\+/g, '%20');
    // Do we have JSON string
    if ( params.substring(0,1) === '{' && params.substring(params.length-1) === '}' )
    { // We have a JSON string
      return eval(decodeURIComponent(params));
    }
    // We have a params string
    params = params.split(/\&|\&amp\;/);
    var json = {};
    // We have params
    for ( var i = 0, n = params.length; i < n; ++i )
    {
      // Adjust
      var param = params[i] || null;
      if ( param === null ) { continue; }
      param = param.split('=');
      if ( param === null ) { continue; }
      // ^ We now have "var=blah" into ["var","blah"]
      
      // Get
      var key = param[0] || null;
      if ( key === null ) { continue; }
      if ( typeof param[1] === 'undefined' ) { continue; }
      var value = param[1];
      // ^ We now have the parts
      
      // Fix
      key = decodeURIComponent(key);
      value = decodeURIComponent(value);
      try {
          // value can be converted
          value = eval(value);
      } catch ( e ) {
          // value is a normal string
      }
      
      // Set
      // console.log({'key':key,'value':value}, split);
      var keys = key.split('.');
      if ( keys.length === 1 )
      { // Simple
        json[key] = value;
      }
      else
      { // Advanced
        var path = '';
        for ( ii in keys )
        { //
          key = keys[ii];
          path += '.'+key;
          eval('json'+path+' = json'+path+' || {}');
        }
        eval('json'+path+' = value');
      }
      // ^ We now have the parts added to your JSON object
    }
    return json;
  };
  
  // Declare our class
  $.LightboxClass = function ( )
  { // This is the handler for our constructor
    this.construct();
  };

  // Extend jQuery elements for Lightbox
  $.fn.lightbox = function ( options )
  { // Init a el for Lightbox
    // Eg. $('#gallery a').lightbox();
    
    // If need be: Instantiate $.LightboxClass to $.Lightbox
    $.Lightbox = $.Lightbox || new $.LightboxClass();
    
    // Handle IE6 appropriatly
    if ( $.Lightbox.ie6 && !$.Lightbox.ie6_support )
    { // We are IE6 and we want to ignore
      return this; // chain
    }
    
    // Establish options
    options = $.extend({start:false,events:true} /* default options */, options);
    
    // Get group
    var group = $(this);
    
    // Events?
    if ( options.events )
    { // Add events
      $(group).unbind('click').click(function(){
        // Get obj
        var obj = $(this);
        // Get rel
        // var rel = $(obj).attr('rel');
        // Init group
        if ( !$.Lightbox.init($(obj)[0], group) )
        { return false; }
        // Display lightbox
        if ( !$.Lightbox.start() )
        { return false; }
        // Cancel href
        return false;
      });
      // Add style
      $(group).addClass('lightbox-enabled');
    }
    
    // Start?
    if ( options.start )
    { // Start
      // Get obj
      var obj = $(this);
      // Get rel
      // var rel = $(obj).attr('rel');
      // Init group
      if ( !$.Lightbox.init($(obj)[0], group) )
      { return this;  }
      // Display lightbox
      if ( !$.Lightbox.start() )
      { return this;  }
    }
    
    // And chain
    return this;
  };
  
  // Define our class
  $.extend($.LightboxClass.prototype,
  { // Our LightboxClass definition
    
    // -----------------
    // Everyting to do with images
    
    images: {
      
      // -----------------
      // Variables
      
      // Our array of images
      list:[], /* [ {
        src: 'url to image',
        link: 'a link to a page',
        title: 'title of the image',
        name: 'name of the image',
        description: 'description of the image'
      } ], */
      
      // The current active image
      image: false,
      
      // -----------------
      // Functions
      
      prev: function ( image )
      { // Get previous image
        
        // Get previous from current?
        if ( typeof image === 'undefined' )
        { image = this.active();
          if ( !image ) { return image; }
        }
        
        // Is there a previous?
        if ( this.first(image) )
        { return false; }
        
        // Get the previous
        return this.get(image.index-1);
      },
      
      next: function ( image )
      { // Get next image
        
        // Get next from current?
        if ( typeof image === 'undefined' )
        { image = this.active();
          if ( !image ) { return image; }
        }
        
        // Is there a next?
        if ( this.last(image) )
        { return false; }
        
        // Get the next
        return this.get(image.index+1);
      },
      
      first: function ( image )
      { //
        // Get the first image?
        if ( typeof image === 'undefined' )
        { return this.get(0); }
        
        // Are we the first?
        return image.index === 0;
      },
      
      last: function ( image )
      { //
        // Get the last image?
        if ( typeof image === 'undefined' )
        { return this.get(this.size()-1); }
        
        // Are we the last?
        return image.index === this.size()-1;
      },
    
      single: function ( )
      { // Are we only one
        return this.size() === 1;
      },
      
      size: function ( )
      { // How many images do we have
        return this.list.length;
      },
      
      empty: function ( )
      { // Are we empty
        return this.size() === 0;
      },
      
      clear: function ( )
      { // Clear image arrray
        this.list = [];
        this.image = false;
      },
    
      active: function ( image )
      { // Set or get the active image
        // Use false to reset
        
        // Get the active image?
        if ( typeof image === 'undefined' )
        { return this.image;  }
        
        // Set the ative image
        if ( image !== false )
        { // Make sure image exists
          image = this.get(image);
          if ( !image )
          { // Error
            return image;
          }
        }
        
        // Set the active image
        this.image = image;
        return true;
      },
    
      add: function ( obj )
      {
        // Do we need to recurse?
        if ( obj[0] )
        { // We have a lot of images
          for ( var i = 0; i < obj.length; i++ )
          { this.add(obj[i]); }
          return true;
        }
        
        // Default image
        
        // Try and create a image
        var image = this.create(obj);
        if ( !image ) { return image; }
        
        // Set image index
        image.index = this.size();
        
        // Push image
        this.list.push(image);
        
        // Success
        return true;
      },
      
      create: function ( obj )
      { // Create image
        
        // Define
        var image = { // default
          src:  '',
          title:  'Untitled',
          description:  '',
          name: '',
          index:  -1,
          color:  null,
          width:  null,
          height: null,
          image:  true
        };
        
        // Create
        if ( obj.image )
        { // Already a image, so copy over values
          image.src = obj.src || image.src;
          image.title = obj.title || image.title;
          image.description = obj.description || image.description;
          image.name = obj.name || image.name;
          image.color = obj.color || image.color;
          image.width = obj.width || image.width;
          image.height = obj.height || image.height;
          image.index = obj.index || image.index;
        }
        else if ( obj.tagName )
        { // We are an element
          obj = $(obj);
          if ( obj.attr('src') || obj.attr('href') )
          {
            image.src = obj.attr('src') || obj.attr('href');
            image.title = obj.attr('title') || obj.attr('alt') || image.title;
            image.name = obj.attr('name') || '';
            image.color = obj.css('backgroundColor');
            // Extract description from title
            var s = image.title.indexOf(': ');
            if ( s > 0 )
            { // Description exists
              image.description = image.title.substring(s+2) || image.description;
              image.title = image.title.substring(0,s) || image.title;
            }
          }
          else
          { // Unsupported element
            image = false;
          }
        }
        else
        { // Unknown
          image = false;
        }
        
        if ( !image )
        { // Error
          $.console.error('We dont know what we have:', obj);
          return false;
        }
        
        // Success
        return image;
      },
      
      get: function ( image )
      { // Get the active, or specified image
        
        // Establish image
        if ( typeof image === 'undefined' || image === null )
        { // Get the active image
          return this.active();
        }
        else
        if ( typeof image === 'number' )
        { // We have a index
          
          // Get image
          image = this.list[image] || false;
        }
        else
        { // Create
          image = this.create(image);
          if ( !image ) { return false; }
          
          // Find
          var f = false;
          for ( var i = 0; i < this.size(); i++ )
          {
            var c = this.list[i];
            if ( c.src === image.src && c.title === image.title && c.description === image.description )
            { f = c;  }
          }
          
          // Found?
          image = f;
        }
        
        // Determine image
        if ( !image )
        { // Image doesn't exist
          $.console.error('The desired image does not exist: ', image, this.list);
          return false;
        }
        
        // Return image
        return image;
      },
      
      debug: function ( )
      {
        return $.Lightbox.debug(arguments);
      }
      
    },
    
    // -----------------
    // Variables
    
    constructed:    false,
    compressed:     null,
    
    // -----------------
    // Options
    
    src:        null,   // the source location of our js file
    baseurl:      null,
    
    files: {
      compressed: {
        js: {
          lightbox: 'js/jquery.lightbox.min.js',
          colorBlend: 'js/jquery.color.min.js'
        },
        css: {
          lightbox: 'css/jquery.lightbox.css'
        }
      },
      uncompressed: {
        js: {
          lightbox: 'js/jquery.lightbox.js',
          colorBlend: 'js/jquery.color.js'
        },
        css: {
          lightbox: 'css/jquery.lightbox.css'
        }
      },
      images: {
        prev:   'images/prev.gif',
        next:   'images/next.gif',
        blank:    'images/blank.gif',
        loading:  'images/loading.gif'
      }
    },
    
    text: {
      // For translating
      image:    'Image',
      of:     'of',
      close:    'Close X',
      closeInfo:  'You can also click anywhere outside the image to close.',
      download: 'Download.',
      help: {
        close:    'Click to close',
        interact: 'Hover to interact'
      },
      about: {
        text:   'jQuery Lightbox Plugin (balupton edition)',
        title:  'Licenced under the GNU Affero General Public License.',
        link: 'http://jquery.com/plugins/project/jquerylightbox_bal'
      }
    },
    
    keys: {
      close:  'c',
      prev: 'p',
      next: 'n'
    },
    
    handlers: {
      // For custom actions
      show: null
    },
    
    opacity:    0.9,
    padding:    null,   // if null - autodetect
    
    speed:      400,    // Duration of effect, milliseconds
    
    rel:      'lightbox', // What to look for in the rels
    
    auto_relify:  true,   // should we automaticly do the rels?
    
    auto_scroll:  'follow', // should the lightbox scroll with the page? follow, disabled, ignore
    auto_resize:  true,   // true or false
    
    ie6:      null,   // are we ie6?
    ie6_support:  true,   // have ie6 support
    ie6_upgrade:  true,   // show ie6 upgrade message
    
    colorBlend:   null,   // null - auto-detect, true - force, false - no
    
    download_link:    true, // Display the download link
    
    show_helper_text: true, // Display the helper text up the top right
    show_linkback:    true, // true, false
    show_info:      'auto', // auto - automaticly handle, true - force
    show_extended_info: 'auto', // auto - automaticly handle, true - force  
    
    // names of the options that can be modified
    options:  ['show_helper_text', 'auto_scroll', 'auto_resize', 'download_link', 'show_info', 'show_extended_info', 'ie6_support', 'ie6_upgrade', 'colorBlend', 'baseurl', 'files', 'text', 'show_linkback', 'keys', 'opacity', 'padding', 'speed', 'rel', 'auto_relify'],
    
    // -----------------
    // Functions
    
    construct: function ( options )
    { // Construct our Lightbox
      
      // -------------------
      // Prepare
      
      // Initial construct
      var initial = typeof this.constructed === 'undefined' || this.constructed === false;
      this.constructed = true;
      
      // Perform domReady
      var domReady = initial;
      
      // Prepare options
      options = options || {};
      
      // -------------------
      // Handle files
      
      // Prepend function to use later
      var prepend = function(item, value) {
        if ( typeof item === 'object' ) {
          for (var i in item) {
            item[i] = prepend(item[i], value);
          }
        } else if ( typeof value === 'array' ) {
          for (var i=0,n=item.length; i<n; ++i) {
            item[i] = prepend(item[i], value);
          }
        } else {
          item = value+item;
        }
        return item;
      }
      
      // Add baseurl
      if ( initial && (typeof options.files === 'undefined') )
      { // Load the files like default
        
        // Reset compressed
        this.compressed = null;
        
        // Get the src of the first script tag that includes our js file (with or without an appendix)
        var $script = $('script[src*='+this.files.compressed.js.lightbox+']:first');
        if ( $script.length !== 0 ) {
          // Compressed
          $.extend(true, this.files, this.files.compressed);
          this.compressed = true;
        } else {
          // Uncompressed
          $script = $('script[src*='+this.files.uncompressed.js.lightbox+']:first');
          if ( $script.length !== 0 ) {
            // Uncompressed
            $.extend(true, this.files, this.files.uncompressed);
            this.compressed = false;
          } else {
            // Nothing
          }
        }
        
        // Make sure we found ourselves
        if ( this.compressed === null )
        { // We didn't
          $.console.error('Lightbox was not able to find it\'s javascript script tag necessary for auto-inclusion.');
          // We don't work with files anymore, so don't care for domReady
          domReady = false;
        }
        else
        { // We found ourself
          
          // Grab the script src
          this.src = $script.attr('src');
          
          // The baseurl is the src up until the start of our js file
          this.baseurl = this.src.substring(0, this.src.indexOf(this.files.js.lightbox));
          
          // Prepend baseurl to files
          this.files = prepend(this.files, this.baseurl);
          
          // Now as we have source, we may have more params
          options = $.extend(options, $.params_to_json(this.src));
        }
        
      }
      else
      if ( typeof options.files === 'object' )
      { // We have custom files
        // Prepend baseurl to files
        options.files = prepend(options.files, this.baseurl);
      }
      else
      { // Don't have any files, so no need to perform domReady
        domReady = false;
      }
      
      // -------------------
      // Apply options
      
      for ( var i in this.options )
      { // Cycle through the options
        var name = this.options[i];
        if ( (typeof options[name] === 'object') && (typeof this[name] === 'object') )
        { // We have a group like text or files
          this[name] = $.extend(true, this[name], options[name]);
        }
        else if ( typeof options[name] !== 'undefined' )
        { // We have that option, so apply it
          this[name] = options[name];
        }
      } delete i;
      
      // -------------------
      // Figure out what to do
      
      // Handle IE6
      if ( initial && navigator.userAgent.indexOf('MSIE 6') >= 0 )
      { // Is IE6
        this.ie6 = true;
      }
      else
      { // We are not IE6
        this.ie6 = false;
      }
      
      // -------------------
      // Handle our DOM
      
      if ( domReady || typeof options.download_link !== 'undefined' ||  typeof options.colorBlend !== 'undefined' || typeof options.files === 'object' || typeof options.text === 'object' || typeof options.show_linkback !== 'undefined' || typeof options.scroll_with !== 'undefined' )
      { // We have reason to handle the dom
        $(function() {
          // DOM is ready, so fire our DOM handler
          $.Lightbox.domReady();
        });
      }
      
      // -------------------
      // Finish Up
      
      // All good
      return true;
    },
    
    domReady: function ( )
    {
      // -------------------
      // Include resources
      
      // Grab resources
      var bodyEl = document.getElementsByTagName($.browser.safari ? 'head' : 'body')[0];
      var stylesheets = this.files.css;
      var scripts = this.files.js;
      
      // Handle IE6 appropriatly
      if ( this.ie6 && this.ie6_upgrade )
      { // Add the upgrade message
        scripts.ie6 = 'http://www.savethedevelopers.org/say.no.to.ie.6.js';
      }
      
      // colorBlend
      if ( this.colorBlend === true && typeof $.colorBlend === 'undefined' )
      { // Force colorBlend
        this.colorBlend = true;
        // Leave file in place to be loaded
      }
      else
      { // We either have colorBlend or we don't
        this.colorBlend = typeof $.colorBlend !== 'undefined';
        // Remove colorBlend file
        delete scripts.colorBlend;
      }
      
      // Include stylesheets
      for ( stylesheet in stylesheets )
      {
        var linkEl = document.createElement('link');
        linkEl.type = 'text/css';
        linkEl.rel = 'stylesheet';
        linkEl.media = 'screen';
        linkEl.href = stylesheets[stylesheet];
        linkEl.id = 'lightbox-stylesheet-'+stylesheet.replace(/[^a-zA-Z0-9]/g, '');
        $('#'+linkEl.id).remove();
        bodyEl.appendChild(linkEl);
      }
      
      // Include javascripts
      for ( script in scripts )
      {
        var scriptEl = document.createElement('script');
        scriptEl.type = 'text/javascript';
        scriptEl.src = scripts[script];
        scriptEl.id = 'lightbox-script-'+script.replace(/[^a-zA-Z0-9]/g, '');
        $('#'+scriptEl.id).remove();
        bodyEl.appendChild(scriptEl);
      }
      
      // Cleanup
      delete scripts;
      delete stylesheets;
      delete bodyEl;
      
      // -------------------
      // Append display
      
      // Append markup
      $('#lightbox,#lightbox-overlay').remove();
      $('body').append('<div id="lightbox-overlay"><div id="lightbox-overlay-text">'+(this.show_linkback?'<p><span id="lightbox-overlay-text-about"><a href="#" title="'+this.text.about.title+'">'+this.text.about.text+'</a></span></p><p>&nbsp;</p>':'')+(this.show_helper_text?'<p><span id="lightbox-overlay-text-close">'+this.text.help.close+'</span><br/>&nbsp;<span id="lightbox-overlay-text-interact">'+this.text.help.interact+'</span></p>':'')+'</div></div><div id="lightbox"><div id="lightbox-imageBox"><div id="lightbox-imageContainer"><img id="lightbox-image" /><div id="lightbox-nav"><a href="#" id="lightbox-nav-btnPrev"></a><a href="#" id="lightbox-nav-btnNext"></a></div><div id="lightbox-loading"><a href="#" id="lightbox-loading-link"><img src="' + this.files.images.loading + '" /></a></div></div></div><div id="lightbox-infoBox"><div id="lightbox-infoContainer"><div id="lightbox-infoHeader"><span id="lightbox-caption">'+(this.download_link ? '<a href="#" title="' + this.text.download + '" id="lightbox-caption-title"></a>' : '<span id="lightbox-caption-title"></span>')+'<span id="lightbox-caption-seperator"></span><span id="lightbox-caption-description"></span></span></div><div id="lightbox-infoFooter"><span id="lightbox-currentNumber"></span><span id="lightbox-close"><a href="#" id="lightbox-close-button" title="'+this.text.closeInfo+'">' + this.text.close + '</a></span></div><div id="lightbox-infoContainer-clear"></div></div></div></div>');
      
      // Update Boxes - for some crazy reason this has to be before the hide in safari and konqueror
      this.resizeBoxes();
      this.repositionBoxes();
      
      // Hide
      $('#lightbox,#lightbox-overlay,#lightbox-overlay-text-interact').hide();
      
      // -------------------
      // Browser specifics
      
      // Handle IE6
      if ( this.ie6 && this.ie6_support )
      { // Support IE6
        // IE6 does not support fixed positioning so absolute it
        // ^ This is okay as we disable scrolling
        $('#lightbox-overlay').css({
          position: 'absolute',
          top:    '0px',
          left:   '0px'
        });
      }
      
      // -------------------
      // Preload Images
      
      // Cycle and preload
      $.each(this.files.images, function()
      { // Proload the image
        var preloader = new Image();
        preloader.onload = function() {
          preloader.onload = null;
          preloader = null;
        };  preloader.src = this;
      });
      
      // -------------------
      // Apply events
      
      // If the window resizes, act appropriatly
      $(window).unbind('resize').resize(function ()
      { // The window has been resized
        $.Lightbox.resizeBoxes('resized');
      });
      
      // If the window scrolls, act appropriatly
      if ( this.scroll === 'follow' )
      { // We want to
        $(window).scroll(function ()
        { // The window has scrolled
          $.Lightbox.repositionBoxes();
        });
      }
      
      // Prev
      $('#lightbox-nav-btnPrev').unbind().hover(function() { // over
        $(this).css({ 'background' : 'url(' + $.Lightbox.files.images.prev + ') left 35% no-repeat' });
      },function() { // out
        $(this).css({ 'background' : 'transparent url(' + $.Lightbox.files.images.blank + ') no-repeat' });
      }).click(function() {
        $.Lightbox.showImage($.Lightbox.images.prev());
        return false;
      });
          
      // Next
      $('#lightbox-nav-btnNext').unbind().hover(function() { // over
        $(this).css({ 'background' : 'url(' + $.Lightbox.files.images.next + ') right 35% no-repeat' });
      },function() { // out
        $(this).css({ 'background' : 'transparent url(' + $.Lightbox.files.images.blank + ') no-repeat' });
      }).click(function() {
        $.Lightbox.showImage($.Lightbox.images.next());
        return false;
      });
      
      // Help
      if ( this.show_linkback )
      { // Linkback exists so add handler
        $('#lightbox-overlay-text-about a').click(function(){window.open($.Lightbox.text.about.link); return false;});
      }
      $('#lightbox-overlay-text-close').unbind().hover(
        function(){
          $('#lightbox-overlay-text-interact').fadeIn();
        },
        function(){
          $('#lightbox-overlay-text-interact').fadeOut();
        }
      );
      
      // Image link
      $('#lightbox-caption-title').click(function(){window.open($(this).attr('href')); return false;});
      
      // Assign close clicks
      $('#lightbox-overlay, #lightbox, #lightbox-loading-link, #lightbox-btnClose').unbind().click(function() {
        $.Lightbox.finish();
        return false; 
      });
      
      // -------------------
      // Finish Up
      
      // Relify
      if ( this.auto_relify )
      { // We want to relify, no the user
        this.relify();
      }
      
      // All good
      return true;
    },
    
    relify: function ( )
    { // Create event
    
      //
      var groups = {};
      var groups_n = 0;
      var orig_rel = this.rel;
      // Create the groups
      $.each($('[rel*='+orig_rel+']'), function(index, obj){
        // Get the group
        var rel = $(obj).attr('rel');
        // Are we really a group
        if ( rel === orig_rel )
        { // We aren't
          rel = groups_n; // we are individual
        }
        // Does the group exist
        if ( typeof groups[rel] === 'undefined' )
        { // Make the group
          groups[rel] = [];
          groups_n++;
        }
        // Append the image
        groups[rel].push(obj);
      });
      // Lightbox groups
      $.each(groups, function(index, group){
        $(group).lightbox();
      });
      // Done
      return true;
    },
    
    init: function ( image, images )
    { // Init a batch of lightboxes
      
      // Establish images
      if ( typeof images === 'undefined' )
      {
        images = image;
        image = 0;
      }
      
      // Clear
      this.images.clear();
      
      // Add images
      if ( !this.images.add(images) )
      { return false; }
      
      // Do we need to bother
      if ( this.images.empty() )
      { // No images
        $.console.warn('WARNING', 'Lightbox started, but no images: ', image, images);
        return false;
      }
      
      // Set active
      if ( !this.images.active(image) )
      { return false; }
      
      // Done
      return true;
    },
    
    start: function ( )
    { // Display the lightbox
        
      // We are alive
      this.visible = true;
      
      // Adjust scrolling
      if ( this.scroll === 'disable' )
      { // 
        $(document.body).css('overflow', 'hidden');
      }
      
      // Fix attention seekers
      $('embed, object, select').css('visibility', 'hidden');//.hide(); - don't use this, give it a go, find out why!
      
      // Resize the boxes appropriatly
      this.resizeBoxes('general');
      
      // Reposition the Boxes
      this.repositionBoxes({'speed':0});
      
      // Hide things
      $('#lightbox-infoFooter').hide(); // we hide this here because it makes the display smoother
      $('#lightbox-image,#lightbox-nav,#lightbox-nav-btnPrev,#lightbox-nav-btnNext,#lightbox-infoBox').hide();
          
      // Display the boxes
      $('#lightbox-overlay').css('opacity',this.opacity).fadeIn(400, function(){
        // Show the lightbox
        $('#lightbox').fadeIn(300);
        
        // Display first image
        if ( !$.Lightbox.showImage($.Lightbox.images.active()) )
        { $.Lightbox.finish();  return false; }
      });
      
      // All done
      return true;
    },
    
    finish: function ( )
    { // Get rid of lightbox
    
      // Hide lightbox
      $('#lightbox').hide();
      $('#lightbox-overlay').fadeOut(function() { $('#lightbox-overlay').hide(); });
      
      // Fix attention seekers
      $('embed, object, select').css({ 'visibility' : 'visible' });//.show();
      
      // Kill active image
      this.images.active(false);
      
      // Adjust scrolling
      if ( this.scroll === 'disable' )
      { // 
        $(document.body).css('overflow', 'visible');
      }
      
      // We are dead
      this.visible = false;
      
    },
    
    resizeBoxes: function ( type )
    { // Resize the boxes
      // Used on transition or window resize
      
      // Resize Overlay
      if ( type !== 'transition' )
      { // We don't care for transition
        var $body = $(this.ie6 ? document.body : document);
        $('#lightbox-overlay').css({
          width:    $body.width(),
          height:   $body.height()
        });
        delete $body;
      }
      
      // Handle cases
      switch ( type )
      {
        case 'general': // general resize (start of lightbox)
          return true;
          break;
        case 'resized': // window was resized
          if ( this.auto_resize === false )
          { // Stop
            // Reposition
            this.repositionBoxes({'nHeight':nHeight, 'speed':this.speed});
            return true;
          }
        case 'transition': // transition between images
        default: // unknown
          break;
      }
      
      // Get image
      var image = this.images.active();
      if ( !image || !image.width || !this.visible )
      { // No image or no visible lightbox, so we don't care
        //$.console.warn('A resize occured while no image or no lightbox...');
        return false;
      }
      
      // Resize image box
      // i:image, w:window, b:box, c:current, n:new, d:difference
      
      // Get image dimensions
      var iWidth  = image.width;
      var iHeight = image.height;
      
      // Get window dimensions
      var wWidth  = $(window).width();
      var wHeight = $(window).height();
      
      // Check if we are in size
      // Lightbox can take up 4/5 of size
      if ( this.auto_resize !== false )
      { // We want to auto resize
        var maxWidth  = Math.floor(wWidth*(4/5));
        var maxHeight = Math.floor(wHeight*(4/5));
        var resizeRatio;
        while ( iWidth > maxWidth || iHeight > maxHeight )
        { // We need to resize
          if ( iWidth > maxWidth )
          { // Resize width, then height proportionally
            resizeRatio = maxWidth/iWidth;
            iWidth = maxWidth;
            iHeight = Math.floor(iHeight*resizeRatio);
          }
          if ( iHeight > maxHeight )
          { // Resize height, then width proportionally
            resizeRatio = maxHeight/iHeight;
            iHeight = maxHeight;
            iWidth = Math.floor(iWidth*resizeRatio);
          }
        }
      }
      
      // Get current width and height
      var cWidth  = $('#lightbox-imageBox').width();
      var cHeight = $('#lightbox-imageBox').height();
  
      // Get the width and height of the selected image plus the padding
      // padding*2 for both sides (left+right || top+bottom)
      var nWidth  = (iWidth  + (this.padding * 2));
      var nHeight = (iHeight + (this.padding * 2));
      
      // Diferences
      var dWidth  = cWidth  - nWidth;
      var dHeight = cHeight - nHeight;
      
      // Set the overlay buttons height and the infobox width
      // Other dimensions specified by CSS
      $('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css('height', nHeight); 
      $('#lightbox-infoBox').css('width', nWidth);
      
      // Handle final action
      if ( type === 'transition' )
      { // We are transition
        // Do we need to wait? (just a nice effect to counter the other
        if ( dWidth === 0 && dHeight === 0 )
        { // We are the same size
          this.pause(this.speed/3);
          this.showImage(null, 3);
        }
        else
        { // We are not the same size
          // Animate
          $('#lightbox-image').width(iWidth).height(iHeight);
          $('#lightbox-imageBox').animate({width: nWidth, height: nHeight}, this.speed, function ( ) { $.Lightbox.showImage(null, 3); } );
        }
      }
      else
      { // We are a resize
        // Animate Lightbox
        $('#lightbox-image').animate({width:iWidth, height:iHeight}, this.speed);
        $('#lightbox-imageBox').animate({width: nWidth, height: nHeight}, this.speed);
      }
      
      // Reposition
      this.repositionBoxes({'nHeight':nHeight, 'speed':this.speed});
      
      // Done
      return true;
    },
    
    repositioning:      false,  // are we currently repositioning
    reposition_failsafe:  false,  // failsafe
    repositionBoxes: function ( options )
    {
      // Prepare
      if ( this.repositioning )
      { // Already here
        this.reposition_failsafe = true;
        return null;
      }
      this.repositioning = true;
      
      // Options
      options = $.extend({}, options);
      options.callback = options.callback || null;
      options.speed = options.speed || 'slow';
      
      // Get page scroll
      var pageScroll = this.getPageScroll();
      
      // Figure it out
      // alert($(window).height()+"\n"+$(document.body).height()+"\n"+$(document).height());
      // var nHeight = options.nHeight || parseInt($('#lightbox').height(),10) || $(document).height()/3;
      var nHeight = options.nHeight || parseInt($('#lightbox').height(),10);
      
      // Display lightbox in center
      // var nTop = pageScroll.yScroll + ($(document.body).height() /*frame height*/ - nHeight) / 2.5;
      var nTop = pageScroll.yScroll + ($(window).height() /*frame height*/ - nHeight) / 2.5;
      var nLeft = pageScroll.xScroll;
      
      // Animate
      var css = {
        left: nLeft,
        top: nTop
      };
      if (options.speed) {
        $('#lightbox').animate(css, 'slow', function(){
          if ( $.Lightbox.reposition_failsafe )
          { // Fire again
            $.Lightbox.repositioning = $.Lightbox.reposition_failsafe = false;
            $.Lightbox.repositionBoxes(options);
          }
          else
          { // Done
            $.Lightbox.repositioning = false;
            if ( options.callback )
            { // Call the user callback
              options.callback();
            }
          }
        });
      }
      else
      {
        $('#lightbox').css(css);
        if ( this.reposition_failsafe )
        { // Fire again
          this.repositioning = this.reposition_failsafe = false;
          this.repositionBoxes(options);
        }
        else
        { // Done
          this.repositioning = false;
        }
      }
      
      // Done
      return true;
    },
    
    visible: false,
    showImage: function ( image, step )
    {
      // Establish image
      image = this.images.get(image);
      if ( !image ) { return image; }
      
      // Default step
      step = step || 1;
      
      // Split up below for jsLint compliance
      var skipped_step_1 = step > 1 && this.images.active().src !== image.src;
      var skipped_step_2 = step > 2 && $('#lightbox-image').attr('src') !== image.src;
      if ( skipped_step_1 || skipped_step_2 )
      { // Force step 1
        $.console.info('We wanted to skip a few steps: ', image, step, skipped_step_1, skipped_step_2);
        step = 1;
      }
      
      // What do we need to do
      switch ( step )
      {
        // ---------------------------------
        // We need to preload
        case 1:
        
          // Disable keyboard nav
          this.KeyboardNav_Disable();
          
          // Show the loading image
          $('#lightbox-loading').show();
          
          // Hide things
          $('#lightbox-image,#lightbox-nav,#lightbox-nav-btnPrev,#lightbox-nav-btnNext,#lightbox-infoBox').hide();
          
          // Remove show info events
          $('#lightbox-imageBox').unbind();
          // ^ Why? Because otherwise when the image is changing, the info pops out, not good!
          
          // Make the image the active image
          if ( !this.images.active(image) ) { return false; }
          
          // Check if we need to preload
          if ( image.width && image.height )
          { // We don't
            // Continue to next step
            this.showImage(null, 2);
          }
          else
          { // We do
            // Create preloader
            var preloader = new Image();
            // Set callback
            preloader.onload = function()
            { // We have preloaded the image
              // Update image with our new info
              image.width  = preloader.width;
              image.height = preloader.height;
              // Continue to next step
              $.Lightbox.showImage(null, 2);
              // Kill preloader
              preloader.onload = null;
              preloader = null;
            };
            // Start preload
            preloader.src = image.src;
          }
          
          // Done
          break;
        
        
        // ---------------------------------
        // Resize the container
        case 2:
          
          // Apply image changes
          $('#lightbox-image').attr('src', image.src);
          
          // Set container border (Moved here for Konqueror fix - Credits to Blueyed)
          if ( typeof this.padding === 'undefined' || this.padding === null || isNaN(this.padding) )
          { // Autodetect
            this.padding = parseInt($('#lightbox-imageContainer').css('padding-left'), 10) || parseInt($('#lightbox-imageContainer').css('padding'), 10) || 0;
          }
          
          // Use colorBlend?
          if ( this.colorBlend )
          { // We have colorBlend
            // Background
            $('#lightbox-overlay').animate({'backgroundColor':image.color}, this.speed*2);
            // Border
            $('#lightbox-imageBox').css('borderColor', image.color);
          }
          
          // Resize boxes
          this.resizeBoxes('transition');
          // ^ contains callback to next step
          
          // Done
          break;
        
        
        // ---------------------------------
        // Display the image
        case 3:
          
          // Hide loading
          $('#lightbox-loading').hide();
          
          // Animate image
          $('#lightbox-image').fadeIn(this.speed*1.5, function() {$.Lightbox.showImage(null, 4); });
          
          // Start the proloading of other images
          this.preloadNeighbours();
          
          // Fire custom handler show
          if ( this.handlers.show !== null )
          { // Fire it
            this.handlers.show(image);
          }
          
          // Done
          break;
        
        
        // ---------------------------------
        // Set image info / Set navigation
        case 4:
          
          // ---------------------------------
          // Set image info
          
          // Hide and set image info
          var $title = $('#lightbox-caption-title').html(image.title || 'Untitled');
          if ( this.download_link )
          { $title.attr('href', this.download_link ? image.src : ''); }
          delete $title;
          $('#lightbox-caption-seperator').html(image.description ? ': ' : '');
          $('#lightbox-caption-description').html(image.description || '&nbsp;');
          
          // If we have a set, display image position
          if ( this.images.size() > 1 )
          { // Display
            $('#lightbox-currentNumber').html(this.text.image + '&nbsp;' + ( image.index + 1 ) + '&nbsp;' + this.text.of + '&nbsp;' + this.images.size());
          } else
          { // Empty
            $('#lightbox-currentNumber').html('&nbsp;');
          }
          
          // ---------------------------------
          // Info events
          
          // Apply event
          $('#lightbox-imageBox').unbind('mouseover').mouseover(function(){
            $('#lightbox-infoBox:not(:visible)').stop().slideDown('fast');
          });
          
          // Apply event
          $('#lightbox-infoBox').unbind('mouseover').mouseover(function(){
            $('#lightbox-infoFooter:not(:visible)').stop().slideDown('fast');
          });
          
          // Forced show?
          if ( this.show_extended_info === true )
          { // Force show
            $('#lightbox-imageBox').trigger('mouseover');
            $('#lightbox-infoBox').trigger('mouseover');
          }
          else if ( this.show_info === true )
          { // Force show
            $('#lightbox-imageBox').trigger('mouseover');
          }
          
          // ---------------------------------
          // Set navigation
    
          // Instead to define this configuration in CSS file, we define here. And it's need to IE. Just.
          $('#lightbox-nav-btnPrev, #lightbox-nav-btnNext').css({ 'background' : 'transparent url(' + this.files.images.blank + ') no-repeat' });
          
          // If not first, show previous button
          if ( !this.images.first(image) ) {
            // Not first, show button
            $('#lightbox-nav-btnPrev').show();
          }
          
          // If not last, show next button
          if ( !this.images.last(image) ) {
            // Not first, show button
            $('#lightbox-nav-btnNext').show();
          }
          
          // Make navigation active / show it
          $('#lightbox-nav').show();
          
          // Enable keyboard navigation
          this.KeyboardNav_Enable();
          
          // Done
          break;
          
          
        // ---------------------------------
        // Error handling
        default:
          $.console.error('Don\'t know what to do: ', image, step);
          return this.showImage(image, 1);
          // break;
        
      }
      
      // All done
      return true;
    },
    
    preloadNeighbours: function ( )
    { // Preload all neighbour images
      
      // Do we need to do this?
      if ( this.images.single() || this.images.empty() )
      { return true;  }
      
      // Get active image
      var image = this.images.active();
      if ( !image ) { return image; }
      
      // Load previous
      var prev = this.images.prev(image);
      var objNext;
      if ( prev ) {
        objNext = new Image();
        objNext.src = prev.src;
      }
      
      // Load next
      var next = this.images.next(image);
      if ( next ) {
        objNext = new Image();
        objNext.src = next.src;
      }
    },
    
    // --------------------------------------------------
    // Things we don't really care about
    
    KeyboardNav_Enable: function ( ) {
      $(document).keydown(function(objEvent) {
        $.Lightbox.KeyboardNav_Action(objEvent);
      });
    },
    
    KeyboardNav_Disable: function ( ) {
      $(document).unbind('keydown');
    },
    
    KeyboardNav_Action: function ( objEvent ) {
      // Prepare
      objEvent = objEvent || window.event;
      
      // Get the keycode
      var keycode = objEvent.keyCode;
      var escapeKey = objEvent.DOM_VK_ESCAPE /* moz */ || 27;
      
      // Get key
      var key = String.fromCharCode(keycode).toLowerCase();
      
      // Close?
      if ( key === this.keys.close || keycode === escapeKey )
      { return $.Lightbox.finish();   }
      
      // Prev?
      if ( key === this.keys.prev || keycode === 37 )
      { // We want previous
        return $.Lightbox.showImage($.Lightbox.images.prev());
      }
      
      // Next?
      if ( key === this.keys.next || keycode === 39 )
      { // We want next
        return $.Lightbox.showImage($.Lightbox.images.next());
      }
      
      // Unknown
      return true;
    },
    
    getPageScroll: function ( ) {
      var xScroll, yScroll;
      if (self.pageYOffset)
      { // Some browser
        yScroll = self.pageYOffset;
        xScroll = self.pageXOffset;
      } else if (document.documentElement && document.documentElement.scrollTop)
      { // Explorer 6 Strict
        yScroll = document.documentElement.scrollTop;
        xScroll = document.documentElement.scrollLeft;
      } else if (document.body)
      { // All other browsers
        yScroll = document.body.scrollTop;
        xScroll = document.body.scrollLeft; 
      }
      var arrayPageScroll = {'xScroll':xScroll,'yScroll':yScroll};
      return arrayPageScroll;
    },
    
    pause: function ( ms ) {
      var date = new Date();
      var curDate = null;
      do { curDate = new Date(); }
      while ( curDate - date < ms);
    }
  
  }); // We have finished extending/defining our LightboxClass


  // --------------------------------------------------
  // Finish up
  
  // Instantiate
  if ( typeof $.Lightbox === 'undefined' )
  { // 
    $.Lightbox = new $.LightboxClass();
  }

// Finished definition

})(jQuery); // We are done with our plugin, so lets call it with jQuery as the argument
