<!--
/**
 * @fileoverview Calendar component
 * @license MIT
 * @author Rafal Pospiech <neuronet.io@gmail.com>
 * @package GanttElastic
 */
-->
<template>
  <div
    :style="{ ...root.style['calendar-wrapper'], width: root.state.options.width + 'px' }"
    class="gantt-elastic__calendar-wrapper"
  >
    <div :style="{ ...root.style['calendar'], width: root.state.options.width + 'px' }" class="gantt-elastic__calendar">
      <calendar-row v-if="root.state.options.calendar.month.display" :items="dates.months" which="month"/>
      <calendar-row v-if="root.state.options.calendar.day.display" :items="dates.days" which="day"/>
      <calendar-row v-if="root.state.options.calendar.hour.display" :items="dates.hours" which="hour"/>
    </div>
  </div>
</template>

<script>
import dayjs from 'dayjs'
import CalendarRow from './CalendarRow.vue'

export default {
  name: 'Calendar',
  components: {
    CalendarRow
  },
  inject: ['root'],
  data() {
    return {}
  },

  computed: {
    dates() {
      const hours = this.generateHours()
      const days = this.generateDays()
      const months = this.generateMonths()
      const allDates = { hours, days, months }
      this.calculateCalendarDimensions(allDates)
      return allDates
    }
  },

  methods: {
    /**
     * How many hours will fit?
     *
     * @returns {object}
     */
    howManyHoursFit(dayIndex) {
      const stroke = 1
      const additionalSpace = stroke + 2
      const fullCellWidth = this.root.state.options.times.steps[dayIndex].width.px
      const formatNames = Object.keys(this.root.state.options.calendar.hour.format)
      for (let hours = 24; hours > 1; hours = Math.ceil(hours / 2)) {
        for (const formatName of formatNames) {
          if (
            (this.root.state.options.calendar.hour.maxWidths[formatName] + additionalSpace) * hours <= fullCellWidth &&
            hours > 1
          ) {
            return {
              count: hours,
              type: formatName
            }
          }
        }
      }
      return {
        count: 0,
        type: ''
      }
    },

    /**
     * How many days will fit?
     *
     * @returns {object}
     */
    howManyDaysFit() {
      const stroke = 1
      const additionalSpace = stroke + 2
      const fullWidth = this.root.state.options.width
      const formatNames = Object.keys(this.root.state.options.calendar.day.format)
      for (let days = this.root.state.options.times.steps.length; days > 1; days = Math.ceil(days / 2)) {
        for (const formatName of formatNames) {
          if (
            (this.root.state.options.calendar.day.maxWidths[formatName] + additionalSpace) * days <= fullWidth &&
            days > 1
          ) {
            return {
              count: days,
              type: formatName
            }
          }
        }
      }
      return {
        count: 0,
        type: ''
      }
    },

    /**
     * How many months will fit?
     *
     * @returns {object}
     */
    howManyMonthsFit() {
      const stroke = 1
      const additionalSpace = stroke + 2
      const fullWidth = this.root.state.options.width
      const formatNames = Object.keys(this.root.state.options.calendar.month.format)
      // eslint-disable-next-line no-unused-vars
      const currentMonth = dayjs(this.root.state.options.times.firstTime)
      // const previousMonth = currentMonth.clone()
      // const lastTime = this.root.state.options.times.lastTime
      const monthsCount = this.root.monthsCount(
        this.root.state.options.times.firstTime,
        this.root.state.options.times.lastTime
      )
      if (monthsCount === 1) {
        for (const formatName of formatNames) {
          if (this.root.state.options.calendar.month.maxWidths[formatName] + additionalSpace <= fullWidth) {
            return {
              count: 1,
              type: formatName
            }
          }
        }
      }
      for (let months = monthsCount; months > 1; months = Math.ceil(months / 2)) {
        for (const formatName of formatNames) {
          if (
            (this.root.state.options.calendar.month.maxWidths[formatName] + additionalSpace) * months <= fullWidth &&
            months > 1
          ) {
            return {
              count: months,
              type: formatName
            }
          }
        }
      }
      return {
        count: 0,
        type: formatNames[0]
      }
    },

    /**
     * Generate hours
     *
     * @returns {array}
     */
    generateHours() {
      const allHours = []
      if (!this.root.state.options.calendar.hour.display) {
        return allHours
      }
      const steps = this.root.state.options.times.steps
      // const localeName = this.root.state.options.locale.name
      for (let hourIndex = 0, len = steps.length; hourIndex < len; hourIndex++) {
        const hoursCount = this.howManyHoursFit(hourIndex)
        if (hoursCount.count === 0) {
          continue
        }
        const hours = { key: hourIndex + 'step', children: [] }
        const hourStep = 24 / hoursCount.count
        const hourWidthPx = steps[hourIndex].width.px / hoursCount.count
        for (let i = 0, len = hoursCount.count; i < len; i++) {
          const hour = i * hourStep
          let index = hourIndex
          if (hourIndex > 0) {
            index = hourIndex - Math.floor(hourIndex / 24) * 24
          }
          let textWidth = 0
          if (typeof this.root.state.options.calendar.hour.widths[index] !== 'undefined') {
            textWidth = this.root.state.options.calendar.hour.widths[index][hoursCount.type]
          }
          const x = steps[hourIndex].offset.px + hourWidthPx * i
          hours.children.push({
            index: hourIndex,
            key: 'h' + i,
            x,
            y: this.root.state.options.calendar.day.height + this.root.state.options.calendar.month.height,
            width: hourWidthPx,
            textWidth,
            height: this.root.state.options.calendar.hour.height,
            label: this.root.state.options.calendar.hour.formatted[hoursCount.type][hour]
          })
        }
        allHours.push(hours)
      }
      return allHours
    },

    /**
     * Generate days
     *
     * @returns {array}
     */
    generateDays() {
      const days = []
      if (!this.root.state.options.calendar.day.display) {
        return days
      }
      const daysCount = this.howManyDaysFit()
      if (daysCount.count === 0) {
        return days
      }
      const steps = this.root.state.options.times.steps
      const localeName = this.root.state.options.locale.name
      const dayStep = Math.ceil(steps.length / daysCount.count)
      for (let dayIndex = 0, len = steps.length; dayIndex < len; dayIndex += dayStep) {
        let dayWidthPx = 0
        // day could be shorter (daylight saving time) so join widths and divide
        for (let currentStep = 0; currentStep < dayStep; currentStep++) {
          if (typeof steps[dayIndex + currentStep] !== 'undefined') {
            dayWidthPx += steps[dayIndex + currentStep].width.px
          }
        }
        const date = dayjs(steps[dayIndex].time)
        let textWidth = 0
        if (typeof this.root.state.options.calendar.day.widths[dayIndex] !== 'undefined') {
          textWidth = this.root.state.options.calendar.day.widths[dayIndex][daysCount.type]
        }
        const x = steps[dayIndex].offset.px
        days.push({
          index: dayIndex,
          key: steps[dayIndex].time + 'd',
          x,
          y: this.root.state.options.calendar.month.height,
          width: dayWidthPx,
          textWidth,
          height: this.root.state.options.calendar.day.height,
          label: this.root.state.options.calendar.day.format[daysCount.type](date.locale(localeName))
        })
      }
      return days.map(item => ({
        key: item.key,
        children: [item]
      }))
    },

    /**
     * Generate months
     *
     * @returns {array}
     */
    generateMonths() {
      const months = []
      if (!this.root.state.options.calendar.month.display) {
        return months
      }
      const monthsCount = this.howManyMonthsFit()
      if (monthsCount.count === 0) {
        return months
      }
      // const steps = this.root.state.options.times.steps
      const localeName = this.root.state.options.locale.name
      const formatNames = Object.keys(this.root.state.options.calendar.month.format)
      let currentDate = dayjs(this.root.state.options.times.firstTime)
      for (let monthIndex = 0; monthIndex < monthsCount.count; monthIndex++) {
        let monthWidth = 0
        let monthOffset = Number.MAX_SAFE_INTEGER
        let finalDate = dayjs(currentDate)
          .add(1, 'month')
          .startOf('month')
        if (finalDate.valueOf() > this.root.state.options.times.lastTime) {
          finalDate = dayjs(this.root.state.options.times.lastTime)
        }
        // we must find first and last step to get the offsets / widths
        for (let step = 0, len = this.root.state.options.times.steps.length; step < len; step++) {
          const currentStep = this.root.state.options.times.steps[step]
          if (currentStep.time >= currentDate.valueOf() && currentStep.time < finalDate.valueOf()) {
            monthWidth += currentStep.width.px
            if (currentStep.offset.px < monthOffset) {
              monthOffset = currentStep.offset.px
            }
          }
        }
        let label = ''
        let choosenFormatName
        for (const formatName of formatNames) {
          if (this.root.state.options.calendar.month.maxWidths[formatName] + 2 <= monthWidth) {
            label = this.root.state.options.calendar.month.format[formatName](currentDate.locale(localeName))
            choosenFormatName = formatName
          }
        }
        let textWidth = 0
        if (typeof this.root.state.options.calendar.month.widths[monthIndex] !== 'undefined') {
          textWidth = this.root.state.options.calendar.month.widths[monthIndex][choosenFormatName]
        }
        const x = monthOffset
        months.push({
          index: monthIndex,
          key: monthIndex + 'm',
          x,
          y: 0,
          width: monthWidth,
          textWidth,
          choosenFormatName,
          height: this.root.state.options.calendar.month.height,
          label
        })
        currentDate = currentDate.add(1, 'month').startOf('month')
        if (currentDate.valueOf() > this.root.state.options.times.lastTime) {
          currentDate = dayjs(this.root.state.options.times.lastTime)
        }
      }
      return months.map(item => ({
        key: item.key,
        children: [item]
      }))
    },

    /**
     * Sum all calendar rows height and return result
     *
     * @returns {int}
     */
    calculateCalendarDimensions({ hours, days, months }) {
      let height = 0
      if (this.root.state.options.calendar.hour.display && hours.length > 0) {
        height += this.root.state.options.calendar.hour.height
      }
      if (this.root.state.options.calendar.day.display && days.length > 0) {
        height += this.root.state.options.calendar.day.height
      }
      if (this.root.state.options.calendar.month.display && months.length > 0) {
        height += this.root.state.options.calendar.month.height
      }
      this.root.state.options.calendar.height = height
    }
  }
}
</script>
