<template>
  <div class="warning-detail">
    <div class="warning-detail-head">
      <div class="warning-detail-title">
        <h2>预警详情</h2>
        <el-button size="mini" icon="el-icon-arrow-left" @click="goBack"
          >返回列表</el-button
        >
        <!-- <el-button
          @click="initSubscribeVideo"
          type="primary"
          size="mini"
          icon="el-icon-arrow-left"
          >开始视频</el-button
        >
        <el-button
          @click="closeSubscribeVideo"
          type="danger"
          size="mini"
          icon="el-icon-arrow-left"
          >关闭视频</el-button
        > -->
      </div>
      <div class="warning-detail-student">
        <div class="student-head">
          <div class="student-head-left">
            <p><i class="icon icon-user-act"></i></p>
            <p>
              <span>姓名：</span><span>{{ detailInfo.examStudentName }}</span>
            </p>
            <p>
              <span>证件号：</span><span>{{ detailInfo.identity }}</span>
            </p>
            <p>
              <span>科目（代码）：</span
              ><span>{{ detailInfo.courseNameCode }}</span>
            </p>
          </div>
          <div class="student-head-right">
            <el-button
              class="el-icon-btn"
              size="mini"
              type="primary"
              icon="el-icon-arrow-left"
              title="查看上一个"
              @click="changeStudent(0)"
              :disabled="holding"
            ></el-button>
            <el-button
              class="el-icon-btn"
              size="mini"
              type="primary"
              icon="el-icon-arrow-right"
              title="查看下一个"
              @click="changeStudent(1)"
              :disabled="holding"
            ></el-button>
          </div>
        </div>
        <div class="student-views">
          <div class="student-avatar">
            <img
              :src="detailInfo.basePhotoPath"
              :alt="detailInfo.examStudentName"
              v-if="detailInfo.basePhotoPath"
            />
            <div class="avatar-default" v-else>
              <i class="el-icon-user-solid"></i>
            </div>
          </div>
          <div class="student-video">
            <div class="student-video-item">
              <flv-media
                ref="FirstViewVideo"
                :live-url="firstViewVideo.liveUrl"
                v-if="firstViewVideoReady"
              ></flv-media>
              <div class="student-video-none" v-else>
                <i class="el-icon-video-camera-solid"></i>
              </div>
            </div>
            <div class="student-video-item">
              <flv-media
                ref="SecondViewVideo"
                :live-url="secondViewVideo.liveUrl"
                v-if="secondViewVideoReady"
              ></flv-media>
              <div class="student-video-none" v-else>
                <i class="el-icon-video-camera-solid"></i>
              </div>
            </div>
          </div>
        </div>
        <div class="student-exception">
          <ul>
            <li v-for="(log, index) in exceptionSummary" :key="index">
              <i>{{ index + 1 }}</i>
              <h4>{{ log.title }}</h4>
              <p v-if="log.desc">{{ log.desc }}</p>
              <!-- <p>
                时间段：
                <span v-if="log.startTime">{{ log.startTime }} ~ </span>
                <span>{{ log.endTime }}</span>
              </p>
              <p v-if="log.durationTime">持续时长约：{{ log.durationTime }}</p> -->
            </li>
          </ul>
        </div>
      </div>
    </div>

    <div class="warning-detail-body">
      <div class="warning-body-head clear-float">
        <div class="warning-body-head-action">
          <h3>考试轨迹</h3>
          <!-- <el-button
            class="el-icon-btn"
            type="primary"
            icon="icon icon-view"
          ></el-button> -->
          <el-button
            class="el-icon-btn"
            type="primary"
            icon="icon icon-text"
            @click="toSendTextMsg"
            title="发送文字提醒"
            v-if="detailInfo.statusCode === 'ANSWERING'"
          ></el-button>
          <el-button
            class="el-icon-btn"
            type="primary"
            icon="icon icon-audio"
            @click="toSendAudioMsg"
            v-if="detailInfo.statusCode === 'ANSWERING'"
          ></el-button>
          <el-popover
            class="warning-body-head-call"
            placement="bottom-start"
            v-model="popoverVisible"
            v-if="
              detailInfo.monitorVideoSource &&
              detailInfo.statusCode === 'ANSWERING'
            "
          >
            <el-button type="success" @click="answer(0)" :loading="holding"
              >语音通话</el-button
            >
            <el-button type="primary" @click="answer(1)" :loading="holding"
              >视频通话</el-button
            >
            <el-button type="primary" slot="reference">实时通话</el-button>
          </el-popover>
        </div>
        <div class="warning-body-head-info summary-line">
          <p class="summary-line-item">
            <i class="line-point line-point-danger"></i>
            <span class="line-name">系统预警</span>
            <span>{{ detailInfo.warningCount }}次</span>
          </p>
          <p class="summary-line-item">
            <i class="line-point line-point-danger"></i>
            <span class="line-name">陌生人脸</span>
            <span>{{ detailInfo.multipleFaceCount }}次</span>
          </p>
          <p class="summary-line-item">
            <i class="line-point line-point-danger"></i>
            <span class="line-name">异常处理</span>
            <span>{{ detailInfo.exceptionCount }}次</span>
          </p>
          <p class="summary-line-item">
            <span></span>
            <span>
              <b>违纪状态：</b>
              <b :class="{ 'color-danger': isBreach }">
                {{ isBreach ? "违纪" : "正常" }}
              </b>
            </span>
          </p>
          <el-button
            :type="isBreach ? 'success' : 'danger'"
            icon="icon icon-stop"
            @click="toBreach"
            >{{ isBreach ? "撤销违纪" : "违纪处理" }}</el-button
          >
          <el-button
            type="warning"
            icon="icon icon-forbide"
            @click="toFinish"
            v-if="detailInfo.statusCode === 'ANSWERING'"
            >强制收卷</el-button
          >
        </div>
      </div>
      <div class="warning-body-main">
        <div
          class="warning-history"
          v-for="log in detailInfo.examStudentLogList"
          :key="log.id"
        >
          <div class="warning-history-info">
            <h3>{{ log.title }}</h3>
            <p v-if="log.desc">{{ log.desc }}</p>
            <p>
              时间段：
              <span v-if="log.startTime">{{ log.startTime }} ~ </span>
              <span>{{ log.endTime }}</span>
            </p>
            <p v-if="log.durationTime">持续时长约：{{ log.durationTime }}</p>
          </div>
          <div
            :class="[
              'warning-history-type',
              log.viewType === 'common' ? 'type-common' : 'type-exception',
            ]"
          >
            <i
              :class="[
                'icon',
                {
                  'icon-current-step': log.viewType === 'common',
                  'icon-warning-act': log.viewType === 'warning',
                  'icon-net-break': log.viewType === 'exception',
                },
              ]"
            ></i>
          </div>
          <div class="warning-history-media">
            <ul class="media-list" v-if="log.photos">
              <li v-for="(photo, pindex) in log.photos" :key="pindex">
                <img :src="photo" />
              </li>
            </ul>
          </div>
        </div>
      </div>
    </div>

    <!-- student-breach-dialog -->
    <student-breach-dialog
      :instance="curDetail"
      @modified="breachFinish"
      ref="StudentBreachDialog"
    ></student-breach-dialog>
    <!-- warning-text-message-dialog -->
    <warning-text-message-dialog
      :record-id="recordId"
      ref="WarningTextMessageDialog"
    ></warning-text-message-dialog>
    <!-- audio-record-dialog -->
    <audio-record-dialog
      :record-id="recordId"
      ref="AudioRecordDialog"
    ></audio-record-dialog>

    <!-- 通话弹出层 -->
    <el-dialog
      custom-class="communication-dialog"
      :visible.sync="dialogVisible"
      width="600px"
      :show-close="false"
      :close-on-press-escape="false"
      :close-on-click-modal="false"
      append-to-body
      fullscreen
    >
      <div class="communication-box" v-show="!isWaiting">
        <div class="communication-host" id="communication-host"></div>
        <div class="communication-guest" id="communication-guest"></div>
        <div class="communication-action">
          <el-button round type="danger" @click="hangup">结束通话</el-button>
        </div>
        <div class="communication-info">
          <!-- <span>当前网络状态良好</span> -->
          <span>持续时长：<second-timer ref="SecondTimer"></second-timer></span>
        </div>
      </div>
      <div class="communication-wait" v-show="isWaiting">
        <p class="communication-wait-tips">等待接听…</p>
        <div class="communication-wait-avatar">
          <img
            :src="detailInfo.basePhotoPath"
            :alt="detailInfo.examStudentName"
          />
        </div>
        <p class="communication-wait-username">
          {{ detailInfo.examStudentName }}
        </p>
        <div class="communication-wait-action">
          <el-button round type="danger" @click="hangup">取消通话</el-button>
        </div>
      </div>
      <span slot="footer" class="dialog-footer"> </span>
    </el-dialog>
  </div>
</template>

<script>
import { createClient, createStream } from "@/plugins/trtc";
import {
  invigilateDetail,
  invigilateFinish,
  warningStudentDetail,
  getUserMonitorKey,
} from "@/api/invigilation";
import FlvMedia from "../common/FlvMedia";
import StudentBreachDialog from "./StudentBreachDialog";
import WarningTextMessageDialog from "./WarningTextMessageDialog";
import AudioRecordDialog from "./audioRecord/AudioRecordDialog";
import SecondTimer from "../common/SecondTimer";
import { formatDate, timeNumberToText, objTypeOf } from "@/utils/utils";
import { mapState } from "vuex";

const domEmpty = (dom) => {
  dom.childNodes.forEach((childNode) => {
    dom.removeChild(childNode);
  });
};

export default {
  name: "warning-detail",
  components: {
    FlvMedia,
    StudentBreachDialog,
    WarningTextMessageDialog,
    AudioRecordDialog,
    SecondTimer,
  },
  data() {
    return {
      recordId: this.$route.params.recordId,
      detailInfo: {},
      curDetail: {},
      serialIds: [],
      exceptionSummary: [],
      firstViewVideo: {
        liveUrl: "",
      },
      secondViewVideo: {
        liveUrl: "",
      },
      firstViewVideoReady: false,
      secondViewVideoReady: false,
      holding: false,
      // communication
      popoverVisible: false,
      userMonitor: {},
      client: null,
      localStream: null,
      dialogVisible: false,
      isWaiting: true,
      subscribeSetT: null,
      loopRunning: false,
      loopSetTs: [],
      isHandup: false,
    };
  },
  computed: {
    ...mapState("invigilation", ["detailIds", "liveDomains"]),
    isBreach() {
      return (
        !this.detailInfo.breachStatus && this.detailInfo.breachStatus !== null
      );
    },
  },
  watch: {
    $route: {
      handler() {
        this.initData();
      },
    },
  },
  mounted() {
    this.initData();
  },
  methods: {
    async initData() {
      this.recordId = this.$route.params.recordId;
      await this.getInvigilateDetail().catch(() => {});
      await this.getStudentVideo().catch(() => {});
      this.holding = false;

      // 学生正在考试，开启定时更新
      if (this.detailInfo.statusCode === "ANSWERING") {
        this.loopRunning = true;
        this.clearLoopSetTs();
        this.loopSetTs.push(
          setTimeout(() => {
            this.timerUpdatePage();
          }, 10 * 1000)
        );
      } else {
        this.loopRunning = false;
        this.clearLoopSetTs();
      }
    },
    clearLoopSetTs() {
      if (!this.loopSetTs.length) return;
      this.loopSetTs.forEach((sett) => {
        clearTimeout(sett);
      });
      this.loopSetTs = [];
    },
    async timerUpdatePage() {
      this.clearLoopSetTs();
      if (!this.loopRunning) return;

      await this.getInvigilateDetail().catch(() => {});

      this.loopSetTs.push(
        setTimeout(() => {
          this.timerUpdatePage();
        }, 10 * 1000)
      );
    },
    async getStudentVideo() {
      const res = await warningStudentDetail({ recordId: this.recordId });
      const records = res.data.data.map((item, index) => {
        const domain = this.liveDomains[index] || "";
        item.liveUrl = item.liveUrl
          ? `${domain}/live/${item.liveUrl.toLowerCase()}.flv`
          : "";
        item.name = item.source;
        return item;
      });
      const orderSources = {
        CLIENT_CAMERA: 1,
        MOBILE_FIRST: 2,
        CLIENT_SCREEN: 3,
        MOBILE_SECOND: 4,
      };

      records.sort((a, b) => {
        return orderSources[a.source] - orderSources[b.source];
      });

      this.firstViewVideo = records[0] || {};
      this.secondViewVideo = records[1] || {};

      if (records.length) this.initSubscribeVideo();
    },
    async getInvigilateDetail() {
      const res = await invigilateDetail(this.recordId);
      this.detailInfo = res.data.data;
      this.detailInfo.examStudentLogList = this.parseStudentLogs(
        this.detailInfo.examStudentLogList
      );
      this.exceptionSummary = this.detailInfo.examStudentLogList
        .filter((item) => item.viewType === "warning")
        .slice(0, 3);
    },
    parseStudentLogs(examStudentLogList) {
      const statusTypes = {
        common: [
          "FIRST_START",
          "RESUME_START",
          "IN_PROCESS",
          "PREPARE",
          "ANSWERING",
          "BREAK_OFF",
          "RESUME_PREPARE",
          "FINISHED",
          "FIRST_PREPARE",
        ],
        warning: [
          "FACE_COUNT_ERROR",
          "FACE_COMPARE_ERROR",
          "EYE_CLOSE_ERROR",
          "LIVENESS_ACTION_ERROR",
          "REALNESS",
          "NONE",
        ],
        exception: [
          "NET_TIME_OUT",
          "MACHING_STOP",
          "NET_TIME_BREAK",
          "SOFTWARE_STOP",
          "POWER_CUT",
        ],
      };
      const transformInfo = {
        FIRST_START: "身份识别",
        ANSWERING: "进入考试",
        RESUME_START: "身份识别",
      };
      let statusTypeMap = {};
      Object.keys(statusTypes).map((k) => {
        statusTypes[k].map((item) => {
          statusTypeMap[item] = k;
        });
      });

      const logs = examStudentLogList.map((item) => {
        let info = { ...item };
        info.endTime = formatDate("HH:mm:ss", new Date(info.createTime));
        info.viewType = statusTypeMap[info.type] || "common";
        const content = info.info.split(/【|】/);
        if (content.length === 3) {
          info.title = content[1];
          info.desc = content[2];
        } else {
          info.title = transformInfo[info.type] || content[0];
        }
        if (info.remark && info.remark.includes('{"')) {
          info.remark = JSON.parse(info.remark);
          if (info.remark["MIN_CREATE_TIME"]) {
            info.startTime = formatDate(
              "HH:mm:ss",
              new Date(info.remark["MIN_CREATE_TIME"])
            );
            info.durationTime = timeNumberToText(
              info.createTime - info.remark["MIN_CREATE_TIME"]
            );
          }

          let photos = [];
          Object.keys(info.remark).map((key) => {
            if (key.includes("PHOTO")) {
              const kPhotos =
                objTypeOf(info.remark[key]) === "array"
                  ? info.remark[key]
                  : [info.remark[key]];
              photos = [...photos, ...kPhotos];
            }
          });
          info.photos = photos;
        } else if (info.updateTime) {
          info.startTime = formatDate("HH:mm:ss", new Date(info.createTime));
          info.endTime = formatDate("HH:mm:ss", new Date(info.updateTime));
          info.durationTime = timeNumberToText(
            info.updateTime - info.createTime
          );
        }
        return info;
      });
      return logs;
    },
    changeStudent(type) {
      let index = this.detailIds.indexOf(this.recordId);
      if (type) {
        if (index >= this.detailIds.length - 1) {
          this.$message.error("当前没有下一个学生了");
          return;
        }
        index++;
      } else {
        if (index <= 0) {
          this.$message.error("当前没有上一个学生了");
          return;
        }
        index--;
      }

      if (this.holding) return;
      this.holding = true;

      this.closeSubscribeVideo();
      console.log(this.detailIds[index]);

      this.$router.replace({
        name: "WarningDetail",
        params: {
          recordId: this.detailIds[index],
        },
      });
    },
    toBreach() {
      this.curDetail = {
        examStudentName: this.detailInfo.examStudentName,
        identity: this.detailInfo.identity,
        courseNameCode: this.detailInfo.courseNameCode,
        description: "",
        examRecordId: [this.detailInfo.examRecordId],
        breachStatus: this.detailInfo.breachStatus,
        status: this.detailInfo.breachStatus ? 0 : 1,
        // 状态,0:新建,1:撤销
        // 违纪状态：正常(1)=>新建，违纪(0)=>撤销
        type: "",
      };
      this.$refs.StudentBreachDialog.open();
    },
    async toFinish() {
      const result = await this.$confirm(
        "试卷若被强制回收，考试再无法重置继续完成考试，请您慎重选择！您确定要强制回收改考试试卷吗？",
        "强制收卷确认提醒",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          iconClass: "el-icon-warning",
          customClass: "el-message-box__error",
        }
      ).catch(() => {});

      if (!result) return;

      await invigilateFinish({
        examRecordId: [this.detailInfo.examRecordId],
        type: "INTERRUPT",
      });
      this.$message.success("强制收卷成功！");
      this.goBack();
    },
    toSendTextMsg() {
      this.$refs.WarningTextMessageDialog.open();
    },
    toSendAudioMsg() {
      this.$refs.AudioRecordDialog.open();
    },
    breachFinish() {
      this.getInvigilateDetail();
    },
    // video relative
    initSubscribeVideo() {
      if (this.firstViewVideo.liveUrl) this.firstViewVideoReady = true;
      if (this.secondViewVideo.liveUrl) this.secondViewVideoReady = true;
    },
    closeSubscribeVideo() {
      this.firstViewVideoReady = false;
      this.secondViewVideoReady = false;
    },
    notifyError(content) {
      this.$notify({
        type: "error",
        message: content,
      });
    },
    async initClient(examRecordId) {
      const res = await getUserMonitorKey(examRecordId);
      this.userMonitor = res.data.data;
      this.client = createClient({
        mode: "live",
        sdkAppId: this.userMonitor.appId,
        userId: this.userMonitor.monitorUserId,
        userSig: this.userMonitor.monitorUserSig,
      });
    },
    async getLocalMedia(isVideo) {
      const localStream = createStream({
        userId: this.userMonitor.monitorUserId,
        audio: true,
        video: isVideo,
      });
      const errorTips = {
        NotFoundError: "找不到硬件设备，请确保硬件设备正常。",
        NotAllowedError: "不授权摄像头/麦克风访问无法进行音视频通话。",
        NotReadableError:
          "暂时无法访问摄像头/麦克风，请确保当前没有其他应用请求访问摄像头/麦克风，并重试。",
        OverConstrainedError: "设备异常",
        AbortError: "设备异常",
      };

      let initLocalStreamResult = true;
      await localStream.initialize().catch((error) => {
        console.log(errorTips[error.name]);
        this.notifyError(errorTips[error.name] || "未知错误");
        initLocalStreamResult = false;
        localStream.close();
      });
      return initLocalStreamResult && localStream;
    },
    async answer(isVideo) {
      // 客户端两路视频公用一个userId：
      // main：有音频，有视频
      // auxiliary：无音频，有视频
      // 手机端userId各不同
      if (this.holding) return;
      this.holding = true;
      this.closeSubscribeVideo();

      await this.initClient(this.recordId).catch(() => {});
      if (!this.client) {
        this.holding = false;
        return;
      }
      this.localStream = await this.getLocalMedia(isVideo);
      if (!this.localStream) {
        this.holding = false;
        return;
      }

      this.dialogVisible = true;
      this.popoverVisible = false;
      this.holding = false;
      // 添加远程用户视频发布监听
      this.client.on("stream-added", (event) => {
        console.log(event);
        console.log(event.stream.getUserId(), this.userMonitor.sourceUserId);
        const remoteStream = event.stream;
        if (remoteStream.getUserId() !== this.userMonitor.sourceUserId) return;
        if (remoteStream.getType() !== "main") return;
        console.log(`有效视频${remoteStream.getUserId()}，准备订阅`);

        // 延迟订阅视频
        this.subscribeSetT = setTimeout(() => {
          this.client
            .subscribe(remoteStream, { audio: true, video: true })
            .catch((error) => {
              console.log(`${remoteStream.getUserId()}视频订阅失败！`, error);
              this.notifyError("学生视频获取失败！");
            });
        }, 5000);
      });
      this.client.on("stream-subscribed", (event) => {
        const remoteStream = event.stream;
        console.log(event);
        console.log(`${remoteStream.getUserId()}视频已订阅！`);
        this.isWaiting = false;
        this.$nextTick(() => {
          if (!this.$refs.SecondTimer.recoding) this.$refs.SecondTimer.start();
          domEmpty(document.getElementById("communication-host"));
          remoteStream.play("communication-host", { objectFit: "contain" });
        });
      });
      this.client.on("stream-removed", (event) => {
        const remoteStream = event.stream;
        if (
          remoteStream.getUserId() !== this.userMonitor.sourceUserId ||
          remoteStream.getType() !== "main" ||
          this.isHandup
        )
          return;

        console.log(event);
        console.log(`${remoteStream.getUserId()}已退出房间！`);
        this.notifyError("对方已挂断！");
        this.hangup();
      });

      // 加入房间
      let roomJoinResult = true;
      await this.client
        .join({
          roomId: this.userMonitor.monitorKey,
          role: "audience",
        })
        .catch((error) => {
          roomJoinResult = false;
          console.log("加入房间失败！", error);
          this.notifyError("发起通信失败！");
        });
      if (!roomJoinResult) return;
      console.log("加入房间成功！");

      // 切换角色，连麦互动
      let switchResult = true;
      await this.client.switchRole("anchor").catch((error) => {
        console.log("切换角色失败！", error);
        this.notifyError("角色错误！");
        switchResult = false;
      });
      if (!switchResult) return;

      // 发布本地视频
      let publishStreamResult = true;
      this.client.publish(this.localStream).catch((error) => {
        console.log("发布本地视频失败！", error);
        this.notifyError("本地音视频推送失败！");
        publishStreamResult = false;
      });
      if (!publishStreamResult) return;
      console.log("发布本地音视频成功！");

      // 播放本地视频
      this.localStream.play("communication-guest", { muted: true });
      this.isHandup = false;
    },
    async hangup() {
      if (this.isHandup) return;
      this.isHandup = true;
      if (this.subscribeSetT) clearTimeout(this.subscribeSetT);

      this.$refs.SecondTimer.end();

      // 取消发布本地视频
      await this.client.unpublish(this.localStream).catch((error) => {
        console.log("取消发布本地视频失败！", error);
      });

      this.localStream.close();
      this.localStream = null;

      // 离开房间
      let result = true;
      await this.client.leave().catch((error) => {
        console.log("离开房间失败！", error);
        this.notifyError("操作异常，请重新尝试！");
        result = false;
      });
      if (!result) return;

      this.client.off("*");
      this.client = null;
      this.userMonitor = {};

      this.dialogVisible = false;
      this.isWaiting = true;
      this.initSubscribeVideo();
    },
    goBack() {
      window.history.go(-1);
    },
  },
  beforeDestroy() {
    this.loopRunning = false;
    this.clearLoopSetTs();
    if (this.subscribeSetT) clearTimeout(this.subscribeSetT);
    if (this.client) {
      this.client.leave();
      this.client.off("*");
    }
  },
};
</script>
