<script>
import ElPopover from 'modesign-ui/packages/popover';
import fecha from 'modesign-ui/src/utils/date';
import deepmerge from 'deepmerge';
import { range as rangeArr, formatDate, clearTime, modifyTime, getFirstDayOfMonth, getPrevMonthLastDays, getMonthDays, getI18nSettings, validateRangeInOneMonth } from 'modesign-ui/src/utils/date-util';

const dayMilliseconds = 24 * 60 * 60 * 1000;
const WEEKSLENGTH = 7;
const getDateTimestamp = function(time) {
  if (typeof time === 'number' || typeof time === 'string') {
    return clearTime(new Date(time)).getTime();
  } else if (time instanceof Date) {
    return clearTime(time).getTime();
  } else {
    return NaN;
  }
};

export default {
  props: {
    selectedDay: String, // formated date yyyy-MM-dd
    range: {
      type: Array,
      validator(val) {
        if (!(val && val.length)) return true;
        const [start, end] = val;
        return validateRangeInOneMonth(start, end);
      }
    },
    date: Date,
    hideHeader: Boolean,
    firstDayOfWeek: Number,
    useMonthRange: Boolean,
    events: {
      type: Array,
      default: () => []
    }
  },

  components: { ElPopover },

  data() {
    return {
      showFlatEvents: [], // 将parseEvents拍平
      parseEvents: [[], [], [], [], [], []], // 解析过的的事件
      parseEventsStorage: [[], [], [], [], [], []], // 记录parseEvents层级
      monthRange: [0, 0],  // 本月的时间范围 时间戳
      dayEvents: [],
      tempEvents: [],
      clickPickDay: false,
      startDate: null,
      endDate: null,
      clickType: null
    }
  },

  inject: ['elCalendar'],

  methods: {
    init() {
      this.tempEvents = [];
      this.setRangeDate();
      this.splitEvents();
    },
    // 处理时间
    dealEvents(startTime, endTime, index) {
      const arr = [];
      this.dayEvents.forEach(item => {
        const { start, end } = item;
        // 将不在该周范围的事件过滤掉
        if (this.isDateInRange(start, startTime, endTime) || this.isDateInRange(end, startTime, endTime)) {
          const arrItem = Object.assign({ ...item }, {
            startTime,
            endTime,
            rowIndex: index
          })
          arr.push(arrItem);
        } else if (this.isDateAllOutRange(start, end, startTime, endTime)) {
          // 如果开始时间早于范围开始时间且结束时间早于范围结束时间,也属于该周任务
          const arrItem = Object.assign({ ...item }, {
            startTime,
            endTime,
            rowIndex: index
          })
          arr.push(arrItem);
        }
      });
      this.$set(this.tempEvents, index, arr);
    },
    // 设定本月开始和结束时间
    setRangeDate() {
      if (this.isInRange) {
        const [start, end] = this.range;
        this.monthRange[0] = start;
        this.monthRange[1] = end;
      } else if (this.useMonthRange) {
        const { startDate, endDate } = this.getStartAndEnd(this.date || new Date())
        this.monthRange[0] = startDate;
        this.monthRange[1] = endDate;
      } else {
        const date = this.date || new Date();
        const currentMonth = date.getMonth();
        this.monthRange[0] = new Date(date.getFullYear(), currentMonth, 1);
        this.monthRange[1] = new Date(date.getFullYear(), currentMonth + 1, 0, 23, 59, 59);
      }
    },
    // 获取月份日历中起始日期和结束日期
    getStartAndEnd(day, parse) {
      const date = new Date(day)
      const year = date.getFullYear()
      const month = date.getMonth()
      const weekDay = new Date(date.setDate(1)).getDay()
      let startDate = new Date(year, month, (weekDay - 1) * -1, 0, 0, 0)
      let endDate = new Date(year, month, 42 - weekDay, 23, 59, 59)
      if (parse === 'yyyy-MM-dd') {
        startDate = formatDate(startDate)
        endDate = formatDate(endDate)
      }
      return { startDate, endDate }
    },
    // 过滤非本月任务
    splitEvents() {
      this.dayEvents = [];
      const [ rangeStart, rangeEnd ] = this.monthRange;
      this.events.forEach(item => {
        const { start, end } = item;
        // 将不在该范围的事件过滤掉
        if (this.isDateInRange(start, rangeStart, rangeEnd) || this.isDateInRange(end, rangeStart, rangeEnd)) {
          this.dayEvents.push(item);
        } else if (this.isDateAllOutRange(start, end, rangeStart, rangeEnd)) {
          // 如果开始时间早于范围开始时间且结束时间早于范围结束时间,也属于本月任务
          this.dayEvents.push(item);
        }
      });
    },
    isDateAllOutRange(start, end, rangeStart, rangeEnd) {
      if (!start || !end) {
        return false
      }
      return start.getTime() <= rangeStart.getTime() && end >= rangeEnd.getTime();
    },
    // 是否在时间范围区间内
    isDateInRange(date, rangeStart, rangeEnd) {
      if (!date) {
        return false;
      }
      return date.getTime() >= rangeStart.getTime() && date <= rangeEnd.getTime();
    },
    // 全天任务排列组合
    rangeAllEvents(list) {
      // 根据start将事件排序
      list.sort((a, b) => {
        return a.start - b.start;
      });
      // 按照start时间 用map集合将事件组合
      const filtered = this.filterEvents(list);
      filtered.forEach((val, item) => {
        const startDate = clearTime(new Date(item));
        const curr = filtered.get(item);
        this.findBefindEvents(curr, startDate);
      });
    },
    filterEvents(events) {
      const res = new Map()
      events.forEach(item => {
        let key = 0;
        if (new Date(item.start).getTime() - new Date(item.startTime).getTime() > 0) {
          key = clearTime(item.start).toString();
        } else {
          key = clearTime(item.startTime).toString();
        }
        if (res.has(key)) {
          res.set(key, [...res.get(key), item]);
        } else {
          res.set(key, [item]);
        }
      });
      return res;
    },
    // 解析事件并计算位置及宽度
    findBefindEvents(event, startDate) {
      event.forEach(item => {
        const itemInfo = { ...item };
        const { end } = item;
        let dateLength;
        let j = 0;
        if (new Date(startDate).getTime() >  new Date(item.startTime).getTime()) {
          j = this.getDateRange(startDate, item.startTime) - 1;
        } else {
          j = 0;
        }
        let i = 0;
        while (Boolean(this.parseEvents[item.rowIndex][i]) && Boolean(this.parseEvents[item.rowIndex][i][j])) {
          i++;
        }
        if (!end) {
          dateLength = 1;
        } else  {
          dateLength = this.isDateInRange(end, item.startTime, item.endTime) ? this.getDateRange(end, startDate) : this.getDateRange(item.endTime, startDate);
        }
        itemInfo.leftPos = this.round(Number(j) / WEEKSLENGTH);
        itemInfo.width = Math.min(1 - this.round(Number(j) / WEEKSLENGTH), this.round((dateLength) * 1 / WEEKSLENGTH));
        itemInfo.widthLength = Math.min(7 - this.round(Number(j)), dateLength);
        itemInfo.topInd = i;

        item.topInd = i;
        item.headerIndex = j;

        const list = new Array(dateLength).fill().map((x,i) => i);
        list.forEach(dist => {
          if (!this.parseEvents[item.rowIndex][i]) {
            this.parseEvents[item.rowIndex][i] = [];
          }
          if (dist > 0) {
            this.parseEvents[item.rowIndex][i][j + dist] = deepmerge({
              leftPos: this.round(Number(j + dist) / WEEKSLENGTH)
            }, item);
          } else {
            this.parseEvents[item.rowIndex][i][j + dist] = itemInfo;
          }
          if (!this.parseEventsStorage[item.rowIndex][j + dist] || this.parseEventsStorage[item.rowIndex][j + dist] < i) {
            this.parseEventsStorage[item.rowIndex][j + dist] = i;
          }
        });
      });
    },
    round(value) {
      return Math.round(value * 1000) / 1000;
    },
    // 获取两个时间间隔（最小间隔为天）
    getDateRange(date1, date2) {
      const interval = clearTime(date1).getTime() - clearTime(date2).getTime();
      return interval / dayMilliseconds + 1;
    },
    toNestedArr(days) {
      return rangeArr(days.length / 7).map((_, index) => {
        const start = index * 7;
        return days.slice(start, start + 7);
      });
    },

    getFormateDate(day, type) {
      if (!day || ['prev', 'current', 'next'].indexOf(type) === -1) {
        throw new Error('invalid day or type');
      }
      let prefix = this.curMonthDatePrefix;
      if (type === 'prev') {
        prefix = this.prevMonthDatePrefix;
      } else if (type === 'next') {
        prefix = this.nextMonthDatePrefix;
      }
      day = `00${day}`.slice(-2);
      return `${prefix}-${day}`;
    },

    getCellClass({ text, type}) {
      const classes = ['el-calendar-month__row-item', type];
      if (type === 'current') {
        const date = this.getFormateDate(text, type);
        if (date === this.selectedDay && this.clickPickDay) {
          classes.push('is-selected');
        }
        if (date === this.formatedToday) {
          classes.push('is-today');
        }
      }
      return classes;
    },

    pickDay({ text, type }) {
      if (this.clickType !== 'clickEvents') {
        this.clickPickDay = true;
        const date = this.getFormateDate(text, type);
        this.$emit('pick', date);
      } else {
        this.clickType = null;
      }
    },

    clickEvents(e) {
      // 点击事件不触发pickDay，如果以后需要点击也触发事件，则加个开关控制
      // e.stopPropagation();
      
      // 如果阻止冒泡，会出现：lastPopover存在时，点击第三条内容出现很多气泡，不点击非事件的地方就不会消失，所以通过clickType处理
      this.clickType = 'clickEvents'
    },

    cellRenderProxy({ text, type }) {
      let render = this.elCalendar.$scopedSlots.dateCell;
      if (!render) return <div class="el-calendar-day"><span class="month-text">{ text }</span></div>;

      const day = this.getFormateDate(text, type);
      const date = new Date(day);
      const data = {
        isSelected: this.selectedDay === day && this.clickPickDay,
        type: `${type}-month`,
        day,
        text
      };
      return render({ date, data });
    },

    getRowItems(obj, list, cell) {
      const text = cell.text + Math.round(obj.leftPos * 1000 * 7 / 1000);
      let cellDay = null;
      const year = this.date.getFullYear();
      const month = this.date.getMonth();
      if (cell.type === 'prev') {
        cellDay = new Date(year, month - 1, text, 0, 0, 0);
      } else if (cell.type === 'next') {
        cellDay = new Date(year, month + 1, text, 0, 0, 0);
      } else {
        cellDay = new Date(year, month, text, 0, 0, 0);
      }
      const result = [];
      const resultMap = new Map();
      list.forEach(item => {
        if (!(getDateTimestamp(item.start) > getDateTimestamp(cellDay) || getDateTimestamp(item.end) < getDateTimestamp(cellDay))) {
          if (!resultMap.get(item.topInd)) {
            result.push(item);
            resultMap.set(item.topInd, item);
          }
        }
      });
      return result;
    },
    dealParseEvents() {
      this.parseEventsStorage.forEach((rowList, index) => {
        rowList && rowList.forEach((item, i) => {
          if (item > 2) {
            // 对parseEvents进行处理，如果某个二维数组长度超过3，则要显示对应的lastContent
            const obj = this.parseEvents[index][2][i];
            if (obj && obj.topInd && !obj.topIndex) {
              obj.topIndex = 'lastContent';
              if (!obj.width) {
                this.parseEvents[index][2].forEach((foo, fooIndex) => {
                  if (fooIndex >= obj.headerIndex && fooIndex < obj.headerIndex + this.parseEvents[index][2][obj.headerIndex].widthLength) {
                    foo.topIndex = 'lastContent';
                    foo.width = 0.143; // 一格的宽度
                  }
                })
              }
            } else {
              // 存在一种情况，比如第三行没有值，但是第四行第五行有值，也需要在第三行展示lastContent
              this.parseEvents[index][2][i] = {
                topInd: 2,
                topIndex: 'lastContent',
                width: 0.143,
                leftPos: this.round(i / WEEKSLENGTH)
              }
            }
          }
        })
      })
    },
    dealMonthEvents() {
      this.init();
      const days = this.rows;
      days.forEach((item, index) => {
        const result = item
        if (this.events.length > 0) {
          let prevFlag = result[0].type === 'prev';
          let nextFlag = result[6].type === 'next';
          let startTime = 0;
          let endTime = 0;
          const monthStart = new Date(this.date);
          if (prevFlag) {
            startTime = new Date(this.date.getFullYear(), this.date.getMonth() - 1, result[0].text).getTime();
            endTime = startTime + dayMilliseconds * 6;
          } else if (nextFlag) {
            startTime = new Date(this.date.getFullYear(), result[0].type === 'next' ? this.date.getMonth() + 1 : this.date.getMonth(), result[0].text).getTime();
            endTime = startTime + dayMilliseconds * 6;
          } else {
            startTime = new Date(monthStart.setDate(result[0].text)).getTime();
            endTime = startTime + dayMilliseconds * 6;
          }
          this.dealEvents(modifyTime(new Date(startTime), 0, 0, 0), modifyTime(new Date(endTime), 23, 59, 59), index);
        }
      })
      this.showFlatEvents = [];
      this.parseEvents = [[], [], [], [], [], []];
      this.parseEventsStorage = [[], [], [], [], [], []];
      this.tempEvents.forEach(item => {
        if (item && item.length > 0) {
          this.rangeAllEvents(item);
        }
      });
      this.dealParseEvents();
      this.parseEvents.forEach(item => {
        this.showFlatEvents.push(item.flat());
      });
    },

    // 点击事件
    clickItem(item, type) {
      this.$emit('clickItem', item, type)
    }
  },

  computed: {
    WEEK_DAYS() {
      return getI18nSettings().dayNames_2;
    },
    prevMonthDatePrefix() {
      const temp = new Date(this.date.getTime());
      temp.setDate(0);
      return fecha.format(temp, 'yyyy-MM');
    },

    curMonthDatePrefix() {
      return fecha.format(this.date, 'yyyy-MM');
    },

    nextMonthDatePrefix() {
      const temp = new Date(this.date.getFullYear(), this.date.getMonth() + 1, 1);
      return fecha.format(temp, 'yyyy-MM');
    },

    formatedToday() {
      return this.elCalendar.formatedToday;
    },

    isInRange() {
      return this.range && this.range.length;
    },

    rows() {
      let days = [];
      // if range exists, should render days in range.
      if (this.isInRange) {
        const [start, end] = this.range;
        const currentMonthRange = rangeArr(end.getDate() - start.getDate() + 1).map((_, index) => ({
          text: start.getDate() + index,
          type: 'current'
        }));
        let remaining = currentMonthRange.length % 7;
        remaining = remaining === 0 ? 0 : 7 - remaining;
        const nextMonthRange = rangeArr(remaining).map((_, index) => ({
          text: index + 1,
          type: 'next'
        }));
        days = currentMonthRange.concat(nextMonthRange);
      } else {
        const date = this.date;
        let firstDay = getFirstDayOfMonth(date);
        firstDay = firstDay === 0 ? 7 : firstDay;
        const firstDayOfWeek = typeof this.firstDayOfWeek === 'number' ? this.firstDayOfWeek : 1;
        const offset = (7 + firstDay - firstDayOfWeek) % 7;
        const prevMonthDays = getPrevMonthLastDays(date, offset).map(day => ({
          text: day,
          type: 'prev'
        }));
        const currentMonthDays = getMonthDays(date).map(day => ({
          text: day,
          type: 'current'
        }));
        days = [...prevMonthDays, ...currentMonthDays];
        const nextMonthDays = rangeArr(42 - days.length).map((_, index) => ({
          text: index + 1,
          type: 'next'
        }));
        days = days.concat(nextMonthDays);
      }
      return this.toNestedArr(days);
    },

    weekDays() {
      const start = this.firstDayOfWeek;
      const { WEEK_DAYS } = this;

      if (typeof start !== 'number' || start === 0) {
        return WEEK_DAYS.slice();
      } else {
        return WEEK_DAYS.slice(start).concat(WEEK_DAYS.slice(0, start));
      }
    }
  },

  watch: {
    // 改变月份日历区间、useMonthRange和事件需要重新渲染事件
    date(val) {
      if (this.isInRange) {
        this.dealMonthEvents()
      } else {
        const { startDate, endDate } = this.getStartAndEnd(val, 'yyyy-MM-dd')
        if (this.startDate !== startDate && this.endDate !== endDate) {
          this.startDate = startDate
          this.endDate = endDate
          this.dealMonthEvents()
        }
      }
    },
    events: {
      immediate: true,
      deep: true,
      handler(val) {
        this.dealMonthEvents()
      }
    },
    useMonthRange(val) {
      this.dealMonthEvents()
    }
  },

  created() {
    // 月份日历有范围时没做判断开始结束范围，直接调用dealMonthEvents
    if (!this.isInRange) {
      const { startDate, endDate } = this.getStartAndEnd(this.date, 'yyyy-MM-dd')
      this.startDate = startDate
      this.endDate = endDate
    }

  },
  
  render(h) {
    const head = this.hideHeader ? null : (<div class='el-calendar-month__weeks'>
      {
        this.weekDays.map(day => <div class='el-calendar-month__week' key={day}>{ day }</div>)
      }
    </div>);
    let popperTemplate = null;
    let popoverTrigger = null;
    let lastContent = null;
    let lastPopover = null;
    let lastPopoverTrigger = null;
    if (this.events.length > 0) {
      popperTemplate = this.events[0].popper;
      popoverTrigger = this.events[0].popoverTrigger || 'hover';
      lastContent = this.events[0].lastContent;
      lastPopover = this.events[0].lastPopover
      lastPopoverTrigger = this.events[0].lastPopoverTrigger
    }
    return (
      <div
        class={{
          'el-calendar-month': true,
          'is-range': this.isInRange
        }}>
        {
          head
        }
        {
          this.rows.map((row, index) => <div
            class={{
              'el-calendar-month__row': true,
              'el-calendar-month__row--hide-border': index === 0 && this.hideHeader
            }}
            key={index}>
            {
              row.map((cell, key) => {
                let showFlatEvents = []
                if (this.showFlatEvents.length > 0) {
                  showFlatEvents = this.showFlatEvents[index].filter(x => x.width)
                }
                return <div key={key}
                  class={ this.getCellClass(cell) }
                  onClick={() => {
                    this.pickDay(cell)
                }}>
                { 
                  key === 0 && showFlatEvents.length > 0 && <ul class='el-calendar-month--events' onClick={(e) => {this.clickEvents(e)}}>
                    {
                      showFlatEvents.map((item, i) => {
                        const styleObj = {
                          left: `calc(${item.leftPos * 100}% + 8px)`,
                          width: `calc(${item.width * 100}% - 16px)`,
                          top: `calc(${item.topInd}em + 44px)`
                        }
                        const lastObj = {
                          width: `calc(14.3% - 8px)`,
                        }
                        return item.topInd < 3 && item.topIndex !== 'lastContent' ?
                        (
                          !(item.popperTemplate || item.popper) ? 
                            <li key={`${cell.text}${cell.type}${i}`}
                              class="el-calendar-month--events-li"
                              slot="reference"
                              style={styleObj}>
                              <div onClick={() => {
                                  this.clickItem(item)
                              }} class="el-calendar-month--events-item" style={[item.style || {}, item.leftStyle]}  domPropsInnerHTML={item.children}></div>
                            </li>
                          : <el-popover key={`${cell.text}${cell.type}${i}`} placement="top" trigger={popoverTrigger || 'hover'} maxHeight={item.maxHeight || 200} width={item.popoverWidth || 450} title={item.title || ''} open-delay={ item.openDelay || 400 } close-delay={ item.closeDelay || 300 }>
                            {
                              item.popperTemplate ? popperTemplate.call(this._renderProxy, h, { _self: this.$vnode.context, item })
                              : item.popper ? 
                              item.popper.call(this._renderProxy, h, { _self: this.$vnode.context, item }) : ''
                            }
                            <li key={`reference${cell.text}${cell.type}${i}`}
                              class="el-calendar-month--events-li"
                              slot="reference"
                              style={styleObj}>
                              <div class="el-calendar-month--events-item" style={[item.style || {}, item.leftStyle]}  domPropsInnerHTML={item.children}></div>
                            </li>
                          </el-popover>
                        ) : item.topIndex === 'lastContent' ?
                            lastContent ? 
                              lastPopover ? 
                                <el-popover key={`last${cell.text}${cell.type}${i}`} placement="top" maxHeight={item.maxHeight || 200} width={item.popoverWidth || 450} trigger={lastPopoverTrigger || 'hover'} title={item.title || ''} open-delay={ item.openDelay || 400 } close-delay={ item.closeDelay || 300 }>
                                  {
                                    lastPopover.call(this._renderProxy, h, { _self: this.$vnode.context, list: this.getRowItems(item, showFlatEvents, cell), item })
                                  }
                                  <li key={`lastReference${cell.text}${cell.type}${i}`}
                                    class="el-calendar-month--events-li"
                                    slot="reference"
                                    style={[styleObj, lastObj]}>
                                    <div class="el-calendar-month--events-last-item" style={[item.style || {}, item.leftStyle]}  domPropsInnerHTML={lastContent(this.getRowItems(item, showFlatEvents, cell), item)}></div>
                                  </li>
                                </el-popover>
                              : <li class="el-calendar-month--events-li" style={[styleObj, lastObj]}>
                                  <div onClick={() => {
                                    this.clickItem(item, 'last')
                                  }} class="el-calendar-month--events-last-item" domPropsInnerHTML={lastContent(this.getRowItems(item, showFlatEvents, cell), item)}></div>
                                </li>
                            : ''
                          : ''
                      })
                    }
                  </ul>
                }
                <div>
                  {
                    this.cellRenderProxy(cell)
                  }
                </div>
              </div>})
            }
          </div>)
        }
      </div>);
  }
};
</script>
