import DateInput from './DateInput' import TimeInput from './TimeInput' import DateMenu from './DateMenu' import TimeMenu from './TimeMenu' import DatepickerBtnArrow from './DatepickerBtnArrow' import dateGt from '../utils/dateGt' import dateLt from '../utils/dateLt' import supportDom from '../decorators/supportDom' import {

endOfDay,
dateToTimestamp,
getHours,
getMinutes,
getSeconds,
noop,
parse,
set,
startOfDay,
timestampToDate

} from '../utils'

@supportDom export default class DateTimeRanger {

constructor(dom, options = {}) {
  this.dom = dom
  this.options = options
  this.options.change = options.change || noop
  this.options.useMouseOver = ('useMouseOver' in options) ?
    options.useMouseOver : true

  this.lastTriggered = null
  this.nextDate = null
  this.focused = false
  this.inputDateStartSet = false
  this.inputDateEndSet = false
  this.init()
}

init() {
  const { dom } = this
  const { startAt, endAt } = this.options

  this.startDate = startAt ? new Date(startAt * 1000) : startOfDay(new Date())
  this.endDate = endAt ? new Date(endAt * 1000) : endOfDay(this.startDate)

  this.currentDate = this.startDate

  this.inputDateStart = new DateInput(
    dom.querySelector('[data-date-start]'),
    this.startDate,
    this.options
  )

  this.inputTimeStart = new TimeInput(
    dom.querySelector('[data-time-start]'),
    this.startDate,
    this.options
  )

  this.btnArrow = new DatepickerBtnArrow(
    dom.querySelector('[data-btn-arrow]')
  )

  this.inputDateEnd = new DateInput(
    dom.querySelector('[data-date-end]'),
    this.endDate,
    this.options
  )

  this.inputTimeEnd = new TimeInput(
    dom.querySelector('[data-time-end]'),
    this.endDate,
    this.options
  )

  this.dateMenu = new DateMenu({
    date: this.currentDate,
    startDate: this.startDate,
    endDate: this.endDate,
    options: this.options
  })
  this.timeMenu = new TimeMenu()

  this.addEvents()
}

setTimestamps(startAt, endAt) {
  return this.setDates(
    timestampToDate(startAt),
    timestampToDate(endAt)
  )
}

setDates(startDate, endDate) {
  if (dateGt(startDate, endDate)) {
    throw new Error('Start date cannot be greater than end date.')
  }
  this.startDate = startDate
  this.endDate = endDate
  this.inputDateStart.setDate(this.startDate)
  this.inputTimeStart.setDate(this.startDate)
  this.inputDateEnd.setDate(this.endDate)
  this.inputTimeEnd.setDate(this.endDate)
}

clearInputStatus() {
  this.inputDateStart.clearStatus()
  this.inputTimeStart.clearStatus()
  this.inputDateEnd.clearStatus()
  this.inputTimeEnd.clearStatus()
}

clearTimeInputStatus() {
  this.inputTimeStart.clearStatus()
  this.inputTimeEnd.clearStatus()
}

handleDateInputFocus(input) {
  this.focused = true
  this.inputDateStartSet = false
  this.inputDateEndSet = false
  this.clearTimeInputStatus()
  this.inputDateStart.setActive(true)
  this.inputDateEnd.setActive(true)
  this.dateMenu.setDate({
    date: this.startDate,
    startDate: this.startDate,
    endDate: this.endDate
  })
  this.dateMenu.show(this.dom)
  this.timeMenu.hide()
}

handleTimeInputFocus(input) {
  this.focused = true
  this.clearInputStatus()
  input.setActive(true)
  this.lastTriggered = input
  this.dateMenu.hide()
  this.timeMenu.show({
    src: this.dom,
    date: input.date,
    step: parseInt(input.dom.dataset.step, 10) || 30
  })
}

handleDateInputKeyUp({ event, input, date, isStart }) {

  const res = parse(event.target.value, input.datePattern, date)
  this.nextDate = null

  if (res.toString() === 'Invalid Date') {
    return input.setDanger(true)
  }
  if (isStart && dateGt(startOfDay(res), startOfDay(this.endDate))) {
    return input.setDanger(true)
  }
  if ((! isStart) && dateLt(startOfDay(res), startOfDay(this.startDate))) {
    return input.setDanger(true)
  }
  input.setDanger(false)
  this.nextDate = res
}

handleTimeInputKeyUp({ event, input, date, isStart }) {
  const res = parse(event.target.value, input.timePattern, date)
  this.nextDate = null

  if (res.toString() === 'Invalid Date') {
    return input.setDanger(true)
  }
  if (isStart && dateGt(startOfDay(res), startOfDay(this.endDate))) {
    return input.setDanger(true)
  }
  if ((! isStart) && dateLt(startOfDay(res), startOfDay(this.startDate))) {
    return input.setDanger(true)
  }
  input.setDanger(false)
  this.nextDate = res
}

handleDateInputBlur({ input, isStart }) {
  const dateProp = isStart ? 'startDate' : 'endDate'
  const oldDate = this[dateProp]
  const { nextDate } = this

  if (nextDate) {
    this[dateProp] = nextDate
    input.setDate(nextDate)
    this.dateMenu.setDate({ [dateProp]: nextDate })
    this.nextDate = null
  }
  else {
    input.setDate(oldDate)
  }
}

handleTimeInputBlur({ input, isStart }) {
  const dateProp = isStart ? 'startDate' : 'endDate'
  const oldDate = this[dateProp]
  const { nextDate } = this

  if (nextDate) {
    this[dateProp] = nextDate
    input.setDate(nextDate)
    this.nextDate = null
  }
  else {
    input.setDate(oldDate)
  }
}

addDateInputEvents() {
  this.inputDateStart.on('focus', () => this.handleDateInputFocus(this.inputDateStart))
  this.inputDateStart.on('keyup', event => {
    return this.handleDateInputKeyUp({
      event,
      input: this.inputDateStart,
      date: this.startDate,
      isStart: true
    })
  })
  this.inputDateStart.on('blur', () => {
    return this.handleDateInputBlur({
      input: this.inputDateStart,
      isStart: true
    })
  })

  this.inputDateEnd.on('focus', () => this.handleDateInputFocus(this.inputDateEnd))
  this.inputDateEnd.on('keyup', event => {
    return this.handleDateInputKeyUp({
      event,
      input: this.inputDateEnd,
      date: this.endDate,
      isStart: false
    })
  })
  this.inputDateEnd.on('blur', () => {
    return this.handleDateInputBlur({
      input: this.inputDateEnd,
      isStart: false
    })
  })
}

addTimeInputEvents() {
  this.inputTimeStart.on('focus', () => this.handleTimeInputFocus(this.inputTimeStart))
  this.inputTimeStart.on('keyup', event => {
    return this.handleTimeInputKeyUp({
      event,
      input: this.inputTimeStart,
      date: this.startDate,
      isStart: true
    })
  })
  this.inputTimeStart.on('blur', event => {
    return this.handleTimeInputBlur({
      input: this.inputTimeStart,
      isStart: true
    })
  })

  this.inputTimeEnd.on('focus', () => this.handleTimeInputFocus(this.inputTimeEnd))
  this.inputTimeEnd.on('keyup', event => {
    return this.handleTimeInputKeyUp({
      event,
      input: this.inputTimeEnd,
      date: this.startDate,
      isStart: false
    })
  })
  this.inputTimeEnd.on('blur', event => {
    return this.handleTimeInputBlur({
      input: this.inputTimeEnd,
      isStart: false
    })
  })
}

switchDates() {
  const oldStartDate = this.startDate
  const oldEndDate = this.endDate;
  [this.startDate, this.endDate] = [this.endDate, this.startDate]

  // keeps the time
  this.startDate = set(this.startDate, {
    hours: getHours(oldStartDate),
    minutes: getMinutes(oldStartDate),
    seconds: getSeconds(oldStartDate)
  })
  this.endDate = set(this.endDate, {
    hours: getHours(oldEndDate),
    minutes: getMinutes(oldEndDate),
    seconds: getSeconds(oldEndDate)
  })
  this.inputDateStart.setDate(this.startDate)
  this.inputDateEnd.setDate(this.endDate)
}

emitChange() {
  const { startDate, endDate } = this
  this.options.change({
    startDate,
    endDate,
    startAt: dateToTimestamp(startDate),
    endAt: dateToTimestamp(endDate)
  })
}

clearInputDateSetStatus() {
  this.inputDateStartSet = false
  this.inputDateEndSet = false
}

addMenuEvents() {
  this.dateMenu.on('td-mouseover', (event, res) => {
    if (this.dateMenu.startDate && (! this.dateMenu.endDate)) {
      this.dateMenu.setHoveredCell(res)
    }
  })
  this.dateMenu.on('td-click', (event, res) => {
    event.stopPropagation()
    event.preventDefault()

    if (this.inputDateStartSet && this.inputDateEndSet) {
      this.clearInputDateSetStatus()
    }

    const { year, month, date } = res

    if ((! this.inputDateStartSet) && (! this.inputDateEndSet)) {
      this.dateMenu.setDate({ startDate: null, endDate: null })
      const nextStartDate = set(this.startDate, { year, month, date })
      this.startDate = nextStartDate
      this.inputDateStart.setDate(nextStartDate)
      this.inputDateStartSet = true
      return this.dateMenu.setDate({
        startDate: this.startDate
      })
    }

    if (this.inputDateStartSet && (! this.inputDateEndSet)) {
      this.endDate = set(this.endDate, { year, month, date })

      // switch if next endDate is prior to startDate
      if (dateLt(startOfDay(this.endDate), startOfDay(this.startDate))) {
        this.switchDates()
      }
      this.inputDateStart.setDate(this.startDate)
      this.inputDateEnd.setDate(this.endDate)

      this.inputDateEndSet = true
      this.dateMenu.setDate({
        startDate: this.startDate,
        endDate: this.endDate
      })
      return this.emitChange()
    }
  })

  this.timeMenu.on('click', (event, res) => {
    const nextDate = set(this.lastTriggered.date, { hours: res.hour, minutes: res.minute })
    if (this.lastTriggered === this.inputTimeStart) {
      this.startDate = nextDate
    }
    if (this.lastTriggered === this.inputTimeEnd) {
      this.endDate = nextDate
    }
    this.lastTriggered.setDate(nextDate)
    this.timeMenu.hide()
    this.clearInputStatus()
    this.emitChange()
  })
}

addBtnArrowEvents() {
  this.btnArrow.on('click', () => {
    this.inputDateStart.focus()
    this.handleDateInputFocus(this.inputDateStart)
  })
}

addEvents() {

  this.addDateInputEvents()
  this.addTimeInputEvents()
  this.addMenuEvents()
  this.addBtnArrowEvents()

  this.addEvent(document, 'click', event => {
    const { dom, dateMenu, timeMenu } = this
    const { target } = event
    const dateMenuDom = dateMenu.dom
    const timeMenuDom = timeMenu.dom

    if (this.focused) {
      this.focused = false
      return
    }
    if ((! dateMenu.isVisible) && (! timeMenu.isVisible)) {
      return
    }
    if (dom.contains(target)) {
      return
    }

    if (dateMenuDom.contains(target)) {
      return
    }
    if (dateMenuDom === target) {
      return
    }

    if (timeMenuDom.contains(target)) {
      return
    }
    if (timeMenuDom === target) {
      return
    }

    this.clearInputStatus()

    if (dateLt(startOfDay(this.endDate), startOfDay(this.startDate))) {
      this.switchDates()
    }
    if (this.inputDateStartSet && (! this.inputDateEndSet)) {
      this.emitChange()
    }
    this.clearInputDateSetStatus()
    dateMenu.hide()
    timeMenu.hide()
  })
}

destroy() {
  this.inputDateStart.destroy()
  this.inputTimeStart.destroy()
  this.inputDateEnd.destroy()
  this.inputTimeEnd.destroy()
  this.dateMenu.destroy()
  this.timeMenu.destroy()
  this.btnArrow.destroy()
}

}