<template>
  <div class="datetime-picker" :class="{'readonly': readonly}">
    <input class="form-control" :value="pretty" @focus="showPopup" placeholder="(geen)" readonly>
    <span class="icon-cross" @click.stop="valueCopy = null" v-if="value && !required && !readonly"></span>
    <popup ref="popup">
      <div class="container">
        <div class="header">
          <div @click="move(-1)" class="prev">&lt;</div>
          <div @click="levelUp" class="title">{{title}}</div>
          <div @click="move(+1)" class="next">&gt;</div>
        </div>
        <div class="cal-rows">
          <div class="cal-row" v-for="(row, key) in items" :key="key">
            <div class="cal-col" v-for="(date, key) in row" :key="key" @click="!date.disabled && set(date.date)"
                 :class="{other: date.other, selected: isSelected(date.date), disabled: date.disabled}">
              <div v-html="date.label"></div>
            </div>
          </div>
        </div>
        <div class="time">Tijd:
          <input :value="time" @change="setTime($event.target.value)" placeholder="(geen)">
        </div>
      </div>
    </popup>
  </div>
</template>

<style lang="scss" scoped>
  .datetime-picker {
    position: relative;

    .icon-cross {
      position: absolute;
      right: 0.5em;
      top: 50%;
      transform: translateY(-50%);
      opacity: 0;
      transition: 0.3s opacity ease;
    }

    &:hover .icon-cross {
      opacity: 0.5;

      &:hover {
        opacity: 1;
      }
    }

    &:not(.readonly) {
      .form-control {
        background: white;
        margin-bottom: 0;
      }
    }
  }

  .container {
    margin: -10px;
    width: 300px;
    padding: 5px 10px;
  }

  .header {
    display: flex;
    font-weight: bold;
    cursor: pointer;

    div {
      flex: 1;
      padding: 2px 5px;
    }

    .title {
      text-align: center;
      flex: 2;
    }

    .next {
      text-align: right;
    }
  }

  .cal-rows {
    display: flex;
    flex-direction: column;
    height: 200px;
    margin: 2px -2px;

    > .cal-row {
      display: flex;
      flex: 1;

      > .cal-col {
        display: flex;
        flex-direction: column;
        justify-content: center;
        text-align: center;
        flex: 1;
        margin: 2px;
        padding: 2px;
        cursor: pointer;

        &.selected {
          background: #eef;
          font-weight: bold;
        }

        &.disabled {
          background: #f8f8f8;
          color: #ccc;
          cursor: default;
        }

        &.other {
          color: #ccc;

          &.disabled {
            color: #ddd;
            background: #fafafa;
          }
        }
      }
    }
  }

  .time {
    padding: 5px 0px 0px;
    border-top: 1px solid #f5f5f5;
    text-align: center;

    input {
      border: none;
      width: 5em;
      text-align: center;
      background: transparent;
    }
  }
</style>

<script>
import popup from '../popup.vue'

import moment from 'moment'

const months = ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec']

export default {
  props: {
    value: {
      default: null,
      demoValue: () => new Date(),
    },
    min: {
      default: null,
    },
    max: {
      default: null,
    },
    required: {
      type: Boolean,
      default: false,
    },
    readonly: {
      type: Boolean,
      default: false,
    },
  },
  data () {
    return {
      level: 'month',
      popupVisible: false,
      focus: this.value ? new Date(typeof this.value === 'string' ? this.value : 1 * this.value) : new Date(),
    }
  },
  computed: {
    valueCopy: {
      get () {
        return this.value instanceof Date || this.value === null ? this.value : new Date(this.value)
      },
      set (value) {
        this.$emit('input', value)
      },
    },
    pretty () {
      return this.valueCopy ? moment(this.valueCopy).format('D MMMM Y LTS') : ''
    },
    title () {
      switch (this.level) {
        case 'millennium':
          return (this.focus.getFullYear() / 1000 + 1) + 'e millennium'
        case 'century':
          return (this.focus.getFullYear() / 100 + 1) + 'e eeuw'
        case 'decade':
          return this.focus.getFullYear() + ' – ' + (this.focus.getFullYear() + 9)
        case 'year':
          return this.focus.getFullYear()
        case 'month':
          return months[this.focus.getMonth()] + ' ' + this.focus.getFullYear()
        default:
          return '-'
      }
    },
    time () {
      return this.valueCopy ? this.valueCopy.toString().match(/\d\d:\d\d/)[0] : ''
    },
    items () {
      switch (this.level) {
        case 'millennium':
          return [0, 1, 2].map(row => [0, 1, 2, 3].map(col => {
            var century = row * 4 + col - 1
            return {
              date: new Date(Date.UTC(this.focus.getFullYear() + century * 100, 0, 1)),
              other: century < 0 || century > 9,
              label: (this.focus.getFullYear() / 100 + century + 1) + 'e<br>eeuw',
            }
          }))
        case 'century':
          return [0, 1, 2].map(row => [0, 1, 2, 3].map(col => {
            var year = 10 * (row * 4 + col - 1)
            return {
              date: new Date(Date.UTC(this.focus.getFullYear() + year, 0, 1)),
              other: year < 0 || year > 90,
              label: (this.focus.getFullYear() + year) + ' – ' + (this.focus.getFullYear() + year + 9),
            }
          }))
        case 'decade':
          return [0, 1, 2].map(row => [0, 1, 2, 3].map(col => {
            var year = row * 4 + col - 1
            return {
              date: new Date(Date.UTC(this.focus.getFullYear() + year, 0, 1)),
              other: year < 0 || year > 9,
              label: this.focus.getFullYear() + year,
            }
          }))
        case 'year':
          return [0, 1, 2].map(row => [0, 1, 2, 3].map(col => ({
            date: new Date(Date.UTC(this.focus.getFullYear(), row * 4 + col, 1)),
            label: months[row * 4 + col] + '<br>' + this.focus.getFullYear(),
          })))
      }
      var weeks = []
      var iter = new Date(Date.UTC(this.focus.getFullYear(), this.focus.getMonth(), 1))
      var start = new Date(Date.UTC(this.focus.getFullYear(), this.focus.getMonth(), this.focus.getDate())).toISOString().substr(0, 7)
      iter.setDate(iter.getDate() - iter.getDay())
      for (; iter.toISOString().substr(0, 7) <= start;) {
        var week = []
        for (var i = 0; i < 7; i++) {
          week[i] = {
            date: new Date(iter * 1),
            other: iter.getMonth() != this.focus.getMonth(),
            label: iter.getDate(),
            disabled: (this.min && iter < this.min) || (this.max && iter > this.max),
          }
          iter.setDate(iter.getDate() + 1)
        }
        weeks.push(week)
      }
      return weeks
    },
  },
  methods: {
    isSelected (date) {
      if (!this.valueCopy)
        return false
      var len = {
        millennium: 2,
        century: 3,
        decade: 4,
        year: 7,
        month: 10,
      }[this.level]
      var value = new Date(Date.UTC(this.valueCopy.getFullYear(), this.valueCopy.getMonth(), this.valueCopy.getDate()))
      return date.toISOString().substr(0, len) === value.toISOString().substr(0, len)
    },
    set (date) {
      // Changing the valueCopy right away (before running the switch) causes a race condition
      // with the lazy behaviour of computed values. Thus we only set it afterwards
      const tempDate = this.valueCopy ? this.valueCopy : new Date()

      switch (this.level) {
        case 'month':
          tempDate.setFullYear(date.getFullYear())
          tempDate.setMonth(date.getMonth())
          tempDate.setDate(date.getDate())
          // trigger update
          this.valueCopy = new Date(tempDate * 1)
          this.focus = new Date(tempDate * 1)
          break
        default:
          this.focus.setMonth(date.getMonth())
          this.focus.setFullYear(date.getFullYear())
          // trigger update
          this.focus = new Date(this.focus * 1)
          this.level = {
            year: 'month',
            decade: 'year',
            century: 'decade',
            millennium: 'century',
          }[this.level]
          break
      }
    },
    setTime (time) {
      var m = time.match(/(\d\d):(\d\d)(:(\d\d))?/)
      if (m) {
        this.valueCopy.setMilliseconds(0)
        this.valueCopy.setSeconds(m[4] ? 1 * m[4] : 0)
        this.valueCopy.setMinutes(1 * m[2])
        this.valueCopy.setHours(1 * m[1])
        // trigger update
        this.valueCopy = new Date(this.valueCopy * 1)
      }
    },
    levelUp () {
      switch (this.level) {
        case 'month':
          this.level = 'year'
          this.focus.setMonth(0)
          break
        case 'year':
          this.level = 'decade'
          this.focus.setFullYear(this.focus.getFullYear() - this.focus.getFullYear() % 10)
          break
        case 'decade':
          this.level = 'century'
          this.focus.setFullYear(this.focus.getFullYear() - this.focus.getFullYear() % 100)
          break
        case 'century':
          this.level = 'millennium'
          this.focus.setFullYear(this.focus.getFullYear() - this.focus.getFullYear() % 1000)
          break
      }
      // trigger update
      this.focus = new Date(this.focus * 1)
    },
    move (step) {
      switch (this.level) {
        case 'millennium':
          this.focus.setFullYear(this.focus.getFullYear() + step * 1000)
          break
        case 'century':
          this.focus.setFullYear(this.focus.getFullYear() + step * 100)
          break
        case 'decade':
          this.focus.setFullYear(this.focus.getFullYear() + step * 10)
          break
        case 'year':
          this.focus.setFullYear(this.focus.getFullYear() + step)
          break
        case 'month':
          this.focus.setMonth(this.focus.getMonth() + step)
          break
      }
      // trigger update
      this.focus = new Date(this.focus * 1)
    },
    showPopup (event) {
      if (!this.readonly) {
        this.$refs.popup.show(event.target)
      }
    },
  },
  components: {
    popup,
  },
}
</script>
