<template>
  <div
    class="o-device has-padding"
    :class="{ 'u-pointer': isOnline }"
    @click="askStatus"
  >
    <div class="l-flex__none l-flex--row o-device__block o-device__header">
      <i
        class="l-flex__none o-device__status dark"
        :class="statusClass"
      />
      <auto-text
        class="l-flex__fill"
        :text="name"
      />
      <template v-if="isOnline">
        <i
          v-if="isShotting"
          class="l-flex__none el-icon-loading"
        />
        <i
          v-else
          class="l-flex__none o-device__shot"
          @click.stop="screenshot"
        />
      </template>
      <span
        class="l-flex__none o-device__tip"
        :class="statusType"
      >
        {{ statusTip }}
      </span>
    </div>
    <div
      v-if="shot"
      class="l-flex__fill o-device__block o-device__preview"
      :style="styles"
    />
    <div
      v-if="isActivated"
      class="l-flex__auto l-flex--col center o-device__block o-device__info"
    >
      <i
        v-if="loadingTimeline"
        class="l-flex__none el-icon-loading"
      />
      <template v-else>
        <template v-if="current">
          <auto-text
            class="l-flex__none o-device__current"
            :text="current.name"
          />
          <div class="l-flex__auto l-flex--row o-device__time">
            <span class="o-device__hms">
              {{ current.startTime }}
              <span class="o-device__ymd">{{ current.startDate }}</span>
            </span>
            <span class="o-device__line" />
            <span class="o-device__hms">
              {{ current.endTime }}
              <span class="o-device__ymd">{{ current.endDate }}</span>
            </span>
          </div>
        </template>
        <span
          v-else
          class="o-device__current"
        >
          当前暂无节目
        </span>
        <auto-text
          v-if="next"
          class="l-flex__none o-device__next"
          :text="nextInfo"
        />
      </template>
    </div>
    <auto-text
      class="l-flex__none o-device__block o-device__footer"
      :text="address"
    />
    <el-dialog
      :title="name"
      :visible.sync="show"
      custom-class="c-dialog"
      :before-close="handleClose"
      append-to-body
    >
      <div class="u-text-center">
        <i
          v-if="loading"
          class="el-icon-loading"
        />
        <template v-else>
          <div class="has-bottom-padding">
            <button
              class="c-sibling-item o-button"
              @click="ask"
            >
              检测状态
            </button>
            <button
              class="c-sibling-item o-button"
              @click="restart"
            >
              重启
            </button>
          </div>
          <template v-if="message">
            <div v-if="message.type === 's'">{{ message.value }}</div>
          </template>
        </template>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getProgram } from '@/api/program'
import {
  getSchedule,
  getTimeline
} from '@/api/calendar'
import { parseTime } from '@/utils'
import {
  listen,
  unlisten,
  publish
} from '@/utils/mqtt'

const deviceCache = {}

export default {
  name: 'DeviceCard',
  props: {
    device: {
      type: Object,
      default: null
    }
  },
  data () {
    return {
      show: false,
      loading: false,
      message: null,
      isShotting: false,
      shot: null,
      timeline: [],
      loadingTimeline: false,
      current: null,
      next: null
    }
  },
  computed: {
    name () {
      return this.device.name
    },
    isActivated () {
      return this.device.activate === 2
    },
    isOnline () {
      return this.device.onlineStatus === 1
    },
    statusClass () {
      return this.isActivated
        ? this.isOnline
          ? 'u-color--success'
          : 'u-color--error'
        : this.device.activate
          ? 'u-color--primary'
          : 'u-color--warning'
    },
    statusType () {
      return this.isActivated
        ? this.isOnline
          ? 'success'
          : 'error'
        : this.device.activate
          ? 'primary'
          : 'warning'
    },
    statusTip () {
      return this.isActivated
        ? this.isOnline
          ? '在线'
          : '离线'
        : this.device.activate
          ? '已激活'
          : '未激活'
    },
    address () {
      return `地址：${this.device.remark}`
    },
    styles () {
      return this.isOnline && this.shot ? {
        backgroundImage: `url("${this.shot}"`
      } : null
    },
    nextInfo () {
      return this.next ? `下一场：${this.next.startDate} ${this.next.startTime} ${this.next.name}` : ''
    }
  },
  mounted () {
    if (this.isActivated) {
      listen(this.onMessage)
      this.getTimeline()
    }
    this.$timer = -1
  },
  beforeDestroy () {
    if (this.isActivated) {
      unlisten(this.onMessage)
      if (this.show) {
        this.handleClose()
      }
    }
    clearTimeout(this.$timer)
  },
  methods: {
    askStatus () {
      if (!this.isOnline) {
        return
      }
      this.show = true
    },
    handleClose () {
      this.loading = false
      this.message = null
      this.show = false
    },
    onMessage (topic, message) {
      if (message) {
        const result = new RegExp(`${this.device.productId}/${this.device.id}/(.+)`).exec(topic)
        if (result) {
          switch (result[1]) {
            case 'status/reply':
              this.onAskReply(message)
              break
            case 'screenshot/reply':
              this.onScreenshotReply(message)
              break
            case 'calendar/update':
              this.onCalendarUpdate(message)
              break
            default:
              break
          }
        }
      }
    },
    publish (invoke) {
      return publish(`${this.device.productId}/${this.device.id}/${invoke}/ask`, JSON.stringify({ timestamp: Date.now() }))
    },
    ask () {
      this.publish('status').then(() => {
        this.loading = true
        this.message = null
      }, () => {
        this.$message({
          type: 'warning',
          message: '正在连接，请稍后再试'
        })
      })
    },
    onAskReply (message) {
      this.loading = false
      try {
        message = JSON.parse(message)
        switch (message.status) {
          case 1:
            this.message = { type: 's', value: '未播放节目，处于默认状态' }
            break
          case 2:
            this.message = { type: 's', value: '正在播放节目' }
            break
          case 3:
            this.message = { type: 's', value: '解析节目异常，请重新发布' }
            break
          default:
            this.message = { type: 's', value: '未知' }
            break
        }
      } catch {
        this.message = { type: 's', value: '解析异常，请重试' }
      }
    },
    screenshot () {
      this.publish('screenshot').then(() => {
        this.isShotting = true
        this.shot = null
      }, () => {
        this.$message({
          type: 'warning',
          message: '正在连接，请稍后再试'
        })
      })
    },
    onScreenshotReply (message) {
      this.isShotting = false
      this.shot = `data:image/jpeg;base64,${message.replace(/\s/g, '')}`
    },
    restart () {
      this.publish('restart').then(() => {
        this.loading = true
        this.message = null
      }, () => {
        this.$message({
          type: 'warning',
          message: '正在连接，请稍后再试'
        })
      })
    },
    onRestartReply () {
      this.loading = false
    },
    onCalendarUpdate (message) {
      clearTimeout(this.$timer)
      try {
        message = JSON.parse(message)
        this.timeline = (message.eventDetail || []).map(this.createItem)
        this.checkTimeline()
      } catch {
        this.getTimeline()
      }
    },
    createItem ({ programCalendarId, type, startTimestamp, endTimestamp }) {
      const startDateTime = new Date(Number(startTimestamp))
      const endDateTime = endTimestamp ? new Date(Number(endTimestamp)) : null
      return {
        type, startDateTime, endDateTime,
        id: programCalendarId,
        name: null,
        startDate: parseTime(startDateTime, '{y}.{m}.{d}'),
        startTime: parseTime(startDateTime, '{h}:{i}:{s}'),
        endDate: endDateTime ? parseTime(endDateTime, '{y}.{m}.{d}') : '',
        endTime: endDateTime ? parseTime(endDateTime, '{h}:{i}:{s}') : ''
      }
    },
    getTimeline () {
      this.loadingTimeline = true
      getTimeline(this.device.id, { custom: true }).then(({ data }) => {
        this.timeline = (JSON.parse(data.eventDetail) || []).map(this.createItem)
        this.checkTimeline()
      }).catch(({ isCancel }) => {
        if (!isCancel) {
          this.$timer = setTimeout(this.getTimeline, 2000)
        }
      })
    },
    checkTimeline () {
      this.loadingTimeline = true
      const now = Date.now()
      const current = this.timeline.findIndex(({ startDateTime, endDateTime }) => {
        return now >= startDateTime && (!endDateTime || now <= endDateTime)
      })
      this.current = this.timeline[current]
      this.next = this.current && this.timeline[current + 1]
      this.next && this.getName(this.next).then(name => {
        this.next.name = name
      })
      this.getDetail()
    },
    finishTimeline () {
      this.loadingTimeline = false
      const time = this.current ? this.current.endDateTime : this.next ? this.next.startDateTime : null
      clearTimeout(this.$timer)
      if (time) {
        this.$timer = setTimeout(this.checkTimeline, time - Date.now())
      }
    },
    getName (item) {
      if (item.id) {
        if (item.name) {
          return Promise.resolve(item.name)
        }
        if (deviceCache[item.id]) {
          return Promise.resolve(deviceCache[item.id])
        }
        return (item.type === 1 ? getProgram : getSchedule)(item.id, { custom: true }).then(({ name }) => {
          deviceCache[item.id] = name
          return name
        })
      }
      return Promise.resolve('未知')
    },
    getDetail () {
      if (this.current) {
        this.getName(this.current).then(name => {
          this.current.name = name
          this.finishTimeline()
        }).catch(({ isCancel }) => {
          if (!isCancel) {
            this.$timer = setTimeout(this.getDetail, 2000)
          }
        })
      } else {
        this.finishTimeline()
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.o-device {
  display: inline-flex;
  flex-direction: column;
  // height: 220px;
  color: $black;
  line-height: 1;
  border-radius: $radius;
  background-color: #fff;
  background-size: contain;

  &__block + &__block {
    margin-top: $spacing;
  }

  &__header {
    justify-self: flex-start;
    height: 24px;
    font-size: 16px;
    font-weight: bold;
  }

  &__status {
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-right: 6px;
    border-radius: 50%;
    background-color: currentColor;
  }

  &__shot {
    display: inline-block;
    width: 24px;
    height: 24px;
    background: url("~@/assets/icon_screenshot.png") 0 0 / 100% 100% no-repeat;
  }

  &__tip {
    display: inline-block;
    position: relative;
    left: 16px;
    padding: 2px 8px 2px 10px;
    margin-left: -10px;
    color: #fff;
    font-size: 12px;
    line-height: 1;
    border-radius: 9px 0 0 9px;
    background-color: $success--dark;

    &.primary {
      background-color: $primary;
    }

    &.error {
      background-color: $error;
    }

    &.warning {
      background: $warning;
    }
  }

  &__preview {
    padding-top: 50%;
    background-position: center center;
    background-size: contain;
    background-repeat: no-repeat;
  }

  &__info {
    justify-content: center;
    height: 100px;
  }

  &__current {
    align-self: stretch;
    font-size: 20px;
    font-weight: bold;
    text-align: center;
  }

  &__time {
    margin: $spacing 0 24px;
    font-size: 20px;
  }

  &__line {
    display: inline-block;
    width: 20px;
    margin: 0 10px;
    border-bottom: 1px solid currentColor;
  }

  &__hms {
    position: relative;
    font-weight: bold;
  }

  &__ymd {
    position: absolute;
    top: 100%;
    left: 50%;
    color: $gray;
    font-size: 12px;
    font-weight: normal;
    transform: translate(-50%, 4px);
  }

  &__next {
    align-self: stretch;
    color: $gray;
    font-size: 12px;
    text-align: center;
  }

  &__current + &__next {
    margin-top: 24px;
  }

  &__footer {
    font-size: 12px;
    font-weight: bold;
  }
}

.o-shot {
  display: inline-block;
  width: 480px;
  height: 270px;
  object-fit: contain;
}
</style>
