# Design by Chris Meeks
# Code by Tyler van der Hoeven
# https://github.com/Baremetrics/calendar
import moment  from 'moment-timezone'

Calendar = (settings) ->
  self = this
  @settings = settings
  @calIsOpen = false
  @presetIsOpen = false
  @sameDayRange = settings.same_day_range or false
  @element = settings.element or $('.daterange')
  @selected = null
  @type = if @element.hasClass('daterange--single') then 'single' else 'double'
  @required = if settings.required == false then false else true
  @format = settings.format or {}
  @format.input = settings.format and settings.format.input or 'MMMM D, YYYY'
  @format.preset = settings.format and settings.format.preset or 'll'
  @format.jump_month = settings.format and settings.format.jump_month or 'MMMM'
  @format.jump_year = settings.format and settings.format.jump_year or 'YYYY'
  @placeholder = settings.placeholder or @format.input
  @days_array = if settings.days_array and settings.days_array.length == 7 then settings.days_array else moment.weekdaysMin()
  @orig_start_date = null
  @orig_end_date = null
  @orig_current_date = null
  @earliest_date = if settings.earliest_date then moment(settings.earliest_date) else moment('1900-01-01')
  @latest_date = if settings.latest_date then moment(settings.latest_date) else moment('2900-12-31')
  @end_date = if settings.end_date then moment(settings.end_date) else if @type == 'double' then moment() else null
  @start_date = if settings.start_date then moment(settings.start_date) else if @type == 'double' then @end_date.clone().subtract(1, 'month') else null
  @current_date = if settings.current_date then moment(settings.current_date) else if @type == 'single' then moment() else null
  @presets = if settings.presets == false or @type == 'single' then false else true
  @callback = settings.callback or @calendarSetDates
  @calendarHTML @type
  $('.dr-presets', @element).click ->
    self.presetToggle()
    return
  $('.dr-list-item', @element).click ->
    start = $('.dr-item-aside', this).data('start')
    end = $('.dr-item-aside', this).data('end')
    self.start_date = self.calendarCheckDate(start)
    self.end_date = self.calendarCheckDate(end)
    self.calendarSetDates()
    self.presetToggle()
    self.calendarSaveDates()
    return
  $('.dr-date', @element).on
    'click': ->
      self.calendarOpen this
      return
    'keyup': (event) ->
      if event.keyCode == 9 and !self.calIsOpen and !self.start_date and !self.end_date
        self.calendarOpen this
      return
    'keydown': (event) ->
      `var timeframe`
      switch event.keyCode
        when 9
          # Tab
          if $(self.selected).hasClass('dr-date-start')
            event.preventDefault()
            self.calendarCheckDates()
            self.calendarSetDates()
            $('.dr-date-end', self.element).trigger 'click'
          else
            self.calendarCheckDates()
            self.calendarSetDates()
            self.calendarSaveDates()
            self.calendarClose 'force'
        when 13
          # Enter
          event.preventDefault()
          self.calendarCheckDates()
          self.calendarSetDates()
          self.calendarSaveDates()
          self.calendarClose 'force'
        when 27
          # ESC
          self.calendarSetDates()
          self.calendarClose 'force'
        when 38
          # Up
          event.preventDefault()
          timeframe = 'day'
          if event.shiftKey
            timeframe = 'week'
          if event.metaKey
            timeframe = 'month'
          back = moment(self.current_date).subtract(1, timeframe)
          $(this).html back.format(self.format.input)
          self.current_date = back
        when 40
          # Down
          event.preventDefault()
          timeframe = 'day'
          if event.shiftKey
            timeframe = 'week'
          if event.metaKey
            timeframe = 'month'
          forward = moment(self.current_date).add(1, timeframe)
          $(this).html forward.format(self.format.input)
          self.current_date = forward
      return
  $('.dr-month-switcher i', @element).click ->
    m = $('.dr-month-switcher span', self.element).data('month')
    y = $('.dr-year-switcher span', self.element).data('year')
    this_moment = moment([
      y
      m
      1
    ])
    back = this_moment.clone().subtract(1, 'month')
    forward = this_moment.clone().add(1, 'month').startOf('day')
    if $(this).hasClass('dr-left')
      self.calendarOpen self.selected, back
    else if $(this).hasClass('dr-right')
      self.calendarOpen self.selected, forward
    return
  $('.dr-year-switcher i', @element).click ->
    m = $('.dr-month-switcher span', self.element).data('month')
    y = $('.dr-year-switcher span', self.element).data('year')
    this_moment = moment([
      y
      m
      1
    ])
    back = this_moment.clone().subtract(1, 'year')
    forward = this_moment.clone().add(1, 'year').startOf('day')
    if $(this).hasClass('dr-left')
      self.calendarOpen self.selected, back
    else if $(this).hasClass('dr-right')
      self.calendarOpen self.selected, forward
    return
  $('.dr-dates-dash', @element).click ->
    $('.dr-date-start', self.element).trigger 'click'
    return
  # Once you click into a selection.. this lets you click out
  @element.on 'click', ->
    document.addEventListener 'click', (f) ->
      contains = self.element.find(f.path[0])
      if !contains.length
        if self.presetIsOpen
          self.presetToggle()
        if self.calIsOpen
          if $(self.selected).hasClass('dr-date-end')
            self.calendarSaveDates()
          self.calendarSetDates()
          self.calendarClose 'force'
      return
    return
  return

selectOneDate = (other, cal, date) ->
  string = moment(date).format(cal.format.input)
  if other
    $('.dr-date', cal.element).not(cal.selected).html other.format(cal.format.input)
  $(cal.selected).html string
  cal.calendarOpen cal.selected
  if $(cal.selected).hasClass('dr-date-start')
    $('.dr-date-end', cal.element).trigger 'click'
  else
    cal.calendarSaveDates()
    cal.calendarClose 'force'
  return

Calendar::presetToggle = ->
  if @presetIsOpen == false
    @orig_start_date = @start_date
    @orig_end_date = @end_date
    @orig_current_date = @current_date
    @presetIsOpen = true
  else if @presetIsOpen
    @presetIsOpen = false
  if @calIsOpen == true
    @calendarClose()
  $('.dr-preset-list', @element).slideToggle 200
  $('.dr-input', @element).toggleClass 'dr-active'
  $('.dr-presets', @element).toggleClass 'dr-active'
  @element.toggleClass 'dr-active'
  return

Calendar::presetCreate = ->
  self = this
  ul_presets = $('<ul class="dr-preset-list" style="display: none;"></ul>')
  presets = if typeof self.settings.presets == 'object' then self.settings.presets else [
    {
      label: 'Last 30 days'
      start: moment(self.latest_date).subtract(29, 'days')
      end: self.latest_date
    }
    {
      label: 'Last month'
      start: moment(self.latest_date).subtract(1, 'month').startOf('month')
      end: moment(self.latest_date).subtract(1, 'month').endOf('month')
    }
    {
      label: 'Last 3 months'
      start: moment(self.latest_date).subtract(3, 'month').startOf('month')
      end: moment(self.latest_date).subtract(1, 'month').endOf('month')
    }
    {
      label: 'Last 6 months'
      start: moment(self.latest_date).subtract(6, 'month').startOf('month')
      end: moment(self.latest_date).subtract(1, 'month').endOf('month')
    }
    {
      label: 'Last year'
      start: moment(self.latest_date).subtract(12, 'month').startOf('month')
      end: moment(self.latest_date).subtract(1, 'month').endOf('month')
    }
    {
      label: 'All time'
      start: self.earliest_date
      end: self.latest_date
    }
  ]
  if moment(self.latest_date).diff(moment(self.latest_date).startOf('month'), 'days') >= 6 and typeof self.settings.presets != 'object'
    presets.splice 1, 0,
      label: 'This month'
      start: moment(self.latest_date).startOf('month')
      end: self.latest_date
  $.each presets, (i, d) ->
    if moment(d.start).isBefore(self.earliest_date)
      d.start = self.earliest_date
    if moment(d.start).isAfter(self.latest_date)
      d.start = self.latest_date
    if moment(d.end).isBefore(self.earliest_date)
      d.end = self.earliest_date
    if moment(d.end).isAfter(self.latest_date)
      d.end = self.latest_date
    startISO = moment(d.start).toISOString()
    endISO = moment(d.end).toISOString()
    string = moment(d.start).format(self.format.preset) + ' &ndash; ' + moment(d.end).format(self.format.preset)
    if $('.dr-preset-list', self.element).length
      item = $('.dr-preset-list .dr-list-item:nth-of-type(' + i + 1 + ') .dr-item-aside', self.element)
      item.data 'start', startISO
      item.data 'end', endISO
      item.html string
    else
      ul_presets.append '<li class="dr-list-item">' + d.label + '<span class="dr-item-aside" data-start="' + startISO + '" data-end="' + endISO + '">' + string + '</span>' + '</li>'
    return
  ul_presets

Calendar::calendarSetDates = ->
  $('.dr-date-start', @element).html moment(@start_date).format(@format.input)
  $('.dr-date-end', @element).html moment(@end_date).format(@format.input)
  if !@start_date and !@end_date
    old_date = $('.dr-date', @element).html()
    new_date = moment(@current_date).format(@format.input)
    if old_date.length == 0 and !@required
      new_date = ''
    if old_date != new_date
      $('.dr-date', @element).html new_date
  return

Calendar::calendarSaveDates = ->
  if @type == 'double'
    if !moment(@orig_end_date).isSame(@end_date) or !moment(@orig_start_date).isSame(@start_date)
      return @callback()
  else
    if !@required or !moment(@orig_current_date).isSame(@current_date)
      return @callback()
  return

Calendar::calendarCheckDate = (d) ->
  # Today
  if d == 'today' or d == 'now'
    return if moment().isAfter(@latest_date) then @latest_date else if moment().isBefore(@earliest_date) then @earliest_date else moment()
  # Earliest
  if d == 'earliest'
    return @earliest_date
  # Latest
  if d == 'latest'
    return @latest_date
  # Convert string to a date if keyword ago or ahead exists
  if d and (/\bago\b/.test(d) or /\bahead\b/.test(d))
    return @stringToDate(d)
  regex = /(?:\d)((?:st|nd|rd|th)?,?)/
  d_array = if d then d.replace(regex, '').split(' ') else []
  # Add current year if year is not included
  if d_array.length == 2
    d_array.push moment().format(@format.jump_year)
    d = d_array.join(' ')
  # Convert using settings format
  parsed_d = @parseDate(d)
  if !parsed_d.isValid()
    return moment(d)
  # occurs when parsing preset dates
  parsed_d

Calendar::calendarCheckDates = ->
  startTxt = $('.dr-date-start', @element).html()
  endTxt = $('.dr-date-end', @element).html()
  c = @calendarCheckDate($(@selected).html())
  s = undefined
  e = undefined
  # Modify strings via some specific keywords to create valid dates
  # Finally set all strings as dates
  if startTxt == 'ytd' or endTxt == 'ytd'
    # Year to date
    s = moment().startOf('year')
    e = moment().endOf('year')
  else
    s = @calendarCheckDate(startTxt)
    e = @calendarCheckDate(endTxt)
  if c.isBefore(@earliest_date)
    c = @earliest_date
  if s.isBefore(@earliest_date)
    s = @earliest_date
  if e.isBefore(@earliest_date) or e.isBefore(s)
    e = s.clone().add(6, 'day')
  if c.isAfter(@latest_date)
    c = @latest_date
  if e.isAfter(@latest_date)
    e = @latest_date
  if s.isAfter(@latest_date) or s.isAfter(e)
    s = e.clone().subtract(6, 'day')
  # Push and save if it's valid otherwise return to previous state
  if @type == 'double'
    # Is this a valid date?
    if s.isSame(e) and !@sameDayRange
      return @calendarSetDates()
    @start_date = if s.isValid() then s else @start_date
    @end_date = if e.isValid() then e else @end_date
  @current_date = if c.isValid() then c else @current_date
  return

Calendar::stringToDate = (str) ->
  date_arr = str.split(' ')
  if date_arr[2] == 'ago'
    return moment(@current_date).subtract(date_arr[0], date_arr[1])
  else if date_arr[2] == 'ahead'
    return moment(@current_date).add(date_arr[0], date_arr[1])
  @current_date

Calendar::calendarOpen = (selected, switcher) ->
  self = this
  other = undefined
  cal_width = $('.dr-dates', @element).innerWidth() - 8
  @selected = selected or @selected
  if @presetIsOpen == true
    @presetToggle()
  if @calIsOpen == true
    @calendarClose if switcher then 'switcher' else undefined
  else if $(@selected).html().length
    @orig_start_date = @start_date
    @orig_end_date = @end_date
    @orig_current_date = @current_date
  @calendarCheckDates()
  @calendarCreate switcher
  @calendarSetDates()
  next_month = moment(switcher or @current_date).add(1, 'month').startOf('month').startOf('day')
  past_month = moment(switcher or @current_date).subtract(1, 'month').endOf('month')
  next_year = moment(switcher or @current_date).add(1, 'year').startOf('month').startOf('day')
  past_year = moment(switcher or @current_date).subtract(1, 'year').endOf('month')
  this_moment = moment(switcher or @current_date)
  $('.dr-month-switcher span', @element).data('month', this_moment.month()).html this_moment.format(@format.jump_month)
  $('.dr-year-switcher span', @element).data('year', this_moment.year()).html this_moment.format(@format.jump_year)
  $('.dr-switcher i', @element).removeClass 'dr-disabled'
  if next_month.isAfter(@latest_date)
    $('.dr-month-switcher .dr-right', @element).addClass 'dr-disabled'
  if past_month.isBefore(@earliest_date)
    $('.dr-month-switcher .dr-left', @element).addClass 'dr-disabled'
  if next_year.isAfter(@latest_date)
    $('.dr-year-switcher .dr-right', @element).addClass 'dr-disabled'
  if past_year.isBefore(@earliest_date)
    $('.dr-year-switcher .dr-left', @element).addClass 'dr-disabled'
  $('.dr-day', @element).on
    mouseenter: ->
      `var selected`
      selected = $(this)
      start_date = moment(self.start_date)
      end_date = moment(self.end_date)
      current_date = moment(self.current_date)

      setMaybeRange = (type) ->
        other = undefined
        self.range(6 * 7).forEach (i) ->
          next = selected.next().data('date')
          prev = selected.prev().data('date')
          curr = selected.data('date')
          if !curr
            return false
          if !prev
            prev = curr
          if !next
            next = curr
          if type == 'start'
            if moment(next).isSame(self.end_date) or self.sameDayRange and moment(curr).isSame(self.end_date)
              return false
            if moment(curr).isAfter(self.end_date)
              other = other or moment(curr).add(6, 'day').startOf('day')
              if i > 5 or (if next then moment(next).isAfter(self.latest_date) else false)
                $(selected).addClass 'dr-end'
                other = moment(curr)
                return false
            selected = selected.next().addClass('dr-maybe')
          else if type == 'end'
            if moment(prev).isSame(self.start_date) or self.sameDayRange and moment(curr).isSame(self.start_date)
              return false
            if moment(curr).isBefore(self.start_date)
              other = other or moment(curr).subtract(6, 'day')
              if i > 5 or (if prev then moment(prev).isBefore(self.earliest_date) else false)
                $(selected).addClass 'dr-start'
                other = moment(curr)
                return false
            selected = selected.prev().addClass('dr-maybe')
          return
        return

      if $(self.selected).hasClass('dr-date-start')
        selected.addClass 'dr-hover dr-hover-before'
        $('.dr-start', self.element).css
          'border': 'none'
          'padding-left': '0.3125rem'
        setMaybeRange 'start'
      if $(self.selected).hasClass('dr-date-end')
        selected.addClass 'dr-hover dr-hover-after'
        $('.dr-end', self.element).css
          'border': 'none'
          'padding-right': '0.3125rem'
        setMaybeRange 'end'
      if !self.start_date and !self.end_date
        selected.addClass 'dr-maybe'
      $('.dr-selected', self.element).css 'background-color', 'transparent'
      return
    mouseleave: ->
      if $(this).hasClass('dr-hover-before dr-end')
        $(this).removeClass 'dr-end'
      if $(this).hasClass('dr-hover-after dr-start')
        $(this).removeClass 'dr-start'
      $(this).removeClass 'dr-hover dr-hover-before dr-hover-after'
      $('.dr-start, .dr-end', self.element).css
        'border': ''
        'padding': ''
      $('.dr-maybe:not(.dr-current)', self.element).removeClass 'dr-start dr-end'
      $('.dr-day', self.element).removeClass 'dr-maybe'
      $('.dr-selected', self.element).css 'background-color', ''
      return
  if /iPad|iPhone|iPod/.test(navigator.userAgent) and !window.MSStream
    $('.dr-day', @element).on touchstart: ->
      selectOneDate other, self, $(this).data('date')
      return
    $('div[contenteditable]', @element).removeAttr 'contenteditable'
  else
    $('.dr-day', @element).on mousedown: ->
      selectOneDate other, self, $(this).data('date')
      return
  $('.dr-calendar', @element).css('width', cal_width).slideDown 200
  $('.dr-input', @element).addClass 'dr-active'
  $(selected).addClass('dr-active').focus()
  @element.addClass 'dr-active'
  @calIsOpen = true
  return

Calendar::calendarClose = (type) ->
  self = this
  if !@calIsOpen or @presetIsOpen or type == 'force'
    $('.dr-calendar', @element).slideUp 200, ->
      $('.dr-day', self.element).remove()
      return
  else
    $('.dr-day', @element).remove()
  if type == 'switcher'
    return false
  $('.dr-input, .dr-date', @element).removeClass 'dr-active'
  @element.removeClass 'dr-active'
  @calIsOpen = false
  return

Calendar::calendarCreate = (switcher) ->
  self = this
  array = @calendarArray(@start_date, @end_date, @current_date, switcher)
  array.forEach (d, i) ->
    classString = 'dr-day'
    if d.fade
      classString += ' dr-fade'
    if d.start
      classString += ' dr-start'
    if d.end
      classString += ' dr-end'
    if d.current
      classString += ' dr-current'
    if d.selected
      classString += ' dr-selected'
    if d.outside
      classString += ' dr-outside'
    $('.dr-day-list', self.element).append '<li class="' + classString + '" data-date="' + d.date + '">' + d.str + '</li>'
    return
  return

Calendar::calendarArray = (start, end, current, switcher) ->
  self = this
  current = moment(current or start or end).startOf('day')
  reference = switcher or current or start or end
  startRange = moment(reference).startOf('month').startOf('week')
  endRange = moment(startRange).add(6 * 7 - 1, 'days').endOf('day')
  daysInRange = []
  d = moment(startRange)
  while d.isBefore(endRange)
    daysInRange.push
      str: +d.format('D')
      start: start and d.isSame(start, 'day')
      end: end and d.isSame(end, 'day')
      current: current and d.isSame(current, 'day')
      selected: start and end and d.isBetween(start, end)
      date: d.toISOString()
      outside: d.isBefore(self.earliest_date) or d.isAfter(self.latest_date)
      fade: !d.isSame(reference, 'month')
    d.add 1, 'd'
  daysInRange

Calendar::calendarHTML = (type) ->
  ul_days_of_the_week = $('<ul class="dr-days-of-week-list"></ul>')
  days = @days_array.splice(moment.localeData().firstDayOfWeek()).concat(@days_array.splice(0, moment.localeData().firstDayOfWeek()))
  $.each days, (i, elem) ->
    ul_days_of_the_week.append '<li class="dr-day-of-week">' + elem + '</li>'
    return
  if type == 'double'
    return @element.append('<div class="dr-input">' + '<div class="dr-dates">' + '<div class="dr-date dr-date-start" contenteditable>' + moment(@start_date).format(@format.input) + '</div>' + '<span class="dr-dates-dash">&ndash;</span>' + '<div class="dr-date dr-date-end" contenteditable>' + moment(@end_date).format(@format.input) + '</div>' + '</div>' + (if @presets then '<div class="dr-presets">' + '<span class="dr-preset-bar"></span>' + '<span class="dr-preset-bar"></span>' + '<span class="dr-preset-bar"></span>' + '</div>' else '') + '</div>' + '<div class="dr-selections">' + '<div class="dr-calendar" style="display: none;">' + '<div class="dr-range-switcher">' + '<div class="dr-switcher dr-month-switcher">' + '<i class="dr-left"></i>' + '<span>April</span>' + '<i class="dr-right"></i>' + '</div>' + '<div class="dr-switcher dr-year-switcher">' + '<i class="dr-left"></i>' + '<span>2015</span>' + '<i class="dr-right"></i>' + '</div>' + '</div>' + ul_days_of_the_week[0].outerHTML + '<ul class="dr-day-list"></ul>' + '</div>' + (if @presets then @presetCreate()[0].outerHTML else '') + '</div>')
  @element.append '<div class="dr-input">' + '<div class="dr-dates">' + '<div class="dr-date" contenteditable placeholder="' + @placeholder + '">' + (if @settings.current_date then moment(@current_date).format(@format.input) else '') + '</div>' + '</div>' + '</div>' + '<div class="dr-selections">' + '<div class="dr-calendar" style="display: none;">' + '<div class="dr-range-switcher">' + '<div class="dr-switcher dr-month-switcher">' + '<i class="dr-left"></i>' + '<span></span>' + '<i class="dr-right"></i>' + '</div>' + '<div class="dr-switcher dr-year-switcher">' + '<i class="dr-left"></i>' + '<span></span>' + '<i class="dr-right"></i>' + '</div>' + '</div>' + ul_days_of_the_week[0].outerHTML + '<ul class="dr-day-list"></ul>' + '</div>' + '</div>'

Calendar::parseDate = (d) ->
  if moment.defaultZone != null and moment.hasOwnProperty('tz')
    moment.tz d, @format.input, moment.defaultZone.name
  else
    moment d, @format.input

Calendar::range = (length) ->
  range = new Array(length)
  idx = 0
  while idx < length
    range[idx] = idx
    idx++
  range

export default Calendar
