(function($)
{
  var settings = {
    type: 'clickthru',
    effect: 'fade',
    template: $('#slide_template'),

    //default for auto slideshows
    delay: 3000
  };
  
  var methods = {
    init: function(options)
    {
      return this.each(function()
      {
        var $this = $(this),
          data = $this.data('slideshow'),
          prefix = $(this).attr('id') + '_slide';


        if (options)
        {
          $.extend(settings, options);
        }

        $this.data('slideshow', {
          slides: settings.slides,
          position: {
            top: $this.position().top,
            left: $this.position().left
          }
        });

        if ($this.children().length)
        {
          //wrap child elements
          $this.children().wrapAll('<div class="' + prefix + ' ' + prefix + '_0"/>');

          //position the first slide
          var first_slide = $('.' + prefix + '_0');
          first_slide.css({
            'position': 'absolute',
            'top': $this.data('slideshow').position.top + 'px',
            'left': $this.data('slideshow').position.left + 'px',
            'width': $this.width() + 'px'
          });

          $this.data('slideshow').currentSlide = 0;
        }

        _setEvents.call(this);
      });
    },

    next: function()
    {
      return this.each(function()
      {
        //go to next slide
        var $this = $(this),
          data = $this.data('slideshow');

        data.forward = true;
        
        if (data.currentSlide == data.slides.length - 1)
        {
          _gotoSlide.call(this, 0, true);
        }
        else
        {
          _gotoSlide.call(this, data.currentSlide + 1, true);
        }
      });
    },

    previous: function()
    {
      return this.each(function()
      {
        //go to previous slide
        var $this = $(this),
          data = $this.data('slideshow');

        data.forward = false;
        
        if (data.currentSlide == 0)
        {
          _gotoSlide.call(this, data.slides.length - 1, false);
        }
        else
        {
          _gotoSlide.call(this, data.currentSlide - 1, false);
        }
      });

    },

    gotoSlide: function(index)
    {
      var $this = $(this),
        data = $this.data('slideshow');

      if (data.currentSlide !== index)
      {
        data.forward = null;
        _gotoSlide.call(this, index);
      }
    }
  };

  function _setEvents()
  {
    var $this = $this;
    
    if (settings.type == 'clickthru')
    {
      //set event handlers
      settings.previousAnchor.click({ slideshow: this }, function(event)
      {
        var $slideshow = $(event.data.slideshow);

        if (!$slideshow.find(':animated').length)
        {
          $(event.data.slideshow).slideshow('previous');
        }
        
        return false;
      });

      settings.nextAnchor.click({ slideshow: this }, function(event)
      {
        var $slideshow = $(event.data.slideshow);

        if (!$slideshow.find(':animated').length)
        {
          $(event.data.slideshow).slideshow('next');
        }

        return false;
      });

      $(window).keyup({
        slideshow: this
      }, function(event)
      {
        var $slideshow = $(event.data.slideshow);

        if ($slideshow.is(':visible') && !$slideshow.find(':animated').length && (event.which == 37 || event.which == 39))
        {
          if (event.which == 37)
          {
            $slideshow.slideshow('previous');
          }
          else
          {
            $slideshow.slideshow('next');
          }
        }
      });
    }
    else if (settings.type == 'auto')
    {
      //set interval
      window.setInterval(function(slideshow)
      {
        $(slideshow).slideshow('next');
      }, settings.delay, this);
    }
  }

  function _gotoSlide(index)
  {
    var $this = $(this),
      prefix = $this.attr('id') + '_slide';

    var replacement = $('.' + prefix + '_' + index);

    if (!replacement.length)
    {
      _loadSlide.call(this, index);
    }
    else
    {
      _doTransition.call(this, index, replacement);
    }
  }

  function _loadSlide(index)
  {
      var $this = $(this),
        data = $this.data('slideshow'),
        prefix = $this.attr('id') + '_slide';

      var slide = $('<div class="' + prefix + ' ' + prefix + '_' + index + '"/>').css('visibility', 'hidden');
      $('<img src="' + data.slides[index].file + '"/>').load({
        slideshow: this,
        slideData: data.slides[index],
        slideIndex: index,
        slide: slide
      }, function(event)
      {
        _handleSlideLoaded.call(event.data.slideshow, event.data.slideIndex, event.data.slide);
      });
  }

  function _handleSlideLoaded(index, slide)
  {
    var $this = $(this),
      data = $this.data('slideshow');

    $('#slide_template').tmpl(data.slides[index]).appendTo(slide);
    slide.appendTo($this);
    slide.css({
      'position': 'absolute',
      'top': $this.data('slideshow').position.top + 'px',
      'left': $this.data('slideshow').position.left + 'px',
      'width': $this.width() + 'px',
      'visibility': 'inherit'
    });
    slide.hide();

    _doTransition.call($this, index, slide);
  }

  function _doTransition(index, replacement)
  {
    var $this = $(this),
      data = $(this).data('slideshow'),
      prefix = $this.attr('id') + '_slide';

    var current = $('.' + prefix + ':visible');
    
    switch(settings.effect)
    {
      case 'fade':
        current.fadeOut('slow');
        replacement.fadeIn('slow');
        break;

      case 'slide':
        if (data.forward === false)
        {
          current.hide('slide', { direction: 'right' }, 'slow');
          replacement.show('slide', { direction: 'left' }, 'slow');
        }
        else
        {
          current.hide('slide', { direction: 'left' }, 'slow');
          replacement.show('slide', { direction: 'right' }, 'slow');
        }
        break;

      default:
        current.hide();
        replacement.show();
    }

    data.currentSlide = index;
  }

  $.fn.slideshow = function(method)
  {
    if (methods[method])
    {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    }
    else if (typeof(method) === 'object' || !method)
    {
      return methods.init.apply(this, arguments);
    }
    else
    {
      $.error('Method ' + method + ' does not exist on jQuery.slideshow');
    }
  };
})(jQuery);
