# SlidesJS 3.0.4

# Documentation and examples http://slidesjs.com
# Support forum http://groups.google.com/group/slidesjs
# Created by Nathan Searles http://nathansearles.com

# Version: 3.0.4
# Updated: June 26th, 2013

# SlidesJS is an open source project, contribute at GitHub:
# https://github.com/nathansearles/Slides

# (c) 2013 by Nathan Searles

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

(($, window, document) ->
  pluginName = "slidesjs"
  defaults =
    width: 940
      # Set the default width of the slideshow.
    height: 528
      # Set the default height of the slideshow.
    start: 1
      # Set the first slide in the slideshow.
    navigation:
      # Next and previous button settings.
      active: true
        # [boolean] Create next and previous buttons.
        # You can set to false and use your own next/prev buttons.
        # User defined next/prev buttons must have the following:
        # previous: class="slidesjs-previous slidesjs-navigation"
        # next: class="slidesjs-next slidesjs-navigation"
      effect: "slide"
        # [string] Can be either "slide" or "fade".
    pagination:
        # Pagination settings
      active: true
        # [boolean] Create pagination items.
        # You cannot use your own pagination.
      effect: "slide"
        # [string] Can be either "slide" or "fade".
    play:
        # Play and stop button setting.
      active: false
        # [boolean] Create the play and stop buttons.
        # You cannot use your own pagination.
      effect: "slide"
        # [string] Can be either "slide" or "fade".
      interval: 5000
        # [number] Time spent on each slide in milliseconds.
      auto: false
        # [boolean] Start playing the slideshow on load
      swap: true
        # [boolean] show/hide stop and play buttons
      pauseOnHover: false
        # [boolean] pause a playing slideshow on hover
      restartDelay: 2500
        # [number] restart delay on an inactive slideshow
    effect:
      slide:
        # Slide effect settings.
        speed: 500
          # [number] Speed in milliseconds of the slide animation.
      fade:
        speed: 300
          # [number] Speed in milliseconds of the fade animation.
        crossfade: true
          # [boolean] Cross-fade the transition
    callback:
      loaded: () ->
        # [function] Called when slides is loaded
      start: () ->
        # [function] Called when animation has started
      complete: () ->
        # [function] Called when animation is complete

  class Plugin
    constructor: (@element, options) ->
      @options = $.extend true, {}, defaults, options
      @_defaults = defaults
      @_name = pluginName
      @init()

  Plugin::init = ->
    $element = $(@element)
    @data = $.data this

    # Set data
    $.data this, "animating", false
    $.data this, "total", $element.children().not(".slidesjs-navigation", $element).length
    $.data this, "current", @options.start - 1
    $.data this, "vendorPrefix", @_getVendorPrefix()

    # Detect touch device
    if typeof TouchEvent != "undefined"
      $.data this, "touch", true
      # Set slide speed to half for touch
      @options.effect.slide.speed = this.options.effect.slide.speed / 2

    # Hide overflow
    $element.css overflow: "hidden"

    # Create container
    $element.slidesContainer = $element.children().not(".slidesjs-navigation", $element).wrapAll("<div class='slidesjs-container'>", $element).parent().css
      overflow: "hidden"
      position: "relative"

    # Create control div
    $(".slidesjs-container", $element)
    .wrapInner("<div class='slidesjs-control'>", $element)
    .children()

    # Setup control div
    $(".slidesjs-control", $element)
    .css
      position: "relative"
      left: 0

    # Setup slides
    $(".slidesjs-control", $element)
    .children()
    .addClass("slidesjs-slide")
    .css
      position: "absolute"
      top: 0
      left: 0
      width: "100%"
      zIndex: 0
      display: "none"
      webkitBackfaceVisibility: "hidden"

    # Assign an index to each slide
    $.each( $(".slidesjs-control", $element).children(), (i) ->
      $slide = $(this)
      $slide.attr("slidesjs-index", i)
    )

    if @data.touch
      # Bind touch events, if supported
      $(".slidesjs-control", $element).on("touchstart", (e) =>
        @_touchstart(e)
      )

      $(".slidesjs-control", $element).on("touchmove", (e) =>
        @_touchmove(e)
      )

      $(".slidesjs-control", $element).on("touchend", (e) =>
        @_touchend(e)
      )

    # Fades in slideshow, your slideshow ID must be display:none in your CSS
    $element.fadeIn 0

    # Update sets width/height of slideshow
    @update()

    # If touch device setup next slides
    @_setuptouch() if @data.touch

    # Fade in start slide
    $(".slidesjs-control", $element)
    .children(":eq(" + @data.current + ")")
    .eq(0)
    .fadeIn 0, ->
      $(this).css zIndex: 10

    if @options.navigation.active
      # Create next/prev buttons
      prevButton = $("<a>"
        class: "slidesjs-previous slidesjs-navigation"
        href: "#"
        title: "Previous"
        text: "Previous"
      ).appendTo($element)

      nextButton = $("<a>"
        class: "slidesjs-next slidesjs-navigation"
        href: "#"
        title: "Next"
        text: "Next"
      ).appendTo($element)

    # bind click events
    $(".slidesjs-next", $element).click (e) =>
      e.preventDefault()
      @stop(true)
      @next(@options.navigation.effect)

    $(".slidesjs-previous", $element).click (e) =>
      e.preventDefault()
      @stop(true)
      @previous(@options.navigation.effect)

    if @options.play.active
      playButton = $("<a>",
        class: "slidesjs-play slidesjs-navigation"
        href: "#"
        title: "Play"
        text: "Play"
      ).appendTo($element)

      stopButton = $("<a>",
        class: "slidesjs-stop slidesjs-navigation"
        href: "#"
        title: "Stop"
        text: "Stop"
      ).appendTo($element)

      playButton.click (e) =>
        e.preventDefault()
        @play(true)

      stopButton.click (e) =>
        e.preventDefault()
        @stop(true)

      if @options.play.swap
        stopButton.css
          display: "none"


    if @options.pagination.active
      # Create unordered list pagination
      pagination = $("<ul>"
        class: "slidesjs-pagination"
      ).appendTo($element)

      # Create a list item and anchor for each slide
      $.each(new Array(@data.total), (i) =>
        paginationItem = $("<li>"
          class: "slidesjs-pagination-item"
        ).appendTo(pagination)

        paginationLink = $("<a>"
          href: "#"
          "data-slidesjs-item": i
          html: i + 1
        ).appendTo(paginationItem)

        # bind click events
        paginationLink.click (e) =>
          e.preventDefault()
          # Stop play
          @stop(true)
          # Goto to selected slide
          @goto( ($(e.currentTarget).attr("data-slidesjs-item") * 1) + 1 )
      )

    # Bind update on browser resize
    $(window).bind("resize", () =>
      @update()
    )

    # Set start pagination item to active
    @_setActive()

    # Auto play slideshow
    if @options.play.auto
      @play()

    # Slides has loaded
    @options.callback.loaded(@options.start)

  # @_setActive()
  # Sets the active slide in the pagination
  Plugin::_setActive = (number) ->
    $element = $(@element)
    @data = $.data this

    # Get the current slide index
    current = if number > -1 then number else @data.current

    # Set active slide in pagination
    $(".active", $element).removeClass "active"
    $(".slidesjs-pagination li:eq(" + current + ") a", $element).addClass "active"

  # @update()
  # Update the slideshow size on browser resize
  Plugin::update = ->
    $element = $(@element)
    @data = $.data this

    # Hide all slides expect current
    $(".slidesjs-control", $element).children(":not(:eq(" + @data.current + "))").css
      display: "none"
      left: 0
      zIndex: 0

    # Get the new width and height
    width = $element.width()
    height = (@options.height / @options.width) * width

    # Store new width and height
    @options.width = width
    @options.height = height

    # Set new width and height
    $(".slidesjs-control, .slidesjs-container", $element).css
      width: width
      height: height

  # @next()
  # Next mechanics
  Plugin::next = (effect) ->
    $element = $(@element)
    @data = $.data this

    # Set the direction
    $.data this, "direction", "next"

    # Slides or fade effect
    if effect is undefined then effect = @options.navigation.effect
    if effect is "fade" then @_fade() else @_slide()

  # @previous()
  # Previous mechanics
  Plugin::previous = (effect) ->
    $element = $(@element)
    @data = $.data this

    # Set the direction
    $.data this, "direction", "previous"

    # Slides or fade effect
    if effect is undefined then effect = @options.navigation.effect
    if effect is "fade" then @_fade() else @_slide()

  # @goto()
  # Pagination mechanics
  Plugin::goto = (number) ->
    $element = $(@element)
    @data = $.data this

    # Set effect to default if not defined
    if effect is undefined then effect = @options.pagination.effect

    # Error correction if slide doesn't exists
    if number > @data.total
      number = @data.total
    else if number < 1
      number = 1

    if typeof number is "number"
      if effect is "fade" then @_fade(number) else @_slide(number)
    else if typeof number is "string"
      if number is "first"
        if effect is "fade" then @_fade(0) else @_slide(0)
      else if number is "last"
        if effect is "fade" then @_fade(@data.total) else @_slide(@data.total)

  # @_setuptouch()
  # Setup slideshow for touch
  Plugin::_setuptouch = () ->
    $element = $(@element)
    @data = $.data this

    # Define slides control
    slidesControl = $(".slidesjs-control", $element)

    # Get next/prev slides around current slide
    next = @data.current + 1
    previous = @data.current - 1

    # Create the loop
    previous = @data.total - 1 if previous < 0
    next = 0 if next > @data.total - 1

    # By default next/prev slides are hidden, show them when on touch device
    slidesControl.children(":eq(" + next + ")").css
      display: "block"
      left: @options.width

    slidesControl.children(":eq(" + previous + ")").css
      display: "block"
      left: -@options.width

  # @_touchstart()
  # Start touch
  Plugin::_touchstart = (e) ->
    $element = $(@element)
    @data = $.data this
    touches = e.originalEvent.touches[0]

    # Setup the next and previous slides for swiping
    @_setuptouch()

    # Start touch timer
    $.data this, "touchtimer", Number(new Date())

    # Set touch position
    $.data this, "touchstartx", touches.pageX
    $.data this, "touchstarty", touches.pageY

    # Stop event from bubbling up
    e.stopPropagation()

  # @_touchend()
  # Animates the slideshow when touch is complete
  Plugin::_touchend = (e) ->
    $element = $(@element)
    @data = $.data this
    touches = e.originalEvent.touches[0]

    # Define slides control
    slidesControl = $(".slidesjs-control", $element)

    # Slide has been dragged to the right, goto previous slide
    if slidesControl.position().left > @options.width * 0.5 || slidesControl.position().left > @options.width * 0.1 && (Number(new Date()) - @data.touchtimer < 250)
      $.data this, "direction", "previous"
      @_slide()
    # Slide has been dragged to the left, goto next slide
    else if slidesControl.position().left < -(@options.width * 0.5) || slidesControl.position().left < -(@options.width * 0.1) && (Number(new Date()) - @data.touchtimer < 250)
      $.data this, "direction", "next"
      @_slide()
    else
      # Slide has not been dragged far enough, animate back to 0 and reset
        # Get the browser's vendor prefix
        prefix = @data.vendorPrefix

        # Create CSS3 styles based on vendor prefix
        transform = prefix + "Transform"
        duration = prefix + "TransitionDuration"
        timing = prefix + "TransitionTimingFunction"

        # Set CSS3 styles
        slidesControl[0].style[transform] = "translateX(0px)"
        slidesControl[0].style[duration] = @options.effect.slide.speed * 0.85 + "ms"

    # Rest slideshow
    slidesControl.on "transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd", =>
        # Get the browser's vendor prefix
        prefix = @data.vendorPrefix

        # Create CSS3 styles based on vendor prefix
        transform = prefix + "Transform"
        duration = prefix + "TransitionDuration"
        timing = prefix + "TransitionTimingFunction"

        # Set CSS3 styles
        slidesControl[0].style[transform] = ""
        slidesControl[0].style[duration] = ""
        slidesControl[0].style[timing] = ""

    # Stop event from bubbling up
    e.stopPropagation()

  # @_touchmove()
  # Moves the slide on touch
  Plugin::_touchmove = (e) ->
    $element = $(@element)
    @data = $.data this
    touches = e.originalEvent.touches[0]

    # Get the browser's vendor prefix
    prefix = @data.vendorPrefix

    # Define slides control
    slidesControl = $(".slidesjs-control", $element)

    # Create CSS3 styles based on vendor prefix
    transform = prefix + "Transform"

    # Check if user is trying to scroll vertically
    $.data this, "scrolling", Math.abs(touches.pageX - @data.touchstartx) < Math.abs(touches.pageY - @data.touchstarty)

    # Set CSS3 styles
    if !@data.animating && !@data.scrolling
      # Prevent default scrolling
      e.preventDefault()
      @_setuptouch()
      slidesControl[0].style[transform] = "translateX(" + (touches.pageX - @data.touchstartx) + "px)"

    # Stop event from bubbling up
    e.stopPropagation()

  # @play()
  # Play the slideshow
  Plugin::play = (next) ->
    $element = $(@element)
    @data = $.data this

    # Check if the slideshow is already playing
    if not @data.playInterval
      # If next is true goto next slide
      if next
        currentSlide = @data.current
        @data.direction = "next"
        if @options.play.effect is "fade" then @_fade() else @_slide()

      # Set and store interval
      $.data this, "playInterval", setInterval ( =>
        currentSlide = @data.current
        @data.direction = "next"
        if @options.play.effect is "fade" then @_fade() else @_slide()
      ), @options.play.interval

      # Define slides container
      slidesContainer = $(".slidesjs-container", $element)

      if @options.play.pauseOnHover
        # Prevent event build up
        slidesContainer.unbind()

        # Stop/pause slideshow on mouse enter
        slidesContainer.bind "mouseenter", =>
          clearTimeout @data.restartDelay
					$.data this, "restartDelay", null
          @stop()

        # Play slideshow on mouse leave
        slidesContainer.bind "mouseleave", =>
          if @options.play.restartDelay
            $.data this, "restartDelay", setTimeout ( =>
              @play(true)
            ), @options.play.restartDelay
          else
            @play()

      $.data this, "playing", true

      # Add "slidesjs-playing" class to "slidesjs-play" button
      $(".slidesjs-play", $element).addClass("slidesjs-playing")

      if @options.play.swap
        $(".slidesjs-play", $element).hide()
        $(".slidesjs-stop", $element).show()

  # @stop()
  # Stops a playing slideshow
  Plugin::stop = (clicked) ->
    $element = $(@element)
    @data = $.data this

    # Clear play interval
    clearInterval @data.playInterval

    if @options.play.pauseOnHover && clicked
      # Prevent event build up
      $(".slidesjs-container", $element).unbind()

    # Reset slideshow
    $.data this, "playInterval", null
    $.data this, "playing", false
    $(".slidesjs-play", $element).removeClass("slidesjs-playing")

    if @options.play.swap
      $(".slidesjs-stop", $element).hide()
      $(".slidesjs-play", $element).show()

  # @_slide()
  # CSS3 and JavaScript slide animations
  Plugin::_slide = (number) ->
    $element = $(@element)
    @data = $.data this

    if not @data.animating and number isnt @data.current + 1
      # Set animating to true
      $.data this, "animating", true

      # Get current slide
      currentSlide = @data.current

      if number > -1
        number = number - 1
        value = if number > currentSlide then 1 else -1
        direction = if number > currentSlide then -@options.width else @options.width
        next = number
      else
        value = if @data.direction is "next" then 1 else -1
        direction = if @data.direction is "next" then -@options.width else @options.width
        next = currentSlide + value

      # Loop from first to last slide
      next = @data.total - 1 if next is -1

      # Loop from last to first slide
      next = 0 if next is @data.total

      # Set next slide pagination item to active
      @_setActive(next)

      # Define slides control
      slidesControl = $(".slidesjs-control", $element)

      # When changing from touch to pagination reset touch slide setup
      if number > -1
        # Hide all slides expect current
        slidesControl.children(":not(:eq(" + currentSlide + "))").css
          display: "none"
          left: 0
          zIndex: 0

      # Setup the next slide
      slidesControl.children(":eq(" + next + ")").css
        display: "block"
        left: value * @options.width
        zIndex: 10

      # Start the slide animation
      @options.callback.start(currentSlide + 1)

      if @data.vendorPrefix
        # If supported use CSS3 for the animation
        # Get the browser's vendor prefix
        prefix = @data.vendorPrefix

        # Create CSS3 styles based on vendor prefix
        transform = prefix + "Transform"
        duration = prefix + "TransitionDuration"
        timing = prefix + "TransitionTimingFunction"

        # Set CSS3 styles
        slidesControl[0].style[transform] = "translateX(" + direction + "px)"
        slidesControl[0].style[duration] = @options.effect.slide.speed + "ms"

        slidesControl.on "transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd", =>
          # Clear styles
          slidesControl[0].style[transform] = ""
          slidesControl[0].style[duration] = ""

          # Reset slideshow
          slidesControl.children(":eq(" + next + ")").css left: 0
          slidesControl.children(":eq(" + currentSlide + ")").css
            display: "none"
            left: 0
            zIndex: 0

          # Set the new slide to the current
          $.data this, "current", next

          # # Set animating to false
          $.data this, "animating", false

          # Unbind transition callback to prevent build up
          slidesControl.unbind("transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd")

          # Hide all slides expect current
          slidesControl.children(":not(:eq(" + next + "))").css
            display: "none"
            left: 0
            zIndex: 0

          # If touch device setup next slides
          @_setuptouch() if @data.touch

          # End of the animation, call complete callback
          @options.callback.complete(next + 1)
      else
        # If CSS3 isn't support use JavaScript for the animation
        slidesControl.stop().animate
          left: direction
        , @options.effect.slide.speed, (=>
          slidesControl.css left: 0
          slidesControl.children(":eq(" + next + ")").css left: 0
          slidesControl.children(":eq(" + currentSlide + ")").css
            display: "none"
            left: 0
            zIndex: 0

            # Set the new slide to the current
            $.data this, "current", next

            # Set animating to false
            $.data this, "animating", false

            # End of the animation, call complete callback
            @options.callback.complete(next + 1)
          )

  # @_fade()
  # Fading and cross fading
  Plugin::_fade = (number) ->
    $element = $(@element)
    @data = $.data this

    # Check if not currently animating and the selected slide is not the current slide
    if not @data.animating and number isnt @data.current + 1

      # Set animating to true
      $.data this, "animating", true

      # Get current slide
      currentSlide = @data.current

      if number
        # Specific slide has been called
        number = number - 1
        value = if number > currentSlide then 1 else -1
        next = number
      else
        # Next/prev slide has been called
        value = if @data.direction is "next" then 1 else -1
        next = currentSlide + value

      # Loop from first to last slide
      next = @data.total - 1 if next is -1

      # Loop from last to first slide
      next = 0 if next is @data.total

      # Set next slide pagination item to active
      @_setActive next

      # Define slides control
      slidesControl = $(".slidesjs-control", $element)

      # Setup the next slide
      slidesControl.children(":eq(" + next + ")").css
        display: "none"
        left: 0
        zIndex: 10

      # Start of the animation, call the start callback
      @options.callback.start(currentSlide + 1)

      if @options.effect.fade.crossfade
        # Fade out current slide to next slide
        slidesControl.children(":eq(" + @data.current + ")")
        .stop()
        .fadeOut @options.effect.fade.speed

        # Fade in to next slide
        slidesControl.children(":eq(" + next + ")")
        .stop()
        .fadeIn @options.effect.fade.speed, (=>
          # Reset slides
          slidesControl.children(":eq(" + next + ")").css zIndex: 0

          # Set animating to false
          $.data this, "animating", false

          # Set the new slide to the current
          $.data this, "current", next

          # End of the animation, call complete callback
          @options.callback.complete(next + 1)
        )
      else
        # Fade to next slide
        slidesControl.children(":eq(" + currentSlide + ")")
        .stop()
        .fadeOut @options.effect.fade.speed, (=>
          # Reset slides
          slidesControl.children(":eq(" + next + ")")
          .stop()
          .fadeIn @options.effect.fade.speed, (=>
            # Reset slides
            slidesControl.children(":eq(" + next + ")").css zIndex: 10
          )

          # Set animating to false
          $.data this, "animating", false

          # Set the new slide to the current
          $.data this, "current", next

          # End of the animation, call complete callback
          @options.callback.complete(next + 1)
        )

  # @_getVendorPrefix()
  # Check if the browser supports CSS3 Transitions
  Plugin::_getVendorPrefix = () ->
    body = document.body or document.documentElement
    style = body.style
    transition = "transition"

    vendor = ["Moz", "Webkit", "Khtml", "O", "ms"]
    transition = transition.charAt(0).toUpperCase() + transition.substr(1)

    i = 0

    while i < vendor.length
      return vendor[i] if typeof style[vendor[i] + transition] is "string"
      i++
    false

  # Plugin constructor
  $.fn[pluginName] = (options) ->
    @each ->
      if !$.data(@, "plugin_#{pluginName}")
        $.data(@, "plugin_#{pluginName}", new Plugin(@, options))

)(jQuery, window, document)
