<template>
  <transition name="el-zoom-in-top" @after-enter="handleEnter" @after-leave="handleLeave">
    <div
      v-show="visible"
      class="el-picker-panel el-date-picker el-popper"
      :class="[{
        'date-shortcuts': shortcuts,
        'has-time': showTime,
        'is-yearmonth': currentView === 'yearmonth'
      }, popperClass]">
      <div class="el-picker-panel__body-wrapper" :style="{'display': shortcuts ? 'flex !important' : ''}">
        <div class="el-picker-panel--fotterbtn" v-if="shortcuts">
          <el-button
            type="secondary"
            size="mini"
            class="el-picker-panel__shortcut"
            v-for="(shortcut, key) in shortcuts"
            :key="key"
            @click="handleShortcutClick(shortcut)">{{ shortcut.text }}</el-button>
          </div>
        <div class="el-picker-panel__body">
          <slot name="header"></slot>
          <div
            class="el-date-picker__header el-date-picker__header--bordered"
            v-show="currentView !== 'time' && currentView !== 'yearmonth'"
            v-if="checkType === 'date'">
            <button
              type="button"
              @click="prevYear"
              :aria-label="t(`el.datepicker.prevYear`)"
              class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-d-arrow-left">
            </button>
            <button
              type="button"
              @click="prevMonth"
              v-show="currentView === 'date'"
              :aria-label="t(`el.datepicker.prevMonth`)"
              class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-arrow-left">
            </button>
            <span
              @click="showYearPicker"
              role="button"
              class="el-date-picker__header-label"
              :class="currentView === 'year' ? 'year-label' : ''">{{ yearLabel }}</span>
            <span
              @click="showMonthPicker"
              v-show="currentView === 'date'"
              role="button"
              class="el-date-picker__header-label"
              :class="{ active: currentView === 'month' }">{{t(`el.datepicker.month${ month + 1 }`)}}</span>
            <button
              type="button"
              @click="nextYear"
              :aria-label="t(`el.datepicker.nextYear`)"
              class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-d-arrow-right">
            </button>
            <button
              type="button"
              @click="nextMonth"
              v-show="currentView === 'date'"
              :aria-label="t(`el.datepicker.nextMonth`)"
              class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-arrow-right">
            </button>
          </div>

          <div class="el-picker-panel__content" v-if="checkType === 'date' && currentView !== 'yearmonth'">
            <date-table
              v-if="currentView === 'date'"
              @pick="handleDatePick"
              :selection-mode="selectionMode"
              :first-day-of-week="firstDayOfWeek"
              :value="value"
              :default-value="defaultValue ? new Date(defaultValue) : null"
              :date="date"
              :cell-class-name="cellClassName"
              :disabled-date="disabledDate">
            </date-table>
            <year-table
              v-if="currentView === 'year'"
              @pick="handleYearPick"
              :value="value"
              :default-value="defaultValue ? new Date(defaultValue) : null"
              :date="date"
              :disabled-date="disabledDate">
            </year-table>
            <quarter-table
              v-if="currentView === 'quarter'"
              @pick="handleQuarterPick"
              :value="value"
              :default-value="defaultValue ? new Date(defaultValue) : null"
              :date="date"
              :disabled-date="disabledDate">
            </quarter-table>
            <month-table
              v-if="currentView === 'month'"
              @pick="handleMonthPick"
              :value="value"
              :default-value="defaultValue ? new Date(defaultValue) : null"
              :date="date"
              :disabled-date="disabledDate">
            </month-table>
          </div>

          <!-- 年月选择器，宽度和其他的不一致 -->
          <year-month-table
            ref="yearmonth"
            v-if="currentView === 'yearmonth' && checkType === 'date'"
            :default-value="defaultValue ? new Date(defaultValue) : null"
            :date="date"
            @mounted="proxyYearMonthPickerDateProperties"
            @change="changeYearMonth"
            :disabled-date="disabledDate">
          </year-month-table>

          <div
            class="el-date-picker__header el-date-picker__header--bordered"
            v-if="checkType === 'time'">
            选择时间
          </div>

          <div class="el-picker-panel__content" v-if="checkType === 'time'">
            <!-- 选择时间 -->
            <time-spinner
              ref="timespinner"
              @change="handleChange"
              :am-pm-mode="amPmMode"
              @select-range="setSelectionRange"
              @mounted="proxyTimePickerDataProperties"
              :value="value"
              :showSeconds="showSeconds"
              :date="date">
            </time-spinner>
          </div>

          <div class="el-date-picker__select-type" v-if="showTime">
            <span 
              class="el-date-picker__editor-wrap" 
              :class="{'el-date-picker__editor-wrap--checked': checkType === 'date'}"
              @click="openDateSpinner"
            >
              <i class="el-icon-date el-input__icon"></i><span>{{ visibleDate }}</span>
            </span>
            <span 
              class="el-date-picker__editor-wrap" 
              :class="{'el-date-picker__editor-wrap--checked': checkType === 'time'}"
              @click="openTimeSpinner"
            >
              <i class="el-icon-time el-input__icon"></i><span>{{ visibleTime }}</span>
            </span>
          </div>


          <slot name="footer"></slot>
        </div>
      </div>

      <div
        class="el-picker-panel__footer"
        v-if="footerVisible || type === 'datetime'">
        <el-button
          size="mini"
          type="secondary"
          class="el-picker-panel__link-btn"
          v-if="footerVisible"
          @click="cancel">
          {{ t('el.datepicker.cancel') }}
        </el-button>
        <el-button
          size="mini"
          class="el-picker-panel__link-btn"
          @click="confirm">
          {{ t('el.datepicker.confirm') }}
        </el-button>
      </div>
    </div>
  </transition>
</template>

<script type="text/babel">
  import {
    formatDate,
    getWeekNumber,
    isDate,
    modifyDate,
    modifyTime,
    modifyWithTimeString,
    clearMilliseconds,
    clearTime,
    prevYear,
    nextYear,
    prevMonth,
    nextMonth,
    changeYearMonthAndClampDate,
    extractDateFormat,
    extractTimeFormat,
    timeWithinRange
  } from 'modesign-ui/src/utils/date-util';
  import Locale from 'modesign-ui/src/mixins/locale';
  import ElInput from 'modesign-ui/packages/input';
  import ElButton from 'modesign-ui/packages/button';
  import TimeSpinner from '../basic/time-spinner';
  import YearTable from '../basic/year-table';
  import MonthTable from '../basic/month-table';
  import QuarterTable from '../basic/quarter-table';
  import YearMonthTable from '../basic/year-month-table';
  import DateTable from '../basic/date-table';

  export default {
    mixins: [Locale],

    watch: {
      value(val) {
        if (this.selectionMode === 'dates' && this.value) return;
        if (isDate(val)) {
          this.date = new Date(val);
        } else {
          this.date = this.getDefaultValue();
        }
        this.getSpinnerDisable(this.date)
      },

      defaultValue(val) {
        if (!isDate(this.value)) {
          this.date = val ? new Date(val) : new Date();
        }
      },

      timeSpinnerVisible(val) {
        if (val) {
          this.$nextTick(() => {
            this.$refs.timespinner.emitSelectRange('hours')
            this.$refs.timespinner.adjustSpinners()
          });
        }
      },

      selectionMode(newVal) {
        if (newVal === 'month') {
          /* istanbul ignore next */
          if (this.currentView !== 'year' || this.currentView !== 'month' || this.currentView !== 'quarter') {
            this.currentView = 'month';
          }
        } else if (newVal === 'dates') {
          this.currentView = 'date';
        } else if (newVal === 'yearmonth') {
          this.currentView = 'yearmonth';
        }
      },

      visible() {
        if (this.currentView === 'yearmonth') {
          this.$nextTick(() => {
            this.$refs.yearmonth.adjustSpinners()
          });
        }
      }
    },

    methods: {
      openDateSpinner() {
        this.checkType = 'date';
        this.timeSpinnerVisible = false;
        this.$emit('change-type');
      },
      openTimeSpinner() {
        this.checkType = 'time';
        this.timeSpinnerVisible = true;
        this.$emit('change-type');
      },
      handleChange(date) {
        // this.visible avoids edge cases, when use scrolls during panel closing animation
        if (this.visible) {
          this.date = clearMilliseconds(new Date(date));
          // if date is out of range, do not emit
          if (this.isValidValue(this.date)) {
            this.handleTimePick(this.date, true)
          }
        }
      },

      setSelectionRange(start, end) {
        // 时间选择器会focus时、分、秒，前面有日期，所以要加11
        this.$emit('select-range', start + 11, end + 11);
      },

      proxyYearMonthPickerDateProperties() {
        const date = date => {this.$refs.yearmonth.date = date;};
        const selectableRange = selectableRange => {this.$refs.yearmonth.selectableRange = selectableRange;};
        date(this.date);
        selectableRange(this.selectableRange);
        this.$refs.yearmonth.adjustSpinners();
      },

      proxyTimePickerDataProperties() {
        const format = timeFormat => {this.$refs.timespinner.format = timeFormat;};
        const value = value => {this.$refs.timespinner.value = value;};
        const date = date => {this.$refs.timespinner.date = date;};
        const selectableRange = selectableRange => {this.$refs.timespinner.selectableRange = selectableRange;};
        

        format(this.timeFormat);
        value(this.value);
        date(this.date);
        selectableRange(this.selectableRange);
      },

      handleClear() {
        this.date = this.getDefaultValue();
        this.$emit('pick', null);
      },

      emit(value, ...args) {
        if (!value) {
          this.$emit('pick', value, ...args);
        } else if (Array.isArray(value)) {
          const dates = value.map(date => this.showTime ? clearMilliseconds(new Date(date)) : clearTime(date));
          this.$emit('pick', dates, ...args);
        } else {
          this.$emit('pick', this.showTime ? clearMilliseconds(new Date(value)) : clearTime(value), ...args);
        }
        this.userInputDate = null;
        this.userInputTime = null;
      },

      showMonthPicker() {
        this.currentView = 'month';
      },

      showYearPicker() {
        this.currentView = 'year';
      },

      prevMonth() {
        this.date = prevMonth(this.date);
      },

      nextMonth() {
        this.date = nextMonth(this.date);
      },

      prevYear() {
        if (this.currentView === 'year') {
          this.date = prevYear(this.date, 10);
        } else {
          this.date = prevYear(this.date);
        }
      },

      nextYear() {
        if (this.currentView === 'year') {
          this.date = nextYear(this.date, 10);
        } else {
          this.date = nextYear(this.date);
        }
      },

      handleShortcutClick(shortcut) {
        if (shortcut.onClick) {
          shortcut.onClick(this);
        }
      },

      handleTimePick(value, visible, first) {
        if (isDate(value)) {
          const newDate = this.value
            ? modifyTime(new Date(this.value), value.getHours(), value.getMinutes(), value.getSeconds())
            : modifyWithTimeString(this.getDefaultValue(), this.defaultTime);
          this.date = newDate;
          this.emit(this.date, true);
        } else {
          this.emit(value, true);
        }
        if (!first) {
          this.timeSpinnerVisible = visible;
        }
      },

      handleMonthPick(month) {
        if (this.selectionMode === 'month') {
          this.date = modifyDate(this.date, this.year, month, 1);
          this.emit(this.date);
        } else {
          this.date = changeYearMonthAndClampDate(this.date, this.year, month);
          // TODO: should emit intermediate value ??
          // this.emit(this.date);
          this.currentView = 'date';
        }
      },

      handleQuarterPick(quarter) {
		    // 枚举 每个季度第一个月的索引
        const quarterMap = {
          Q1: 0,
          Q2: 3,
          Q3: 6,
          Q4: 9
        }
        if (this.selectionMode === 'quarter') {
          this.date = modifyDate(this.date, this.year, quarterMap[quarter], 1);
          this.emit(this.date);
        } else {
          this.date = changeYearMonthAndClampDate(this.date, this.year, quarterMap[quarter]);
          // TODO: should emit intermediate value ??
          // this.emit(this.date, true);
          this.currentView = 'quarter';
        }
      },

      handleDatePick(value) {
        if (this.selectionMode === 'day') {
          let newDate = this.value
            ? modifyDate(this.value, value.getFullYear(), value.getMonth(), value.getDate())
            : modifyWithTimeString(value, this.defaultTime);
          // change default time while out of selectableRange
          if (!this.checkDateWithinRange(newDate)) {
            newDate = modifyDate(this.selectableRange[0][0], value.getFullYear(), value.getMonth(), value.getDate());
          }
          this.date = newDate;
          this.emit(this.date, this.showTime);
        } else if (this.selectionMode === 'week') {
          this.emit(value.date);
        } else if (this.selectionMode === 'dates') {
          this.emit(value, true); // set false to keep panel open
        }
      },

      handleYearPick(year) {
        if (this.selectionMode === 'year') {
          this.date = modifyDate(this.date, year, 0, 1);
          this.emit(this.date);
        } else {
          this.date = changeYearMonthAndClampDate(this.date, year, this.month);
          // TODO: should emit intermediate value ??
          // this.emit(this.date, true);
          this.currentView = 'month';
        }
      },

      cancel() {
        // NOTE: not a permanent solution
        //       consider disable "now" button in the future
        if ((!this.disabledDate || !this.disabledDate(new Date())) && this.checkDateWithinRange(new Date())) {
          this.date = '';
          this.emit(this.date);
        }
      },

      confirm() {
        if (this.selectionMode === 'dates') {
          this.emit(this.value);
        } else {
          // value were emitted in handle{Date,Time}Pick, nothing to update here
          // deal with the scenario where: user opens the picker, then confirm without doing anything
          const value = this.value
            ? this.value
            : modifyWithTimeString(this.getDefaultValue(), this.defaultTime);
          this.date = new Date(value); // refresh date
          this.emit(value);
        }
      },

      resetView() {
        if (this.selectionMode === 'month') {
          this.currentView = 'month';
        } else if (this.selectionMode === 'quarter') {
          this.currentView = 'quarter';
        } else if (this.selectionMode === 'year') {
          this.currentView = 'year';
        } else if (this.selectionMode === 'yearmonth') {
          this.currentView = 'yearmonth';
        } else {
          this.currentView = 'date';
        }
      },

      handleEnter() {
        document.body.addEventListener('keydown', this.handleKeydown);
      },

      handleLeave() {
        this.checkType = 'date';
        this.timeSpinnerVisible = false;
        this.$emit('dodestroy');
        document.body.removeEventListener('keydown', this.handleKeydown);
      },

      handleKeydown(event) {
        const keyCode = event.keyCode;
        const list = [38, 40, 37, 39];
        if (this.visible && !this.timeSpinnerVisible) {
          if (list.indexOf(keyCode) !== -1) {
            if (this.selectionMode === 'yearmonth') {
              const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
              const step = mapping[keyCode];
              this.$refs.yearmonth.scrollDown(step);
            } else {
              this.handleKeyControl(keyCode);
            }
            event.stopPropagation();
            event.preventDefault();
          }
          if (keyCode === 13 && this.userInputDate === null && this.userInputTime === null) { // Enter
            this.emit(this.date, false);
          }
        }
      },

      handleKeyControl(keyCode) {
        const mapping = {
          'year': {
            38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => date.setFullYear(date.getFullYear() + step)
          },
          'quarter': {
            38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => `${date.setFullYear(date.getFullYear())}-Q${step}`
          },
          'month': {
            38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => date.setMonth(date.getMonth() + step)
          },
          'week': {
            38: -1, 40: 1, 37: -1, 39: 1, offset: (date, step) => date.setDate(date.getDate() + step * 7)
          },
          'day': {
            38: -7, 40: 7, 37: -1, 39: 1, offset: (date, step) => date.setDate(date.getDate() + step)
          }
        };
        const mode = this.selectionMode;
        const year = 3.1536e10;
        const now = this.date.getTime();
        const newDate = new Date(this.date.getTime());
        while (Math.abs(now - newDate.getTime()) <= year) {
          const map = mapping[mode];
          map.offset(newDate, map[keyCode]);
          if (typeof this.disabledDate === 'function' && this.disabledDate(newDate)) {
            continue;
          }
          this.date = newDate;
          this.$emit('pick', newDate, true);
          break;
        }
      },

      isValidValue(value) {
        return value && !isNaN(value) && (
          typeof this.disabledDate === 'function'
            ? !this.disabledDate(value)
            : true
        ) && this.checkDateWithinRange(value);
      },

      getDefaultValue() {
        // if default-value is set, return it
        // otherwise, return now (the moment this method gets called)
        return this.defaultValue ? new Date(this.defaultValue) : new Date();
      },

      checkDateWithinRange(date) {
        this.getSpinnerDisable(date)
        return this.selectableRange.length > 0
          ? timeWithinRange(date, this.selectableRange, this.timeFormat || 'HH:mm:ss')
          : true;
      },
      // 计算时分秒状态
      getSpinnerDisable(date) {
        if (this.selectableRange.length > 0) {
          this.disabledHours = []
          this.disabledMinutes = []
          this.disabledSeconds = []
          this.selectableRange.forEach((item, index) => {
            // 处理小时
            const startHours = item[0].getHours()
            const endHours = item[1].getHours()
            for (let i = 0; i < 24; i ++) {
              if (startHours <= i && i <= endHours) {
                this.disabledHours.push(i)
              }
            }
            // 处理分钟
            const startHoursmi = item[0].getMinutes()
            const endHoursmi = item[1].getMinutes()
            const startDate = modifyTime(date, startHours, startHoursmi, 0).getTime()
            const endDate = modifyTime(date, endHours, endHoursmi, 0).getTime()
            for (let j = 0; j < 60; j++) {
              let curTime = modifyTime(date, date.getHours(), j, 0).getTime()
              if (curTime >= startDate && curTime <= endDate) {
                this.disabledMinutes.push(j)
              }
            }
            // 处理秒数
            const startHoursmisec = item[0].getSeconds()
            const endHoursmisec = item[1].getSeconds()
            const startDate2 = modifyTime(date, startHours, startHoursmi, startHoursmisec).getTime()
            const endDate2 = modifyTime(date, endHours, endHoursmi, endHoursmisec).getTime()
            for (let j = 0; j < 60; j++) {
              let curTime = modifyTime(date, date.getHours(), date.getMinutes(), j).getTime()
              if (curTime >= startDate2 && curTime <= endDate2) {
                this.disabledSeconds.push(j)
              }
            }

          })
          this.disabledHours = this.transNumberArray(this.disabledHours, 24)
          this.disabledMinutes = this.transNumberArray(this.disabledMinutes, 60)
          this.disabledSeconds = this.transNumberArray(this.disabledSeconds, 60)    

        }
      },

      changeYearMonth(date) {
        this.date = date;
        this.emit(this.date, true);
      },
      transNumberArray(arr, num) {
        let newArr = []
        for (let i = 0; i < num; i++) {
          if (!arr.includes(i)) {
            newArr.push(i)
          }
        }
        return newArr
      }
    },
    components: {
      TimeSpinner, YearTable, MonthTable, YearMonthTable, QuarterTable, DateTable, ElInput, ElButton
    },

    data() {
      return {
        popperClass: '',
        date: new Date(),
        value: new Date(),
        defaultValue: null, // use getDefaultValue() for time computation
        defaultTime: null,
        showTime: false,
        selectionMode: 'day',
        shortcuts: '',
        visible: false,
        currentView: 'date',
        disabledDate: '',
        cellClassName: '',
        selectableRange: [],
        firstDayOfWeek: 7,
        showWeekNumber: false,
        timeSpinnerVisible: false,
        format: '',
        userInputDate: null,
        userInputTime: null,
        showFooter: false,
        checkType: 'date', // type为datetime时选择日期或时间
        type: '',
        disabledHours: null,
        disabledMinutes: null,
        disabledSeconds: null,
      };
    },
    computed: {
      year() {
        return this.date.getFullYear();
      },

      month() {
        return this.date.getMonth();
      },

      week() {
        return getWeekNumber(this.date);
      },

      monthDate() {
        return this.date.getDate();
      },

      footerVisible() {
        return this.showFooter || this.selectionMode === 'dates';
      },

      visibleTime() {
        if (this.userInputTime !== null) {
          return this.userInputTime;
        } else {
          this.getSpinnerDisable(this.date)
          return formatDate(this.date, this.timeFormat);
        }
      },

      visibleDate() {
        if (this.userInputDate !== null) {
          return this.userInputDate;
        } else {
          return formatDate(this.value || this.defaultValue || new Date(), this.dateFormat);
        }
      },

      yearLabel() {
        const yearTranslation = this.t('el.datepicker.year');
        if (this.currentView === 'year') {
          const startYear = Math.floor(this.year / 10) * 10;
          if (yearTranslation) {
            return startYear + ' ' + yearTranslation + ' - ' + (startYear + 10) + ' ' + yearTranslation;
          }
          return startYear + ' - ' + (startYear + 10);
        }
        return this.year + ' ' + yearTranslation;
      },

      timeFormat() {
        if (this.format) {
          return extractTimeFormat(this.format);
        } else {
          return 'HH:mm:ss';
        }
      },

      showSeconds() {
        let nowFormat = this.timeFormat
        return nowFormat.indexOf('ss') > -1
      },

      dateFormat() {
        if (this.format) {
          return extractDateFormat(this.format);
        } else {
          return 'yyyy-MM-dd';
        }
      },

      amPmMode() {
        if ((this.format || '').indexOf('A') !== -1) return 'A';
        if ((this.format || '').indexOf('a') !== -1) return 'a';
        return '';
      }
    }
  };
</script>
