###
Slick Lightbox jQuery plugin
http://mreq.github.io/slick-lightbox/

Copyright 2014-2016 mreq https://github.com/mreq

Released under the MIT license

Documentation generated by [CoffeeDoc](http://github.com/omarkhan/coffeedoc)

http://mreq.github.io/slick-lightbox/docs/
###
class SlickLightbox
  ###
  The core class.
  ###
  constructor: (element, @options) ->
    ### Binds the plugin. ###
    @$element = $(element)
    @didInit = false
    slickLightbox = this
    @$element.on 'click.slickLightbox', @options.itemSelector, (e) ->
      e.preventDefault()

      $clickedItem = $(this)
      $clickedItem.blur()

      if typeof slickLightbox.options.shouldOpen is 'function'
        return unless slickLightbox.options.shouldOpen(slickLightbox, $clickedItem, e)

      $items = slickLightbox.$element.find(slickLightbox.options.itemSelector)

      if slickLightbox.elementIsSlick()
        $items = slickLightbox.filterOutSlickClones($items)
        $clickedItem = slickLightbox.handlePossibleCloneClick($clickedItem, $items)

      slickLightbox.init $items.index($clickedItem)

  init: (index) ->
    ### Creates the lightbox, opens it, binds events and calls `slick`. Accepts `index` of the element, that triggered it (so that we know, on which slide to start slick). ###
    # @destroyPrevious()
    @didInit = true
    @detectIE()
    @createModal()
    @bindEvents()
    @initSlick(index)
    @open()

  createModalItems: ->
    ### Creates individual slides to be used with slick. If `options.images` array is specified, it uses it's contents, otherwise loops through elements' `options.itemSelector`. ###
    lazyPlaceholder = @options.lazyPlaceholder || 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'

    itemTemplate = (source, caption, lazy) ->
      if lazy == true
        imgSourceParams = """ data-lazy="#{ source }" src="#{ lazyPlaceholder }" """
      else
        imgSourceParams = """ src="#{ source }" """
      """
        <div class="slick-lightbox-slick-item">
          <div class="slick-lightbox-slick-item-inner">
            <img class="slick-lightbox-slick-img" #{ imgSourceParams } />
            #{ caption }
          </div>
        </div>"""

    if @options.images
      links = $.map @options.images, (img) =>
        itemTemplate(img, @options.lazy)

    else
      $items = @filterOutSlickClones(@$element.find(@options.itemSelector))

      length = $items.length
      createItem = (el, index) =>
        info =
          index: index
          length: length
        caption = @getElementCaption(el, info)
        src = @getElementSrc(el)
        itemTemplate(src, caption, @options.lazy)

      links = $.map $items, createItem
    links

  createModal: ->
    ### Creates a `slick`-friendly modal. ###
    links = @createModalItems()

    html = """
    <div class="slick-lightbox slick-lightbox-hide-init#{ if @isIE then ' slick-lightbox-ie' else '' }" style="background: #{ @options.background };">
      <div class="slick-lightbox-inner">
        <div class="slick-lightbox-slick slick-caption-#{ @options.captionPosition }">#{ links.join('') }</div>
      <div>
    <div>
    """
    @$modalElement = $(html)
    @$parts = {}
    @$parts['closeButton'] = $(@options.layouts.closeButton)
    @$modalElement.find('.slick-lightbox-inner').append @$parts['closeButton']
    $('body').append @$modalElement

  initSlick: (index) ->
    ### Runs slick by default, using `options.slick` if provided. If `options.slick` is a function, it gets fired instead of us initializing slick. Merges in initialSlide option. ###
    # We need to start with the `index`-th item.
    additional = { initialSlide: index }
    additional.lazyLoad = 'ondemand' if @options.lazy

    if @options.slick?
      if typeof @options.slick is 'function'
        # TODO: support element's index
        @slick = @options.slick @$modalElement
      else
        @slick = @$modalElement.find('.slick-lightbox-slick').slick $.extend({}, @options.slick, additional)
    else
      @slick = @$modalElement.find('.slick-lightbox-slick').slick(additional)
    @$modalElement.trigger 'init.slickLightbox'

  open: ->
    ### Opens the lightbox. ###
    @writeHistory() if @options.useHistoryApi
    # Fire events
    @$element.trigger 'show.slickLightbox'
    setTimeout (=> @$element.trigger 'shown.slickLightbox'), @getTransitionDuration()
    @$modalElement.removeClass('slick-lightbox-hide-init')

  close: ->
    ### Closes the lightbox and destroys it, maintaining the original element bindings. ###
    # Fire events
    @$element.trigger 'hide.slickLightbox'
    setTimeout (=> @$element.trigger 'hidden.slickLightbox'), @getTransitionDuration()
    @$modalElement.addClass('slick-lightbox-hide')
    @destroy()

  bindEvents: ->
    ### Binds global events. ###

    # Slides size needs to be 100%, which can't be achieved easily via CSS on floated elements.
    resizeSlides = =>
      h = @$modalElement.find('.slick-lightbox-inner').height()
      @$modalElement.find('.slick-lightbox-slick-item').height h
      # max-height on the image is buggy
      @$modalElement.find('.slick-lightbox-slick-img, .slick-lightbox-slick-item-inner').css 'max-height', Math.round(@options.imageMaxHeight*h)
    $(window).on 'orientationchange.slickLightbox resize.slickLightbox', resizeSlides
    if @options.useHistoryApi
      $(window).on 'popstate.slickLightbox', => @close()
    @$modalElement.on 'init.slickLightbox', resizeSlides

    # Destroy event triggered by other instances
    @$modalElement.on 'destroy.slickLightbox', => @destroy()

    # Destroy event from unslickLightbox
    @$element.on 'destroy.slickLightbox', => @destroy true

    # Close button
    @$parts['closeButton'].on 'click.slickLightbox touchstart.slickLightbox', (e) =>
      e.preventDefault()
      @close()

    # Optional bindings
    if @options.closeOnEscape or @options.navigateByKeyboard
      # Close on ESC key
      $(document).on 'keydown.slickLightbox', (e) =>
        code = if e.keyCode then e.keyCode else e.which
        if @options.navigateByKeyboard
          if code is 37
            @slideSlick 'left'
          else if code is 39
            @slideSlick 'right'
        if @options.closeOnEscape
          @close()  if code is 27

    if @options.closeOnBackdropClick
      @$modalElement.on 'click.slickLightbox touchstart.slickLightbox', '.slick-lightbox-slick-img', (e) ->
        e.stopPropagation()
      @$modalElement.on 'click.slickLightbox', '.slick-lightbox-slick-item', (e) =>
        e.preventDefault()
        @close()

  slideSlick: (direction) ->
    ### Moves the slick prev or next. ###
    if direction is 'left'
      @slick.slick 'slickPrev'
    else
      @slick.slick 'slickNext'

  detectIE: ->
    ### Detects usage of IE8 and lower. ###
    @isIE = false
    if /MSIE (\d+\.\d+);/.test(navigator.userAgent)
      ieversion = new Number(RegExp.$1) # capture x.x portion and store as a number
      if ieversion < 9
        @isIE = true

  getElementCaption: (el, info) ->
    ### Returns caption for each slide based on the type of `options.caption`. ###
    return ''  unless @options.caption
    c = switch typeof @options.caption
      when 'function'
        @options.caption(el, info)
      when 'string'
        $(el).data(@options.caption)
    return """<span class="slick-lightbox-slick-caption">#{ c }</span>"""

  getElementSrc: (el) ->
    ### Returns src for each slide image based on the type of `options.src`. ###
    return switch typeof @options.src
      when 'function'
        @options.src(el)
      when 'string'
        $(el).attr(@options.src)
      else
        el.href

  unbindEvents: ->
    ### Unbinds global events. ###
    $(window).off '.slickLightbox'
    $(document).off '.slickLightbox'
    @$modalElement.off '.slickLightbox'

  destroy: (unbindAnchors = false) ->
    ### Destroys the lightbox and unbinds global events. If `true` is passed as an argument, unbinds the original element as well. ###
    if @didInit
      @unbindEvents()
      # Let transitions take effect
      setTimeout (=>
        @$modalElement.remove()
      ), @options.destroyTimeout
    if unbindAnchors
      @$element.off '.slickLightbox'
      @$element.off '.slickLightbox', @options.itemSelector

  destroyPrevious: ->
    ### Destroys lightboxes currently in DOM. ###
    $('body').children('.slick-lightbox').trigger 'destroy.slickLightbox'

  getTransitionDuration: ->
    ### Detects the transition duration to know when to remove stuff from DOM etc. ###
    return @transitionDuration  if @transitionDuration
    duration = @$modalElement.css('transition-duration')
    if typeof duration is 'undefined'
      @transitionDuration = 500
    else
      @transitionDuration = if duration.indexOf('ms') > -1 then parseFloat(duration) else parseFloat(duration) * 1000

  writeHistory: ->
    ### Writes an empty state to the history API if supported. ###
    history?.pushState?(null, null, '')

  filterOutSlickClones: ($items) ->
    ### Removes all slick clones from the set of elements. Only does so, if the target element is a slick slider. ###
    return $items unless @elementIsSlick()
    $items = $items.filter ->
      $item = $(this)
      not $item.hasClass('slick-cloned') and $item.parents('.slick-cloned').length is 0

  handlePossibleCloneClick: ($clickedItem, $items) ->
    return $clickedItem unless @elementIsSlick()
    return $clickedItem unless $clickedItem.closest('.slick-slide').hasClass('slick-cloned')
    href = $clickedItem.attr('href')
    $items.filter(-> $(this).attr('href') is href).first()

  elementIsSlick: ->
    @$element.hasClass('slick-slider')

# jQuery defaults
defaults =
  background: 'rgba(0,0,0,.8)'
  closeOnEscape: true
  closeOnBackdropClick: true
  destroyTimeout: 500
  itemSelector: 'a'
  navigateByKeyboard: true
  src: false
  caption: false
  captionPosition: 'dynamic'
  images: false
  slick: {}
  useHistoryApi: false
  layouts:
    closeButton: """<button type="button" class="slick-lightbox-close"></button>"""
  shouldOpen: null
  imageMaxHeight: 0.9
  lazy: false

# jQuery methods
$.fn.slickLightbox = (options) ->
  ### Fires the plugin. ###
  options = $.extend {}, defaults, options
  $(this).each ->
    this.slickLightbox = new SlickLightbox this, options
  return this

$.fn.unslickLightbox = ->
  ### Removes everything. ###
  $(this).trigger('destroy.slickLightbox').each ->
    this.slickLightbox = null
