React = require 'react'
_ = require 'lodash'
{Flux} = require 'delorean'

animationMixin = require '../mixins/animation_mixin'

{div, p, button} = React.DOM

NIB_OFFSET = 12
EDGE_BUFFER = 4

###
This class wraps any passed react component in a PVR
It is intend to simplify creating any PVr within app, by handling the positioning and direction of the PVR

Popover Props

@props.element - a react element (not factory) to render inside the pvr

@props.direction - string - default 'auto'
  below, above, right, left
  detirmines on which side of the anchor element the pvr will appear 
    - in 'auto' mode below, above, right, left is the order they are checked
    - should the pvr not fit in the direction provided, the oposite sirection will be used
    - should the pvr not fit in the opposite direction, the other 2 will be checked
    - should the pvr not fit in any direction, the direction with the most space will be used

@props.height - default 100
  height of the pvr 

@props.width default 200
  width of the pvr

@props.styleMixin
  object containing any style properties to mixin with and/or overrride the defaults
  note that width height are passed separately so they can have defaults and auto settings
  passing width/height in this object could cause issues

@props.anchor - DOM element
  pass then e.currentTarget or e.target element through to enable auto-positioning
  if this isn't passed, you'll have to manually configure positioning with styleMixin or CSS

@props.vAdjust - default 0
  for above, below positioning
    negative value moves the pvr closer to the anchor element, positive value moves it further away
  for left, right
    negative value moves pvr higher on the screen, positive value moves it lower

@props.vSlide - default 0
  leave the nib in the same place, but offsets the pvr body by this number of pixels

@props.hAdjust - default 0
  for above, below positioning
    negative value moves the pvr further left, positive value moves it further right
  for left, right
    negative value moves the pvr closer to the anchor element, positive value moves it further away

@props.hSlide - default 0
  leave the nib in the same place, but offsets the pvr body by this number of pixels

@props.nibColor - default white
  the color of the nib

@props.borderColor - default rgb(235,235,235)
  the color of pvr border

@props.updateOnKeys - default none
  any keystrokes that should cause a rerender of the pvr

@props.closeOnAnchorLeave - default yes
  whether or not the pvr should close if the anchor element moves offscreen during a resize or key stroke (of a key in updateOnKeys)
###

Pvr = React.createClass
  
  displayName: 'Pvr'

  mixins: [animationMixin, Flux.mixins.storeListener]

  watchStores: ['popoverModal']

  enterStateStart:
    scale: .9
  enterStateEnd:
    scale: 1
  enterEasing: 'easeOutElastic'
  enterDuration: 600
    

  getDefaultProps: ->
    {
      height: 100
      width: 200
      styleMixin: {}
      Adjust: 0
      vAdjust: 0
      vSlide: 0
      hAdjust: 0
      hSlide: 0
      anchor: {
        getBoundingClientRect: ->
          top: 0
          right: 0
          bottom: 0
          left: 0

      }
      element: null
      direction: 'auto'
      nibColor: 'white'
      borderColor: 'rgb(235,235,235)'
      updateOnKeys: []
      closeOnAnchorLeave: yes

    }


  componentWillMount: ->
    {direction} = @props
    @directionCheck = switch direction
      when 'left' then ['left', 'right', 'below', 'above']
      when 'right' then ['right', 'left', 'below', 'above']
      when 'below' then ['below', 'above', 'right', 'left']  
      when 'above' then ['above', 'below', 'right', 'left'] 
      else ['below', 'above', 'right', 'left']  

    window.addEventListener("resize", @handleResize)
    window.addEventListener("keydown", @handleKeyStroke)



  componentWillUnmount: ->
    window.removeEventListener("resize", @handleResize)
    window.removeEventListener("keydown", @handleKeyStroke)


  handleKeyStroke: (e) ->
    {keyCode} = e
    {updateOnKeys, closeOnAnchorLeave} = @props

    return unless keyCode in updateOnKeys 

    if @anchorIsOffscreen() and closeOnAnchorLeave then @trigger 'closeModal'
    else @forceUpdate()

  handleResize: ->
    {closeOnAnchorLeave} = @props
    if @anchorIsOffscreen() and closeOnAnchorLeave then @trigger 'closeModal'
    else @forceUpdate()

  anchorIsOffscreen: ->
    {top, left, right, bottom} = @props.anchor.getBoundingClientRect()
    
    if top > window.innerHeight or left > window.innerWidth or bottom < 0 or right < 0 then yes
    else no

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

  calculateOptimalDirection: (elPos) ->
    {innerHeight, innerWidth} = window
    {preferredDirection, width, height} = @props
    {right, left, top, bottom} = elPos

    # left and top are already the space to the left and the top of the anchor
    # transform right and bottom to the space to the right and bottom of the anchor
    right = innerWidth - right 
    bottom = innerHeight - bottom

    # Check if the popover fits in the preferred direction
    widthNeeded = width + NIB_OFFSET + EDGE_BUFFER
    heightNeeded = height + NIB_OFFSET + EDGE_BUFFER
    
    for direction in @directionCheck
      switch direction
        when 'left'
          if widthNeeded < left then return 'left'
        when 'right'
          if widthNeeded < right then return 'right'
        when 'below'
          if heightNeeded < bottom then return 'below'
        when 'above'
          if heightNeeded < top then return 'above'

    # If the popover will not fit in any of the the preferred directions
    # return the direction with the most space
    max = _.max [left, top, bottom, right]
    
    switch max
      when left then 'left'
      when right then 'right'
      when top then 'above'
      when bottom then 'below'


  render: ->
    {width, height, styleMixin, position, element, vAdjust, hAdjust, anchor, direction, nibColor, borderColor, vSlide, hSlide} = @props
    
    scale = @props.scale or @state.scale

    elPos = anchor.getBoundingClientRect()
    anchorTop = elPos.top
    anchorbottom = elPos.bottom
    anchorLeft = elPos.left
    anchorRight = elPos.right
    anchorWidth = elPos.width
    anchorHeight = elPos.height

    direction = @calculateOptimalDirection elPos

    className = "pvr #{direction}"
    nibClass = "nib #{direction}"
    nibBorderClass = "nib border #{direction}"
    nibPosition = NIB_OFFSET/2
    
    # When adjusting the nib center point up/down, don't exceed half the height minus border radius
    maxNibVerticalAdjust = height/2 - NIB_OFFSET/2 - 5
    # When adjusting the nib center point left/right, don't exceed half the width minus border radius
    maxNibHorizontalAdjust = width/2 - NIB_OFFSET/2 - 5
    
    switch direction
      when 'left'
        top = anchorTop + anchorHeight/2 - height/2 + vAdjust - vSlide
        left = anchorLeft - NIB_OFFSET - width - hAdjust

        # Make sure the pvr is not off the screen to the bottom
        topAdjust = top + height + EDGE_BUFFER - window.innerHeight
        if topAdjust > 0
          top = top - topAdjust - vSlide
          nibPosition = NIB_OFFSET/2 - Math.min(topAdjust, maxNibVerticalAdjust)
          
        # Make sure the pvr is not off the screen to the top
        topAdjust = top - EDGE_BUFFER
        if topAdjust < 0
          top = top - topAdjust - vSlide 
          nibPosition = NIB_OFFSET/2 - Math.min(topAdjust, maxNibVerticalAdjust)
        
        nibPosition -= vSlide
        nibStyle =
          borderColor: "transparent transparent transparent #{nibColor}"
          top: "calc(50% - #{nibPosition}px)"

        nibBorderStyle = 
          borderColor: "transparent transparent transparent #{borderColor}"
          top: "calc(50% - #{nibPosition}px)"
          right: 0 - NIB_OFFSET - 1

      when 'right'
        top = anchorTop + anchorHeight/2 - height/2 + vAdjust - vSlide
        left = anchorRight + NIB_OFFSET + hAdjust

        # Make sure the pvr is not off the screen to the bottom
        topAdjust = top + height + EDGE_BUFFER - window.innerHeight
        if topAdjust > 0
          top = top - topAdjust - vSlide
          nibPosition = NIB_OFFSET/2 - Math.min(topAdjust, maxNibVerticalAdjust)
          
        # Make sure the pvr is not off the screen to the top
        topAdjust = top - EDGE_BUFFER
        if topAdjust < 0
          top = top - topAdjust - vSlide
          nibPosition = NIB_OFFSET/2 - Math.min(topAdjust, maxNibVerticalAdjust) 

        nibPosition -= vSlide
        nibStyle =
          borderColor: "transparent #{nibColor} transparent transparent"
          top: "calc(50% - #{nibPosition}px)"

        nibBorderStyle =
          borderColor: "transparent #{borderColor} transparent transparent"
          top: "calc(50% - #{nibPosition}px)"
          left: 0 - NIB_OFFSET - 1

      when 'below'
        top = anchorTop + anchorHeight + NIB_OFFSET + vAdjust
        left = anchorLeft + anchorWidth/2 - width/2 + hAdjust - hSlide

        # Make sure the pvr is not off the screen to the right
        leftAdjust = left + width + EDGE_BUFFER - window.innerWidth
        if leftAdjust > 0
          left = left - leftAdjust - hSlide
          nibPosition = NIB_OFFSET/2 - Math.min(leftAdjust, maxNibHorizontalAdjust)
          
        # Make sure the pvr is not off the screen to the left
        leftAdjust = left - EDGE_BUFFER
        if leftAdjust < 0
          left = left - leftAdjust - hSlide
          nibPosition = NIB_OFFSET/2 - Math.min(leftAdjust, maxNibHorizontalAdjust)

        nibPosition -= hSlide
        nibStyle =
          borderColor: "transparent transparent #{nibColor} transparent"
          left: "calc(50% - #{nibPosition}px)"

        nibBorderStyle =
          borderColor: "transparent transparent #{borderColor} transparent"
          left: "calc(50% - #{nibPosition}px)"
          top: 0 - NIB_OFFSET - 1

      when 'above'
        top = anchorTop - NIB_OFFSET - height - vAdjust
        left = anchorLeft + anchorWidth/2 - width/2 + hAdjust - hSlide

        # Make sure the pvr is not off the screen to the right
        leftAdjust = left + width + EDGE_BUFFER - window.innerWidth
        if leftAdjust > 0
          left = left - leftAdjust - hSlide
          nibPosition = NIB_OFFSET/2 - Math.min(leftAdjust, maxNibHorizontalAdjust)
          
        # Make sure the pvr is not off the screen to the left
        leftAdjust = left - EDGE_BUFFER
        if leftAdjust < 0
          left = left - leftAdjust - hSlide
          nibPosition = NIB_OFFSET/2 - Math.min(leftAdjust, maxNibHorizontalAdjust)


        nibPosition -= hSlide
        nibStyle =
          borderColor: "#{nibColor} transparent transparent  transparent"
          left: "calc(50% - #{nibPosition}px)"

        nibBorderStyle =
          borderColor: " #{borderColor} transparent transparent transparent"
          left: "calc(50% - #{nibPosition}px)"
          bottom: 0 - NIB_OFFSET - 1

      

    element = element or div {key: 'inner', className: 'inner'}

    # Set the styles
    style = 
      height: height
      width: width
      top: top
      left: left
      borderColor: borderColor
      transform: "scale(#{scale})"
      WebkitTransform: "scale(#{scale})"
      msTransform: "scale(#{scale})"

    _.assign style, styleMixin

    div {
      className: className
      style: style
      onClick: @handleClick
    }, [
      div {
        key: 'nibBorder'
        className: nibBorderClass
        style: nibBorderStyle
      }
      div {
        key:'nibInner'
        className: nibClass
        style: nibStyle
      }
      element
    ] 

module.exports = Pvr