React = require 'react'
PropTypes = require 'prop-types'
createClass = require 'create-react-class'
assign = require 'lodash/assign'
omit = require 'lodash/omit'
find = require 'lodash/find'
last = require 'lodash/last'
moment = require 'moment'
DatePicker = React.createFactory(require '../date_picker')
SelectInput2 = React.createFactory(require '../select_input_2')

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

###&
@general - 
  This component passes all props forward to datepicker, see datepicker component for it's props.

@props.formLabel - OPTIONAL - [string]
  The value of the label that will display to the left of the input

@props.className - OPTIONAL - [string] - default 'grid grid-pad'
  optional class to be added the main div

@props.labelColumnClass - OPTIONAL - [string]
  optional class that will be added to the label column

@props.inputColumnClass - OPTIONAL - [string]
  optional class that will be added to the input column

@props.isFieldRequired - OPTIONAL - [boolean]
  optional boolean that will display the red asterisk if true

@props.fullRow - OPTIONAL - [boolean]
  optional boolean that will determine whether to display the input in a full row with or without a label

@props.enableTime - OPTIONAL - [boolean]
  includes the time picker

@props.maxDate - OPTIONAL - [moment]
  when using the time picker it will disable times of day after this value

@props.minDate - OPTIONAL - [moment]
  when using the time picker it will disable times of day before this value

@props.children - OPTIONAL - [array]
  optional array of children 
&###

GridFormDatePicker = createClass
  
  displayName: 'GridFormDatePicker'

  propTypes:
    formLabel: PropTypes.string
    className: PropTypes.string
    labelColumnClass: PropTypes.string
    inputColumnClass: PropTypes.string
    isFieldRequired: PropTypes.bool
    fullRow: PropTypes.bool
    enableTime: PropTypes.bool
    twentyFourHour: PropTypes.bool
    selected: PropTypes.object
    minutes: PropTypes.array
    AmPm: PropTypes.array
    isInPopover: PropTypes.bool
    disabled: PropTypes.bool
    onChange: PropTypes.func
    timeColumns: PropTypes.number
    returnDateString: PropTypes.string
    maxDate: PropTypes.object
    minDate: PropTypes.object

  getDefaultProps: ->
    labelColumnClass: 'col-3-12'
    inputColumnClass: 'col-9-12' 
    className: 'grid grid-pad'
    isFieldRequired: no
    fullRow: yes
    enableTime: no
    twentyFourHour: no
    minutes: [0,15,30,45]
    AmPm: ['am', 'pm']
    isInPopover: no
    disabled: no
    timeColumns: 2

  componentDidMount: -> do @changeOnOutOfRange
  componentDidUpdate: -> do @changeOnOutOfRange

  changeOnOutOfRange: ->
    {selected, maxDate, minDate} = @props

    if selected? and (maxDate? or minDate?)
      # If the selected date is out of range, the controls will be updated to a value different than the passed selected date
      # In this case onChange must be fired to ensure the parent form has the value displayed in the form
      propsValue = @getMomentObject(selected)
      controlValue = @getMomentObject(@getValue())

      do @handleChange if not propsValue.isSame(controlValue, 'minute')
  
  render: ->
    {formLabel, className, labelColumnClass, inputColumnClass, inputTextClass, isFieldRequired, selected,
    fullRow, enableTime, twentyFourHour, tabId, children, isInPopover, tabIndex, disabled, onChange, jsonPath} = @props

    if formLabel?
      labelClass = 'form-label'
      if isFieldRequired then labelClass += ' is-required'

      labelField = div {
        key: 'label'
        className: labelColumnClass
      }, label {className: labelClass}, formLabel

    # we do not want to pass the className down as it will mess up the style
    inputProps = omit(assign({}, @props, {ref: 'input'}), ['className'])
    inputProps.onChange = @handleChange

    input = DatePicker inputProps

    if enableTime
      classes = @getCellClasses(inputColumnClass)
      {hour, minute, ampm} = @parseTime(selected)

      hourOptions = @getHourOptions {minute, ampm}

      inputCell = [
        div {
          key: 'picker'
          className: classes[0]
        }, input
        div {
          key: 'time1'
          className: classes[1]
        }, [
          SelectInput2 {
            ref: 'hour'
            key: 'hour'
            tabId: tabId
            wrapperClass: 'hour'
            value: hour or ''
            options: hourOptions
            onChange: @handleChange
            disabled: disabled
            isInPopover: isInPopover
            tabIndex: tabIndex
            jsonPath: jsonPath
          }
          span {
            key: 'divide'
            className: 'time-divide'
          }, ':'
          SelectInput2 {
            ref: 'minute'
            key: 'minute'
            wrapperClass: 'minutes'
            tabId: tabId
            value: minute or ''
            options: @getMinuteOptions {hour, ampm}, hourOptions
            onChange: @handleChange
            disabled: disabled
            isInPopover: isInPopover
            tabIndex: tabIndex
            jsonPath: jsonPath
          }
          SelectInput2 {
            ref: 'ampm'
            key: 'ampm'
            wrapperClass: 'ampm'
            tabId: tabId
            value: ampm or ''
            options: do @getAmPmOptions
            onChange: @handleChange
            disabled: disabled
            isInPopover: isInPopover
            tabIndex: tabIndex
            jsonPath: jsonPath
          } unless twentyFourHour
        ]
      ]


    else
      inputCell = div {
        key: 'input'
        className: inputColumnClass
      }, input

    # This is a full row of a form with a label
    if fullRow and formLabel?
      content = [
        labelField
        inputCell
      ].concat children

      div {
        className: className
      }, content
    # This is a full row w/ out a label
    else if fullRow
      content = [
        inputCell
      ].concat children

      div {
        className: className
      }, content
    # This is a single cell within a row
    else
      inputCell

  handleChange: ->
    {onChange, jsonPath} = @props
    
    value = @getValue()

    @props.onChange(value, jsonPath)

  getValue: -> 
    {enableTime, returnDateString, twentyFourHour} = @props
    timestamp = @refs.input.getValue()
    return null unless timestamp?
    return timestamp unless enableTime

    minute = @refs.minute.getValue()
    hour = @refs.hour.getValue()
    ampm = @refs.ampm?.getValue() or ''

    # When returnDateString is passed the datepicker will return a string instead of a moment object
    # in this case momentize it before extracting the time
    if returnDateString? 
      date = moment(timestamp, returnDateString).format('YYYYMMDD')
      rv = moment("#{date}#{hour}#{minute}#{ampm}", if twentyFourHour then 'YYYYMMDDHHmm' else 'YYYYMMDDhmma')
      rv = rv.format(returnDateString)
      return rv
    else
      date = timestamp.format('YYYYMMDD')
      moment("#{date}#{hour}#{minute}#{ampm}", if twentyFourHour then 'YYYYMMDDHHmm' else 'YYYYMMDDhmma')
     

  getMomentObject: (date) ->
    {returnDateString} = @props

    # When returnDateString is passed the datepicker will return a string instead of a moment object
    # in this case momentize it before extracting the time
    if returnDateString? then moment(date, returnDateString) else date


  parseTime: (timestamp) ->
    return {} unless timestamp?
    
    {twentyFourHour, returnDateString} = @props

    timestamp = @getMomentObject timestamp
    
    {
      hour: timestamp.format(if twentyFourHour then 'HH' else 'h')
      minute: timestamp.format('mm')
      ampm: timestamp.format('a')
    }


  getHourOptions: (parsedTime)->
    {twentyFourHour} = @props

    hoursArray = 
      if twentyFourHour then [0..23]
      else [12].concat [1..11]

    hourOptions = ({
      label: if twentyFourHour then @prependZero hour else "#{hour}"
      value: if twentyFourHour then @prependZero hour else "#{hour}"
      disabled: no
    } for hour in hoursArray)
 
    @removeOutOfRange 'hours', parsedTime, hourOptions

    hourOptions


  getMinuteOptions: (parsedTime, hourOptions) ->
    {minutes} = @props
      
    minuteOptions = ({
      label: @prependZero minute
      value: @prependZero minute
      disabled: no
    } for minute in minutes)

    @removeOutOfRange 'minutes', parsedTime, minuteOptions, hourOptions 

    minuteOptions


  getAmPmOptions: ->
    {AmPm} = @props
  
    ampmOptions = ({
      label: opt
      value: opt
    } for opt in AmPm)

    @removeOutOfRangeAmPm ampmOptions 

    ampmOptions
  

  removeOutOfRange: (timeSection, parsedTime, optionsList, hourOptions = []) ->
    {selected, twentyFourHour, minDate, maxDate, minutes} = @props

    dateFormat = if twentyFourHour then 'MMDDYYYY HHmm' else 'MMDDYYYY hmma'

    if selected?
      currentValue = @getMomentObject selected

      # If the selected hour is out of range, use the first hour option for checking the mintes
      selectedHour = if not find(hourOptions, {value: parsedTime.hour})? then (hourOptions[0]?.value or '') else parsedTime.hour
      
      # When checking for the hours to include in the list, use the highest/lowest minute option
      # This will prevent and hour option from appearing that has no corresponding minute options
      # It will also ensure the max hours is in the list if there is a corresponding minute that puts it in range
      lastMinute = @prependZero last(minutes)
      firstMinute = @prependZero minutes[0]

      # When the max date is selected, remove hours and minutes that fall after the max time
      if maxDate? and currentValue.isSame(maxDate, 'day')
        maxDay = maxDate.format('MMDDYYYY')
        for option, o in optionsList by -1
          checkTime = switch timeSection
            when 'hours' then moment("#{maxDay} #{option.value}#{firstMinute}#{if twentyFourHour then '' else parsedTime.ampm}", dateFormat)
            when 'minutes' then moment("#{maxDay} #{selectedHour}#{option.value}#{if twentyFourHour then '' else parsedTime.ampm}", dateFormat)
          optionsList.splice(o, 1) if checkTime.isAfter(maxDate, 'minute')
      # When the min date is selected, remove hours and minutes that fall before the min time
      if minDate? and currentValue.isSame(minDate, 'day')
        minDay = minDate.format('MMDDYYYY')
        for option, o in optionsList by -1
          checkTime = switch timeSection
            when 'hours' then moment("#{minDay} #{option.value}#{lastMinute}#{if twentyFourHour then '' else parsedTime.ampm}", dateFormat)
            when 'minutes' then moment("#{minDay} #{selectedHour}#{option.value}#{if twentyFourHour then '' else parsedTime.ampm}", dateFormat)
          optionsList.splice(o, 1) if checkTime.isBefore(minDate, 'minute')
      
      return optionsList
    
  removeOutOfRangeAmPm: (optionsList) ->
    {selected, minDate, maxDate} = @props

    if selected?
      currentValue = @getMomentObject selected
      if maxDate? 
        if currentValue.isSame(maxDate, 'day')
          if maxDate.format('a') is 'am' then optionsList.splice 1, 1
      if minDate? 
        if currentValue.isSame(minDate, 'day')
          if minDate.format('a') is 'pm' then optionsList.splice 0, 1

  
  getCellClasses: (inputColumnClass) -> 
    {timeColumns} = @props 
    classSplit = inputColumnClass.split('-')
    dateCol = classSplit[1]
    colCount = classSplit[2]

    [
      "col-#{dateCol - timeColumns}-#{colCount}"
      "col-#{timeColumns}-#{colCount}"
    ]
  

  prependZero: (value) -> if +value < 10 then "0#{value}" else "#{value}"




module.exports = GridFormDatePicker