<template>
  <box
    title="节目排期"
    @fullscreen="onFullscreen"
  >
    <div
      class="l-flex__fill l-flex--col c-timeline"
      :class="{ fullscreen }"
    >
      <div class="l-flex--row prioritys">
        <div class="l-flex--row priority">
          <div class="priority3 priority__block" />
          <div>高优先级</div>
        </div>
        <div class="l-flex--row priority">
          <div class="priority2 priority__block" />
          <div>中优先级</div>
        </div>
        <div class="l-flex--row priority">
          <div class="priority1 priority__block" />
          <div>低优先级</div>
        </div>
      </div>
      <div class="l-flex__none l-flex c-timeline__row header">
        <div class="l-flex__auto l-flex--row c-timeline__right">
          <i
            class="l-flex__none c-timeline__arrow left el-icon-arrow-left u-pointer"
            :class="{ display: canPrevious }"
            @click="offsetTime(-1)"
          />
          <div class="l-flex__auto l-flex--row c-timeline__time">
            <div
              v-for="time in times"
              :key="time"
              class="l-flex__none"
            >
              {{ time }}
            </div>
          </div>
          <i
            class="l-flex__none c-timeline__arrow right display el-icon-arrow-right u-pointer"
            @click="offsetTime(1)"
          />
        </div>
      </div>
      <div class="l-flex--col c-timeline__main u-relative">
        <div class="l-flex__auto u-overflow-y--auto">
          <div class="l-flex c-timeline__row">
            <div class="l-flex__auto l-flex--col c-timeline__right">
              <div
                v-if="deviceOptions.loading"
                class="l-flex--row c-timeline__programs"
              >
                <i class="el-icon-loading has-padding--h" />加载中...
              </div>
              <div
                v-else-if="deviceOptions.error"
                class="l-flex--row c-timeline__programs has-padding--h"
              >
                <el-link
                  type="warning"
                  @click.stop="getTimeline"
                >
                  获取失败，点击重试
                </el-link>
              </div>
              <template v-else-if="deviceOptions.list.length">
                <div
                  v-for="(programs, index) in deviceOptions.list"
                  :key="index"
                  class="c-timeline__programs l-flex--row u-relative"
                >
                  <div
                    v-for="program in programs"
                    :key="program.key"
                    class="l-flex__none l-flex--row c-event-program u-pointer"
                    :class="`priority${program.event.priority}`"
                    :style="program.style"
                  >
                    <i
                      class="l-flex__none c-event-program__img o-program"
                      :style="program.event.style"
                    />
                    <div class="l-flex__auto">
                      <auto-text
                        class="c-event-program__time"
                        :text="program.time"
                        :tag="program.style.width"
                      />
                      <auto-text
                        class="c-event-program__name"
                        :text="program.event.target.name"
                        :tag="program.style.width"
                      />
                    </div>
                  </div>
                </div>
              </template>
              <div
                v-else
                class="l-flex--row c-timeline__programs has-padding--h"
              >
                当前时段暂无节目
              </div>
            </div>
          </div>
        </div>
        <div
          v-show="style"
          class="c-timeline__line"
        >
          <div
            class="c-timeline__mask"
            :style="style"
          />
        </div>
      </div>
    </div>
  </box>
</template>

<script>
import { getTimeline } from '@/api/device'
import { EventFreq } from '@/constant'
import {
  toDate,
  toDateStr,
  toTimeStr,
  toZeroPoint,
  getNearestHitDate,
  getStartDate,
  getFinishDate,
  pickMin,
  pickMax
} from '@/utils/event'
import { EventCache } from '@/utils/cache'
import Box from './Box'

export default {
  name: 'GatewayTimeline',
  components: { Box },
  props: {
    device: {
      type: Object,
      required: true
    }
  },
  data () {
    return {
      fullscreen: false,
      style: null,
      canPrevious: false,
      startHour: 0,
      times: [],
      current: null,
      deviceOptions: {
        loading: true,
        error: false,
        events: [],
        list: []
      }
    }
  },
  created () {
    this.TIME_COUNT = 3
    this.initTimes(new Date())
    this.getTimeline()
    this.$timer = setInterval(this.calcLine, 1000)
  },
  beforeDestroy () {
    clearInterval(this.$timer)
  },
  methods: {
    onFullscreen (fullscreen) {
      this.fullscreen = fullscreen
      this.TIME_COUNT = fullscreen ? 5 : 3
      this.initTimes(this.$startDateTime)
      if (this.deviceOptions.error) {
        this.getTimeline()
      } else {
        this.calcEvents()
      }
    },
    getTimeline () {
      const options = this.deviceOptions
      options.error = false
      options.loading = true
      getTimeline(this.device.id, { custom: true })
        .finally(() => {
          options.loading = false
        })
        .then(
          events => {
            const now = Date.now()
            options.events = this.transformEvents(
              events
                .map(this.transformEvent)
                .filter(({ until }) => !until || now < toDate(until))
            )
            this.calcEvents()
          },
          () => {
            options.error = true
            options.list = []
          }
        )
    },
    transformEvent (event) {
      return {
        ...event,
        time: this.getTime(event),
        startDateTime: toDate(event.start),
        endDateTime: toDate(event.until),
        style: null,
        img () {
          EventCache.getImage(this.target.type, this.target.id).then(img => {
            if (img) {
              this.style = { backgroundImage: `url("${img}")` }
            }
          })
        }
      }
    },
    transformEvents (events) {
      const map = {}
      for (let i = 0; i < events.length; i++) {
        const event = events[i]
        if (!map[event.priority]) {
          map[event.priority] = []
        }
        map[event.priority].push(event)
      }
      return Object.keys(map)
        .sort((a, b) => (a > b ? -1 : 1))
        .map(key => map[key].sort((a, b) => toDate(a.start) - toDate(b.start)))
    },
    getTime (event) {
      const { freq, start, until, byDay, startTime, endTime } = event
      switch (freq) {
        case EventFreq.WEEKLY:
          return `自${start.split(' ')[0]}开始${
            until ? `至${until.split(' ')[0]}前` : ''
          } 每周${byDay
            .split(',')
            .map(val => ['日', '一', '二', '三', '四', '五', '六'][val])
            .join('、')} ${startTime} - ${endTime}`
        default:
          return until ? `${start} - ${until}` : `自${start}开始`
      }
    },
    initTimes (date) {
      const maxDate = pickMax(toDate(date), new Date())
      this.current = toZeroPoint(maxDate)
      this.startHour = Math.min(24 - this.TIME_COUNT + 1, maxDate.getHours())
      this.refreshTimes(this.startHour)
    },
    refreshTimes (start) {
      const count = this.TIME_COUNT
      const times = []
      for (let i = 0; i < count; i++) {
        times.push(`${(start + i).toString().padStart(2, '0')}:00`)
      }
      this.times = times

      this.$startDateTime = toDate(this.current.getTime() + start * 3600000)
      this.$endDateTime = toDate(this.current.getTime() + (start + count - 1) * 3600000)
      this.calcLine()
      this.calcEvents()
    },
    calcLine () {
      const now = Date.now()
      this.canPrevious = now < this.$startDateTime
      if (now < this.$startDateTime || now >= this.$endDateTime) {
        this.style = null
      } else {
        this.style = { left: `${Math.min(100, (now - this.$startDateTime) / ((this.TIME_COUNT - 1) * 36000))}%` }
      }
    },
    offsetTime (offset) {
      const next = this.startHour + offset
      if (offset < 0) {
        const timestamp = this.current.getTime() + next * 3600000
        const now = new Date()
        now.setMinutes(0)
        now.setSeconds(0)
        now.setMilliseconds(0)
        if (timestamp >= now) {
          if (next < 0) {
            this.initTimes(timestamp)
          } else {
            this.refreshTimes((this.startHour = next))
          }
        }
      } else if (next > 24 - this.TIME_COUNT + 1) {
        this.initTimes(this.$endDateTime)
      } else {
        this.refreshTimes((this.startHour = next))
      }
    },
    calcSamePriorityEvents (events) {
      const total = (this.TIME_COUNT - 1) * 36000
      const arr = []
      for (let i = 0; i < events.length; i++) {
        const event = events[i]
        const { startDateTime, endDateTime } = event
        if (
          (endDateTime && endDateTime <= this.$startDateTime)
          || startDateTime >= this.$endDateTime
        ) {
          continue
        }
        const hit = getNearestHitDate(
          event,
          this.$startDateTime,
          this.$endDateTime
        )
        if (hit) {
          const startDate = getStartDate(event, hit)
          const endDate = getFinishDate(event, hit)
          arr.push({
            key: event.target.id,
            event,
            time: `${toDateStr(startDate)} ${toTimeStr(
              startDate
            )} - ${toDateStr(endDate)} ${toTimeStr(endDate)}`,
            style: {
              left: `${(hit - this.$startDateTime) / total}%`,
              width: `${
                (pickMin(this.$endDateTime, getFinishDate(event, hit)) - hit) /
                total
              }%`,
              zIndex: event.priority
            }
          })
          event.img?.()
          if (endDate >= this.$endDateTime) {
            break
          }
        }
      }
      return arr
    },
    calcEvents () {
      const options = this.deviceOptions
      if (options.loading || options.error) {
        return
      }
      options.list = options.events
        .map(this.calcSamePriorityEvents)
        .filter(events => events.length)
    }
  }
}
</script>

<style lang="scss" scoped>
.c-timeline {
  &__main {
    flex: 1 1 auto;
    min-height: 0;
  }

  &__row {
    & + & {
      border-top: 1px solid $border;
    }
  }

  &__row.header &__right {
    color: $black;
    user-select: none;
    background-color: transparent;
  }

  &__right {
    position: relative;
    color: $info--dark;
    font-size: 14px;
    line-height: 1;
    overflow: hidden;
  }

  &__programs {
    height: 40px;

    & + & {
      border-top: 1px dashed $border;
    }
  }

  &__arrow {
    visibility: hidden;
    position: absolute;
    top: 50%;
    color: #fff;
    font-size: 12px;
    border-radius: 4px;
    background-color: $blue;
    transform: translateY(-50%);

    &.left {
      left: 0;
    }

    &.right {
      right: 0;
    }

    &.display {
      visibility: visible;
    }
  }

  &__time {
    justify-content: space-around;
    padding: 10px 0;
    color: #fff;
    font-size: 14px;
  }

  &__line {
    position: absolute;
    top: -12px;
    left: 0px;
    right: 0;
    bottom: 0;
    pointer-events: none;
  }

  &__mask {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    color: #ff0000;
    border-right: 1px solid currentColor;
    z-index: 6;

    &::after {
      content: "";
      position: absolute;
      top: 0;
      right: -5px;
      width: 9px;
      height: 9px;
      border-radius: 50%;
      background-color: currentColor;
    }
  }

  &.fullscreen {
    .c-event-program,
    .c-timeline__time,
    .c-timeline__arrow {
      font-size: 20px;
      font-weight: 600;
    }

    .c-timeline__programs {
      height: 96px;
    }

    .c-event-program__img {
      width: 160px;
    }

    .prioritys {
      margin-bottom: 16px;
    }

    .priority {
      font-size: 20px;

      &__block {
        width: 20px;
        height: 20px;
      }
    }
  }
}

.c-event-program {
  position: absolute;
  top: 0;
  bottom: 0;
  font-size: 12px;
  overflow: hidden;

  &__img {
    width: 64px;
    margin-right: 8px;
  }

  &__name {
    margin-top: 10px;
  }
}

.o-priority {
  display: inline-block;
  padding: 4px;
  font-size: 12px;
  border-radius: 4px;

  & + & {
    margin-left: 4px;
  }
}

.priority1 {
  color: #fff;
  background-color: #79829d;
}

.priority2 {
  color: #fff;
  background-color: #7e4de2;
}

.priority3 {
  color: #fff;
  background-color: #ff2222;
}

.o-program {
  display: inline-block;
  font-size: 0;
  border-radius: $radius--mini;
  background: rgba(#000, 0.8) url("~@/assets/program_bg.png") center center /
    100% 100% no-repeat;

  &::before {
    content: "";
    display: inline-block;
    padding-top: 56.25%;
  }
}

.prioritys {
  align-self: flex-end;
}

.priority {
  font-size: 12px;

  & + & {
    margin-left: 1em;
  }

  &__block {
    width: 12px;
    height: 12px;
    margin-right: 4px;
  }
}
</style>
