#
# Wire
# Copyright (C) 2016 Wire Swiss GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/.
#

window.Namespace 'zeta.webapp.module'


class zeta.webapp.module.Bubble
  constructor: (options) ->

    # default options
    options = $.extend
      host_selector: undefined
      scroll_selector: undefined
      enable_click: false
      modal: true
      offset: undefined
      arrow_size: 16
      resize: true
      on_show: ->
      on_hide: ->
    , options

    # set options as public members
    $.extend @, options

    @init()

    if not @elements_are_present()
      console.warn "Cannot create bubble for '#{@host_selector}'. Selector not found."
      @observer?.disconnect()
      return

    if @enable_click
      @host.on 'mousedown', @on_click

    @observer = new MutationObserver (mutations) =>
      @recalculate()

    @host_observer = new MutationObserver (mutations) =>
      @recalculate()

  elements_are_present: ->
    are_present = true
    host = $(@host_selector)
    bubble = $(host.data 'bubble')
    if host.length is 0
      are_present = false
    if bubble.parent().get(0) is undefined
      are_present = false
    return are_present

  apply_offsets: ->
    if @offset
      if @offset.left
        position_left = @bubble.position().left
        reposition_left = position_left + @offset.left
        @bubble.css 'left', reposition_left
      return true
    else
      return false

  init: ->
    @host = $(@host_selector)
    @bubble = $(@host.data 'bubble')
    @placement = @host.data('placement') or 'top'
    @arrow_diagonal = Math.round(Math.sqrt @arrow_size * @arrow_size * 2)
    @class = ''

    @bubble.removeClass 'bubble-top bubble-left bubble-right bubble-bottom bubble-bottom-right'
    @bubble.css 'top', 'auto'
    @bubble.css 'right', 'auto'
    @bubble.css 'bottom', 'auto'
    @bubble.css 'left', 'auto'

    if @placement in ['top', 'vertical']
      @class = 'bubble-bottom'
    if @placement == 'bottom'
      @class = 'bubble-top'
    if @placement == 'left'
      @class = 'bubble-right'
    if @placement == 'right'
      @class = 'bubble-left'
    if @placement == 'top-left'
      @class = 'bubble-bottom-right'
    if @placement == 'bottom-left'
      @class = 'bubble-top-right'
    return true

  recalculate: ->
    position = @host.get(0)?.getBoundingClientRect()
    host_width = @host.outerWidth()
    host_height = @host.outerHeight()

    return @hide() if not position or host_width is 0 or host_height is 0

    host_left = position.left
    host_top = position.top
    host_bottom = host_top + host_height
    host_right = host_left + host_width

    bubble_width = @bubble.outerWidth()
    bubble_height = @bubble.outerHeight()
    parent_offset = @bubble.parent().get(0).getBoundingClientRect()

    window_height = $(window).innerHeight()

    if @placement == 'top'
      @bubble.css 'top', host_top - @arrow_diagonal / 2 - bubble_height - parent_offset.top
      @bubble.css 'left', host_left + (host_width - bubble_width) / 2 - parent_offset.left

    if @placement == 'bottom'
      @bubble.css 'top', host_bottom + @arrow_diagonal / 2 - parent_offset.top
      @bubble.css 'left', host_left + (host_width - bubble_width) / 2 - parent_offset.left

    if @placement == 'right'
      @bubble.css 'top', host_top + (host_height - bubble_height) / 2 - parent_offset.top
      @bubble.css 'left', host_right + @arrow_diagonal / 2 - parent_offset.left

    if @placement == 'left'
      @bubble.css 'top', host_top + (host_height - bubble_height) / 2 - parent_offset.top
      @bubble.css 'left', host_left - bubble_width - @arrow_diagonal / 2 - parent_offset.left

    if @placement == 'top-left'
      @bubble.css 'top', host_top - @arrow_diagonal / 2 - bubble_height - parent_offset.top
      @bubble.css 'left', host_left - (bubble_width - host_width) - parent_offset.left

    if @placement == 'bottom-left'
      @bubble.css 'top', host_bottom + @arrow_diagonal / 2 - parent_offset.top
      @bubble.css 'left', host_left - (bubble_width - host_width) - parent_offset.left

    if @placement == 'vertical'
      @bubble.css 'top', host_top - @arrow_diagonal / 2 - bubble_height - parent_offset.top
      @bubble.css 'left', host_left + (host_width - bubble_width) / 2 - parent_offset.left

    if @placement == 'vertical'
      distance_from_top = host_top - @arrow_diagonal / 2 - bubble_height

      @bubble.css 'left', host_left + (host_width - bubble_width) / 2 - parent_offset.left
      if distance_from_top >= 0
        # Show the bubble on the top
        @bubble.css 'top', host_top - @arrow_diagonal / 2 - bubble_height - parent_offset.top
        @bubble.addClass('bubble-bottom').removeClass 'bubble-top'
      else
        # Show the bubble on the bottom
        @bubble.css 'top', host_bottom + @arrow_diagonal / 2 - parent_offset.top
        @bubble.addClass('bubble-top').removeClass 'bubble-bottom'

    if @placement == 'right-flex'
      host_midpoint = host_top + host_height / 2

      if host_midpoint < bubble_height / 2
        @bubble.css 'top', host_top - parent_offset.top
        @bubble.addClass('bubble-left-top').removeClass 'bubble-left bubble-left-bottom'
      else if host_midpoint + bubble_height / 2 > window_height
        @bubble.css 'top', host_bottom - bubble_height - parent_offset.top
        @bubble.addClass('bubble-left-bottom').removeClass 'bubble-left bubble-left-top'
      else
        @bubble.css 'top', host_top + (host_height - bubble_height) / 2 - parent_offset.top
        @bubble.addClass('bubble-left').removeClass 'bubble-left-top bubble-left-bottom'

      @bubble.css 'left', host_right + @arrow_diagonal / 2 - parent_offset.left
    return true

  show: ->
    @init()
    @recalculate()
    @apply_offsets()

    # clean up bubble
    @bubble.off 'transitionend'

    return new Promise (resolve, reject) =>
      # observer is only checking the child nodes,
      # to avoid mutation event firing on resize/recalculation
      return reject() if not @bubble[0]
      @observer.observe @bubble[0],
        childList: true,
        subtree: true

      return reject() if not @host.parent()[0]
      @host_observer.observe @host.parent()[0],
        childList: true,
        subtree: true

      setTimeout =>
        return reject() if not @bubble[0]
        @bubble.addClass "bubble-show #{@class}"

        if @resize
          $(window).on 'resize', @on_window_resize

        if @modal
          $(window).one 'mousedown', @on_window_click
          @bubble.on 'mousedown', @on_cancel_click

        if @scroll_selector?
          $(@scroll_selector).one 'mousewheel', @on_scroll

        @on_show()

        setTimeout =>
          if not @bubble[0]
            return @hide().then -> reject()

          @bubble.addClass 'bubble-animation-show'
          resolve()
        , 10

    , 10

  hide: ->
    # clean up events
    $(window).off 'mousedown', @on_window_click
    @bubble.off 'mousedown', @on_cancel_click
    $(window).off 'resize', @on_window_resize
    $(@scroll_selector).off 'scroll', @on_scroll
    @observer?.disconnect()

    return new Promise (resolve) =>
      @bubble
        .off 'transitionend'
        .removeClass 'bubble-animation-show'
        .one 'transitionend', =>
        @bubble.removeClass "bubble-show #{@class}"
        @on_hide()
        resolve()

  toggle: ->
    if @is_visible()
      @hide()
    else
      @show()

  on_click: (e) =>
    e.stopPropagation()
    @toggle()

  on_window_click: (e) =>
    e.stopPropagation()
    @hide()

  on_cancel_click: (e) ->
    e.stopPropagation()

  on_window_resize: =>
    @recalculate() if @is_visible()

  on_scroll: =>
    @hide()

  is_visible: =>
    $(@host.data 'bubble').hasClass 'bubble-show'
