class Tao.Sortable extends TaoComponent

@tag 'tao-sortable'

@attribute 'groupSelector'

@attribute 'itemSelector', default: '[draggable="true"]'

@attribute 'axis', default: 'y'

_connected: ->
  @on 'dragstart', @itemSelector, (e) =>
    e.stopPropagation()
    $item = $ e.currentTarget
    itemRect = $item.get(0).getBoundingClientRect()
    @_startDragging $item

    e.originalEvent.dataTransfer.effectAllowed = 'move'
    e.originalEvent.dataTransfer.setData 'text/plain', 'tao-sortable'
    e.originalEvent.dataTransfer.setDragImage(
      @_sortingItem.get(0),
      e.clientX - itemRect.left,
      e.clientY - itemRect.top
    )
    null

_disconnected: ->
  @off()
  $(document).off ".tao-sortable-#{@taoId}"

_startDragging: ($item) ->
  @namespacedTrigger 'beforeSortStart', [$item]
  @_sortingItem = $item
  setTimeout -> $item.addClass('tao-sortable-sorting')
  @namespacedTrigger 'sortStart', [@_sortingItem]

  $(window).on "scroll.tao-sortable-#{@taoId}", _.throttle (e) =>
    @_expireDimensionCache()
    null
  , 100

  $(document).on "dragover.tao-sortable-#{@taoId}", _.throttle (e) =>
    e.preventDefault()
    @_performDragging x: e.clientX, y: e.clientY
    e.originalEvent.dataTransfer.dropEffect = 'move'
    null
  , 100

  $(document).on "dragend.tao-sortable-#{@taoId}", (e) =>
    e.preventDefault()
    @_stopDragging()
    $(document).off ".tao-sortable-#{@taoId}"
    $(window).off ".tao-sortable-#{@taoId}"
    null

_performDragging: (mousePosition) ->
  return unless @_sortingItem
  item = @_findNearestItem mousePosition
  return unless item && item.el.get(0) != @_sortingItem.get(0)

  @_moveItem item, mousePosition
  @_expireDimensionCache()

_stopDragging: ->
  @_sortingItem.removeClass('tao-sortable-sorting')
  @namespacedTrigger 'sortEnd', [@_sortingItem]
  @_sortingItem = null

_findNearestItem: (mousePosition, dimensions = @_itemDimensions()) ->
  minDistance = null
  nearestItem = null

  dimensions.forEach (item) =>
    distance = @_getDistanceFromItem item, mousePosition
    if _.isNil(minDistance) || minDistance > distance
      minDistance = distance
      nearestItem = item

  if nearestItem.type == 'group' && nearestItem.items &&
      nearestItem.items.length > 0
    nearestItem = @_findNearestItem mousePosition, nearestItem.items

  nearestItem

_moveItem: (item, mousePosition) ->
  if item.type == 'group'
    return if @triggerHandler('tao:sorting', [@_sortingItem, item]) == false
    item.el.append @_sortingItem
  else
    method = if (@axis == 'y' && mousePosition.y > item.center.y) ||
        (@axis == 'x' && mousePosition.x > item.center.x)
      'after'
    else
      'before'
    return if @triggerHandler('tao:sorting', [@_sortingItem, item, method]) == false
    item.el[method] @_sortingItem

_itemDimensions: ->
  @_dimentions ||= do =>
    if @groupSelector
      @jq.find(@groupSelector).map (i, groupEl) =>
        @_getGroupDimension groupEl
      .get()
    else
      @jq.find(@itemSelector).map (i, itemEl) =>
        @_getItemDimension itemEl
      .get()

_getGroupDimension: (groupEl) ->
  $group = $ groupEl
  dimension = $group.get(0).getBoundingClientRect()

  type: 'group'
  el: $group
  dimension: dimension
  center: @_getCenterPointOfDimension(dimension)
  items: $group.find(@itemSelector).map (i, itemEl) =>
    @_getItemDimension itemEl
  .get()

_getItemDimension: (itemEl) ->
  $item = $ itemEl
  dimension = $item.get(0).getBoundingClientRect()

  type: 'item'
  el: $item
  dimension: dimension
  center: @_getCenterPointOfDimension(dimension)

_getCenterPointOfDimension: (dimension) ->
  x: dimension.left + (dimension.width / 2)
  y: dimension.top + (dimension.height / 2)

_getDistanceFromItem: (item, position) ->
  rect = item.dimension
  if position.x >= rect.left && position.x <= rect.right &&
      position.y >= rect.top && position.y <= rect.bottom
    0
  else
    deltaX = Math.min(Math.abs(position.x - rect.left), Math.abs(position.x - rect.right))
    deltaY = Math.min(Math.abs(position.y - rect.top), Math.abs(position.y - rect.bottom))
    Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2))

_expireDimensionCache: ->
  @_dimentions = null

TaoComponent.register Tao.Sortable