React = require 'react'
createClass = require 'create-react-class'
PropTypes = require 'prop-types'
{StyleSheet, css} = require 'aphrodite/no-important'
assign = require 'lodash/assign'

dialogueMixin = require '../mixins/dialogue_mixin'

{ESCAPE, KEY_S} = require '../constants/keyboard'

ConfirmSave = React.createFactory(require('./confirm_save'))
Spinner = React.createFactory(require('./spinner'))

{div, header, span, button} = require 'react-dom-factories'

###&
  @props.title - OPTIONAL - [String]
  title for the modal header

  @props.buttons - OPTIONAL - [Array]
  Array of button objects with name, handler to be called on click, and disabled boolean, eg...
  ```
  [
    {
      name: 'Save'
      handler: @save
      disabled: no
    }
  ]
  ```

  @props.children - REQUIRED - [Element]
  React element (or array of elements) to inserted as the modal body

  @props.styleOverride - OPTIONAL - [Object] 
  aphrodite style object, optionally can contain .modal, .header, .title, .actionButton class defs, eg...
  ```
  {
    modalBase: {}
    modalEnter: {}
    modal: {}
    header: {} 
    title: {}
    actionButton: {}
  }
  ```

  @props.close - OPTIONAL - [Function]
  Function that closes the overlay, passed automatically by the overlay framework

  @props.onClose - OPTIONAL - [Function]
  Function that is called right before the modal closes

  @props.showClose - OPTIONAL - [Boolean]
  Defaults to yes, set it to no to not show the close button

  @props.closeAfterSave - OPTIONAL - [Boolean]
  Defaults to yes, set it to no to prevent the modal from calling @props.close after a save is complete
  Note: in this case you MUST pass an onSaveComplete handler that sets the saveState to null after a save

  @props.onSaveComplete - OPTIONAL - [Function]
  Function that is called right after the saveState is set to complete and the success indicator finishes animating

  @props.closeBtnText - OPTIONAL - [String]
  Defaults to 'Cancel', text to display in the close button

  @props.animateIn - OPTIONAL - [Boolean]
  Defaults to yes, whether or not to bouce the modal on enter

  @props.loading - OPTIONAL - [Boolean]
  Defaults to no, whether or not to show the spinner instead of the children

  @props.draggable - OPTIONAL - [Boolean]
  Defaults to yes, whether or not the modal can be dragged from it's header
  
  @props.stopPropagation - OPTIONAL - [Boolean]
  Defaults to yes, whether or not the modal stop click from bubbling above it

  @props.displayProgressBar - OPTIONAL - [Boolean]
  Defaults to no, whether or not the confirm/save show the progress bar instead of the spinner

  @props.uploadProgress -OPTIONAL
  Progress of a file being uploaded

&###

Modal = createClass
  
  displayName: 'Modal'

  mixins: [dialogueMixin]

  propTypes:
    styleOverride: PropTypes.shape
      modal: PropTypes.object
      header: PropTypes.object
      title: PropTypes.object
      actionButton: PropTypes.object
    title: PropTypes.string
    buttons: PropTypes.array
    close: PropTypes.func.isRequired
    onClose: PropTypes.func
    onSaveComplete: PropTypes.func
    showClose: PropTypes.bool
    closeAfterSave: PropTypes.bool
    closeBtnText: PropTypes.string
    unSavedMessage: PropTypes.string
    unSavedDialogueHeight: PropTypes.number
    unSavedChanges: PropTypes.bool
    onSaveFail: PropTypes.func
    inLineStyle: PropTypes.object
    animateIn: PropTypes.bool
    loading: PropTypes.bool
    draggable: PropTypes.bool
    stopPropagation: PropTypes.bool
    spinnerProps: PropTypes.object
    displayProgressBar: PropTypes.bool
    uploadProgress: PropTypes.oneOfType [
      PropTypes.string
      PropTypes.number
    ]
    

  getDefaultProps: ->
    styleOverride: {}
    inLineStyle: {}
    buttons: []
    showClose: yes
    closeAfterSave: yes
    unSavedDialogueHeight: 100
    saveState: null
    saveMessage: null
    unSavedChanges: no
    animateIn: yes
    loading: no
    draggable: yes
    stopPropagation: yes
    spinnerProps:
      length: 7
      radius: 7
      lines: 12
      width: 2
    displayProgressBar: no
    uploadProgress: ''


  getInitialState: ->
    dragX: 0
    dragY: 0
  
  componentWillMount: ->
    # This is necessary becasue translation is not available on app load
    # So this cannot live in default props
    @closeBtnText = t 'Cancel'

    document.addEventListener 'keydown', @handleKeyPress


  componentDidMount: ->

    setTimeout =>
      do @calculateMaxDrags
    , 0

    window.addEventListener 'resize', @calculateMaxDrags


  componentWillUnmount: ->
    document.removeEventListener 'keydown', @handleKeyPress
    window.removeEventListener 'resize', @calculateMaxDrags

  
  render: ->
    {styleOverride, title, buttons, closeBtnText, showClose, children, saveState, saveMessage, 
    onSaveFail, inLineStyle, animateIn, loading, spinnerProps, draggable, stopPropagation, displayProgressBar, uploadProgress} = @props
    {dragX, dragY} = @state

    closeBtnText = closeBtnText or @closeBtnText

    assign styles, styleOverride

    inLineStyle = assign {}, inLineStyle,
      transform: "translate(#{dragX}px, #{dragY}px) translateZ(0px)"
      WebkitTransform: "translate(#{dragX}px, #{dragY}px) translateZ(0px)"
      msTransform: "translate(#{dragX}px, #{dragY}px)"

    assign spinnerProps,
      key: 'spinner'

    headerChildren = []
    
    # Modal Title
    headerChildren.push span {
      key: 'title'
      className: css(styles.title)
    }, title if title?

    # Close Button
    headerChildren.push button {
      key: 'close'
      className: css(styles.actionButton)
      onClick: @closeWithCheck
    }, closeBtnText if showClose

    # Other buttons
    headerChildren.push button {
      key: b.name
      className: css(styles.actionButton)
      onClick: b.handler
      disabled: b.disabled
    }, b.name for b in buttons by -1

    animateStyle = if animateIn then styles.modalEnter else null

    div {
      className: css(styles.modalBase, animateStyle, styles.modal)
      style: inLineStyle
      onClick: if stopPropagation then @handleClick else null
    }, [
      header {
        key: 'header'
        className: css(styles.header)
        onMouseDown: if draggable then @handleMouseDown else null
        ref: (@header) =>
      }, headerChildren
      @dialogueBox()
      ConfirmSave {
        key: 'confirm'
        done: @saveComplete
        fail: -> onSaveFail?()
        saveMessage: saveMessage
        saveState: saveState
        displayProgressBar: displayProgressBar
        uploadProgress: uploadProgress
      } if saveState?
      if loading then Spinner(spinnerProps) else children
    ]


  handleClick: (e) -> do e.stopPropagation

  
  closeWithCheck: ->
    {unSavedMessage, unSavedDialogueHeight, unSavedChanges} = @props

    if unSavedChanges
      @showDialogue
        message: unSavedMessage or t 'There are unsaved changes. How do you want to proceed?'
        confirmText: t 'Discard Changes'
        height: unSavedDialogueHeight
        confirmCallback: @close
    else
      do @close

  
  close: -> 
    {close, onClose} = @props

    onClose?()

    do close
  

  saveComplete: ->
    {close, onSaveComplete, closeAfterSave} = @props

    onSaveComplete?()

    do close if closeAfterSave

  handleKeyPress: (e) ->
    {keyCode, metaKey} = e

    if keyCode is ESCAPE then do @closeWithCheck
    if keyCode is KEY_S and metaKey
      do e.preventDefault
      {buttons} = @props
      if buttons[0]?.name is t 'Save' then do buttons[0].handler


  handleMouseDown: (e) ->
    do e.preventDefault

    {dragX, dragY} = @state

    @startDragX = dragX
    @startDragY = dragY
    @startX = e.clientX
    @startY = e.clientY

    document.addEventListener 'mousemove', @handleMouseMove
    document.addEventListener 'mouseup', @handleMouseUp


  handleMouseMove: (e) ->
    do e.preventDefault

    dragX = @startDragX + (e.clientX - @startX)
    dragY = @startDragY + (e.clientY - @startY)

    dragY = if dragY < @minDragY then @minDragY else dragY
    dragY = if dragY > @maxDragY then @maxDragY else dragY

    dragX = if dragX < @minDragX then @minDragX else dragX
    dragX = if dragX > @maxDragX then @maxDragX else dragX

    @setState {dragX, dragY}

  handleMouseUp: ->
    document.removeEventListener 'mousemove', @handleMouseMove
    document.removeEventListener 'mouseup', @handleMouseUp


  calculateMaxDrags: ->
    {offsetTop, offsetLeft} = @header.parentNode
    {offsetHeight, offsetWidth} = @header
    {innerHeight, innerWidth} = window

    @maxDragX = innerWidth - (offsetLeft + offsetWidth)
    @maxDragY = innerHeight - (offsetTop + offsetHeight)

    @minDragX = 0 - offsetLeft
    @minDragY = 0 - offsetTop


enterKeyFrames =
  '0%':
    transform: 'scale(0.9)'
    opacity: '0'
  '40%':
    opacity: '1'
    transform: 'scale(1.05)'
  '100%':
    transform: 'scale(1)'

styles = StyleSheet.create
  modalBase:
    position: 'absolute'
    backgroundColor: 'white'
    borderRadius: '8px'
    overflow: 'hidden'
  modalEnter:
    animationName: [enterKeyFrames]
    animationDuration: '.15s'
    animationIterationCount: '1'
    animationTimingFunction: 'ease-out'
  modal:
    top: '10%'
    left: '50%'
    width: '600px'
    height: '500px'
    marginLeft: '-300px'
  header: 
    position: 'relative'
    width: '100%'
    height: '44px'
    lineHeight: '44px'
    backgroundColor: 'rgb(246,246,246)'
    borderBottom: '1px solid rgb(190,190,190)'
    borderTopLeftRadius: '8px'
    borderTopRightRadius: '8px'
    margin: '0px'
    cursor: 'pointer'
  title:
    display: 'inline-block'
    fontSize: '16px'
    fontWeight: 'normal'
    textAlign: 'left'
    color: 'rgb(113,113,113)'
    overflow: 'hidden'
    whiteSpace: 'nowrap'
    textOverflow: 'ellipsis'
    marginLeft: '15px'
  actionButton:
    float: 'right'
    color: 'rgb(0,127,255)'
    height: '28px'
    textAlign: 'center'
    lineHeight: '26px'
    marginTop: '8px'
    marginRight: '15px'
    fontSize: '13px'
    ':disabled':
      color: 'rgb(204,204,204)'



module.exports = Modal


