<template>
  <div v-if="exam && examQuestion()" :key="exam.id" class="container">
    <div class="header">
      <RemainTime></RemainTime>
      <div style="display: flex; flex-direction: column">
        <div style="margin-bottom: 12px">{{ courseName }}</div>
        <OverallProgress
          :exam-question-list="examQuestionList"
        ></OverallProgress>
      </div>
      <div>
        {{ $store.state.user.displayName }} -&nbsp;
        {{ $store.state.user.studentCodeList.join(",") }}
      </div>
      <QuestionFilters :exam-question-list="examQuestionList"></QuestionFilters>
      <i-button class="qm-primary-button" @click="submitPaper">交卷</i-button>
    </div>
    <div id="examing-home-question" class="main">
      <QuestionView :exam-question="examQuestion()"></QuestionView>
      <ArrowNavView
        :previous-question-order="previousQuestionOrder"
        :next-question-order="nextQuestionOrder"
      ></ArrowNavView>
    </div>
    <div :class="['side']">
      <div :class="['question-nav']">
        <QuestionNavView :paper-struct="paperStruct" />
      </div>
      <div
        v-if="faceEnable && startVideoAfterDelay"
        class="camera"
        :style="{ display: showFaceMotion ? 'none' : 'block' }"
      >
        <FaceRecognition
          v-if="faceEnable"
          width="400"
          height="300"
          :show-recognize-button="false"
        />
      </div>
      <div v-if="faceEnable && !startVideoAfterDelay" class="camera">
        <div
          style="
            width: 400px;
            height: 300px;
            border: 1px solid lightgrey;
            display: flex;
            align-items: center;
            justify-content: center;
          "
        >
          <div>正在打开摄像头...</div>
        </div>
      </div>
    </div>
    <Modal
      v-model="showFaceId"
      :mask-closable="false"
      :closable="false"
      width="800"
      :styles="{ top: '10px' }"
    >
      <FaceId v-if="showFaceId" @close-faceid="closeFaceId" />
      <p slot="footer"></p>
    </Modal>
    <Modal
      v-model="showFaceMotion"
      :mask-closable="false"
      :closable="false"
      width="800"
      :styles="{ top: '10px' }"
    >
      <FaceMotion v-if="showFaceMotion" @close-face-motion="closeFaceMotion" />
      <p slot="footer"></p>
    </Modal>
    <FaceTracking v-if="faceEnable && startVideoAfterDelay && PRODUCTION" />
    <div
      v-if="disableExamingBecauseRemoteApp"
      style="
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        background-color: rgba(77, 77, 77, 0.75);
        z-index: 100;
        position: absolute;
      "
    >
      <h3 style="margin-top: 80px">请关闭远程桌面软件再进行考试！</h3>
      <i-button @click="checkRemoteAppClicked">确认已关闭远程桌面软件</i-button>
    </div>
  </div>
  <div v-else>
    正在等待数据返回...
    <i-button v-if="timeouted" class="qm-primary-button" @click="reloadPage">
      重试
    </i-button>
  </div>
</template>

<script>
import RemainTime from "./RemainTime.vue";
import OverallProgress from "./OverallProgress.vue";
import QuestionFilters from "./QuestionFilters.vue";
import QuestionView from "./QuestionView.vue";
import ArrowNavView from "./ArrowNavView.vue";
import QuestionNavView from "./QuestionNavView.vue";
import FaceTracking from "./FaceTracking.vue";
import FaceId from "./FaceId.vue";
import FaceMotion from "./FaceMotion/FaceMotion";
import FaceRecognition from "../../../components/FaceRecognition/FaceRecognition";
import { openWS, closeWsWithoutReconnect } from "./ws.js";
import { createNamespacedHelpers, mapState as globalMapState } from "vuex";
const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
import nativeExe, { fileExists } from "@/utils/nativeExe";
import { REMOTE_APP_NAME } from "@/constants/constant-namelist";
import { STRICT_CHECK_HOSTS } from "@/constants/constants";
import { checkMainExe } from "@/utils/util";

export default {
  name: "ExamingHome",
  components: {
    RemainTime,
    OverallProgress,
    QuestionFilters,
    QuestionView,
    ArrowNavView,
    QuestionNavView,
    FaceRecognition,
    FaceId,
    FaceMotion,
    FaceTracking,
  },
  beforeRouteUpdate(from, to, next) {
    window._hmt.push(["_trackEvent", "答题页面", "题目切换"]);
    if (process.env.NODE_ENV === "development") {
      console.log("beforeRouteUpdate from: " + this.$route.fullPath);
    }
    this.answerAllQuestions();
    next();
  },
  data() {
    return {
      showFaceId: false,
      showFaceMotion: false,
      faceEnable: false,
      timeouted: false,
      startVideoAfterDelay: false,
      PRODUCTION: process.env.NODE_ENV === "production",
      disableExamingBecauseRemoteApp: false,
      courseName: "",
    };
  },
  computed: {
    ...globalMapState(["QECSConfig"]),
    ...mapState([
      "exam",
      "paperStruct",
      "examQuestionList",
      "snapNow",
      "snapProcessingCount",
      "shouldSubmitPaper",
      "remainTime",
      "questionAnswerFileUrl",
      "uploadModalVisible",
      "exceedSwitchCount",
    ]),
    previousQuestionOrder: (vm) => {
      if (vm.examQuestion().order > 1) {
        return vm.examQuestion().order - 1;
      } else {
        return null;
      }
    },
    nextQuestionOrder: (vm) => {
      if (vm.examQuestion().order < vm.examQuestionList.length) {
        return vm.examQuestion().order + 1;
      } else {
        return null;
      }
    },
  },
  watch: {
    $route: function () {
      this.examQuestion();
    },
    shouldSubmitPaper() {
      this.logger({ action: "时间到自动交卷" });
      this.realSubmitPaper();
    },
    questionAnswerFileUrl(value) {
      // console.log(this.examQuestion.studentAnswer);
      // console.log("watch", value);
      const examRecordDataId = this.$route.params.examRecordDataId;
      const that = this;
      for (const q of value) {
        if (!q.saved) {
          let acknowledgeStatus = "CONFIRMED";

          // 目前只针对音频题有丢弃的可能
          if (
            q.transferFileType === "PIC" &&
            (q.order != this.$route.params.order || !this.uploadModalVisible)
          ) {
            acknowledgeStatus = "DISCARDED";
          }
          this.$http
            .post(
              "/api/ecs_oe_student/examControl/saveUploadedFileAcknowledgeStatus",
              {
                examRecordDataId,
                filePath: q.fileUrl,
                order: q.order,
                acknowledgeStatus,
              }
            )
            .then(() => {
              if (q.transferFileType === "AUDIO") {
                that.updateExamQuestion({
                  order: q.order,
                  studentAnswer: q.fileUrl,
                });
              } else if (
                acknowledgeStatus === "CONFIRMED" &&
                q.transferFileType === "PIC"
              ) {
                that.updatePicture(q);
              }
              q.saved = true;
              if (acknowledgeStatus === "CONFIRMED")
                this.$Message.info({
                  content: "小程序作答已更新",
                  duration: 5,
                  closable: true,
                });
            })
            .catch(() => {
              this.$Message.error({
                content: "更新小程序答案失败！",
                duration: 15,
                closable: true,
              });
            });
        }
      }
    },
    exceedSwitchCount(val) {
      if (val) {
        this.logger({ action: "切屏超出次数自动交卷" });
        this.realSubmitPaper();
      }
    },
    // examQuestionList(val, oldVal) {
    //   // console.log(val, oldVal);
    // }
    remainTime(val) {
      if (val === 5 * 60 * 1000) {
        this.reaminModalCreated = true;
        this.$Modal.info({
          render: () => (
            <div>
              <h3>温馨提醒</h3>
              <div style="margin-top: 20px; margin-left: 20px; flex: 1">
                <div style="margin-bottom: 1.5em">
                  还有<span style="font-weight: bold; color: red;"> 五 </span>
                  分钟即将结束本场考试，请合理分配时间！
                </div>
              </div>
            </div>
          ),
          onOk: () => {
            this.reaminModalClosed = true;
          },
        });
      } else if (val === 5 * 60 * 1000 - 10 * 1000) {
        if (this.reaminModalCreated && !this.reaminModalClosed) {
          this.$Modal.remove();
        }
      }
    },
  },
  async created() {
    this.timeoutTimeout = setTimeout(() => (this.timeouted = true), 30 * 1000);
    this.logger({
      action: "答题页面",
      detail: "进入答题页面-created",
      examRecordDataId: this.$route.params.examRecordDataId,
    });

    try {
      await this.initData();
    } catch (error) {
      this.logger({
        action: "答题页面",
        detail: "获取考试和试卷信息失败，退出登录",
      });
      this.$Message.error({
        content: "获取考试和试卷信息失败，退出登录",
        duration: 15,
        closable: true,
      });
      this.logout("?LogoutReason=获取考试和试卷信息失败");
      return;
    }

    this.submitInterval = setInterval(
      () => this.answerAllQuestions(),
      5 * 1000 // 10秒检查是否有更改需要提交答案
    );

    this.checkRemoteAppInterval = setInterval(
      () => this.checkRemoteApp(),
      3 * 60 * 1000 // 10秒检查是否有更改需要提交答案
    );

    console.log("考试开始 ", this.$route.params.examRecordDataId);
  },
  async mounted() {
    // iview bug: https://github.com/iview/iview/issues/4061
    // document.body.style = "";
    // 避免macos上下塘动。避免产生滚动条。
    document.body.classList.toggle("hide-body-scroll", true);

    // NotAllowedError: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD
    this.startVideoAfterDelayTimeout = setTimeout(() => {
      this.startVideoAfterDelay = true;
    }, 10 * 1000);

    window._hmt.push(["_trackEvent", "答题页面", "进入页面"]);

    if (typeof nodeRequire != "undefined") {
      try {
        var fs = window.nodeRequire("fs");
        if (fs.existsSync("multiCamera.exe")) {
          await new Promise((resolve, reject) => {
            window.nodeRequire("node-cmd").get("multiCamera.exe", () => {
              try {
                let cameraInfos = fs.readFileSync("CameraInfo.txt", "utf-8");
                if (cameraInfos && cameraInfos.trim()) {
                  cameraInfos = cameraInfos.trim();
                  cameraInfos = cameraInfos.replace(/\r\n/g, "");
                  cameraInfos = cameraInfos.replace(/\n/g, "");
                  console.log(cameraInfos);
                }
                resolve();
              } catch (error) {
                reject("读取摄像头列表失败");
              }
            });
          });
        }
      } catch (error) {
        console.log(error);
      }
    }
  },
  beforeDestroy() {
    clearTimeout(this.timeoutTimeout);
    clearInterval(this.submitInterval);
    clearInterval(this.initSnapInterval);
    clearInterval(this.snapInterval);
    clearTimeout(this.faceIdMsgTimeout);
    clearTimeout(this.checkMainTimeout);
    clearTimeout(this.faceIdDivTimeout);
    clearTimeout(this.startVideoAfterDelayTimeout);
    clearInterval(this.checkRemoteAppInterval);
    closeWsWithoutReconnect();
    this.updateExamState({
      exam: null,
      paperStruct: null,
      examQuestionList: null,
      questionAnswerFileUrl: [],
      pictureAnswer: {},
      snapNow: false,
      snapProcessingCount: 0,
      exceedSwitchCount: false,
    });
    // TODO: 是否是个错误点？this.$Modal 不存在？
    this.$Modal.remove();
    // 避免macos上下塘动。避免产生滚动条。
    document.body.classList.toggle("hide-body-scroll", false);
  },
  // beforeRouteUpdate(to, from, next) {
  //   this.updateQuestion(next);
  // },
  methods: {
    ...mapMutations([
      "updateExamState",
      "updateExamQuestion",
      "toggleSnapNow",
      "updateExamResult",
      "resetExamQuestionDirty",
      "updatePicture",
    ]),
    async initData() {
      this.logger({
        action: "答题页面",
        detail: "before initData",
      });
      const [
        { data: weixinAnswerEnabled },
        { data: faceCheckEnabled },
        { data: faceLivenessEnabled },
        { data: examProp },
        { data: exam },
        { data: paperStruct },
        { data: examQuestionListOrig },
        { data: courseName },
      ] = await Promise.all([
        this.$http.get(
          "/api/ecs_exam_work/exam/weixinAnswerEnabled/" +
            this.$route.params.examId
        ),
        this.$http.get(
          "/api/ecs_exam_work/exam/faceCheckEnabled/" +
            this.$route.params.examId
        ),
        this.$http.get(
          "/api/ecs_exam_work/exam/identificationOfLivingEnabled/" +
            this.$route.params.examId
        ),
        this.$http.get(
          "/api/ecs_exam_work/exam/getExamPropertyFromCacheByStudentSession/" +
            this.$route.params.examId +
            `/SNAPSHOT_INTERVAL,PRACTICE_TYPE,FREEZE_TIME`
        ),
        this.$http.get("/api/ecs_exam_work/exam/" + this.$route.params.examId),
        this.$http.get(
          "/api/ecs_oe_student/examRecordPaperStruct/getExamRecordPaperStruct?examRecordDataId=" +
            this.$route.params.examRecordDataId
        ),
        this.$http.get("/api/ecs_oe_student/examQuestion/findExamQuestionList"),
        this.$http.get(
          "/api/ecs_oe_student/examControl/courseName/" +
            this.$route.params.examRecordDataId
        ),
      ]);
      this.courseName = courseName;

      let initFaceLivenessResult = null;
      if (faceLivenessEnabled) {
        initFaceLivenessResult = await this.initFaceLiveness();
      }

      let examQuestionList = examQuestionListOrig;

      if (
        weixinAnswerEnabled === undefined ||
        faceCheckEnabled === undefined ||
        faceLivenessEnabled === undefined ||
        examProp === undefined ||
        exam === undefined ||
        paperStruct === undefined ||
        examQuestionList === undefined ||
        (faceLivenessEnabled && initFaceLivenessResult === false)
      ) {
        console.log({
          weixinAnswerEnabled,
          faceCheckEnabled,
          faceLivenessEnabled,
          examProp,
          exam,
          paperStruct,
          examQuestionList,
          initFaceLivenessResult,
        });
        throw new Error("获取考试和试卷信息失败");
      }

      this.logger({
        action: "答题页面",
        detail: `end${
          typeof Object.fromEntries === "function" ? " " : "　"
        }initData`,
      });
      this.logger({
        action: "答题页面dimension",
        scrollX: window.scrollX,
        scrollY: window.scrollY,
        width: window.screen.width,
        height: window.screen.height,
        screenX: window.screen.availWidth,
        screenY: window.screen.availHeight,
        clientWidth: document.documentElement.clientWidth,
        clientHeight: document.documentElement.clientHeight,
        windowInnerWidth: window.innerWidth,
        windowInnerHeight: window.innerHeight,
        windowOuterWidth: window.outerWidth,
        windowOuterHeight: window.outerHeight,
        // 是否全屏
        equal1:
          "dimesion1" +
          (window.screen.width === window.outerWidth &&
            window.screen.height === window.outerHeight),
        // 是否打开了调试窗口
        equal2:
          "dimesion2" +
          (window.innerWidth === window.outerWidth &&
            window.innerHeight === window.outerHeight),
      });
      this.checkMainTimeout = setTimeout(() => {
        if (STRICT_CHECK_HOSTS.includes(window.location.hostname)) {
          if (!checkMainExe()) {
            this.$http.post(
              "/api/ecs_oe_student/client/exam/process/discipline"
            );
            this.logger({
              action: "答题页面discipline",
            });
          }
        }
      }, 60 * 1000);

      exam.WEIXIN_ANSWER_ENABLED = weixinAnswerEnabled;

      if (faceCheckEnabled) {
        this.faceEnable = true;

        let initSnapshotTrialTimes = 0;
        this.initSnapInterval = setInterval(() => {
          const video = document.getElementById("video");
          const videoStartFailed =
            !video || video.readyState !== 4 || !video.srcObject.active;
          if (videoStartFailed && initSnapshotTrialTimes < 5) {
            initSnapshotTrialTimes++;
            this.logger({
              action: "答题页面",
              detail:
                "进入考试后60秒内抓拍-" + `(第${initSnapshotTrialTimes}次尝试)`,
            });
          } else {
            // 超过6次后，强行抓拍，如果抓拍不成功，则会因抓拍不成功而退出。
            clearInterval(this.initSnapInterval);

            if (videoStartFailed) {
              this.logger({
                action: "答题页面",
                detail: "摄像头没有正常启用-进入考试抓拍",
              });
              this.$Message.error({
                content: "摄像头没有正常启用",
                duration: 5,
                closable: true,
              });
              window._hmt.push([
                "_trackEvent",
                "摄像头框",
                "摄像头状态",
                "摄像头没有正常启用-进入考试抓拍",
              ]);

              this.logout("?LogoutReason=" + "摄像头没有正常启用-退出");
            } else {
              this.logger({
                action: "答题页面",
                detail:
                  "进入考试后60秒内抓拍-" +
                  `(第${initSnapshotTrialTimes + 1}次尝试成功)`,
              });
              this.toggleSnapNow(); // 开启抓拍才在进入考试时抓拍一张
            }
          }
        }, 10 * 1000);

        //       let initSnapshotTrialTimes = 0;
        // const initSnapshot = setTimeout(() => {
        //   if (this.exam || initSnapshotTrialTimes < 6) {
        //     this.toggleSnapNow(); // 开启抓拍才在进入考试时抓拍一张
        //   } else {
        //     setTimeout(() => initSnapshot(), 5 * 1000);
        //   }
        // }, 5 * 1000);

        if (examProp.SNAPSHOT_INTERVAL) {
          const SNAPSHOT_INTERVAL = JSON.parse(examProp.SNAPSHOT_INTERVAL);
          // 考务设置抓拍间隔
          this.snapInterval = setInterval(() => {
            this.logger({
              action: "答题页面",
              detail: "定时抓拍",
              SNAPSHOT_INTERVAL: examProp.SNAPSHOT_INTERVAL,
            });
            this.toggleSnapNow();
          }, SNAPSHOT_INTERVAL * 60 * 1000);
        }
      }

      if (exam.examType === "PRACTICE") {
        this.practiceType = examProp.PRACTICE_TYPE; // IN_PRACTICE NO_ANSWER
        exam.practiceType = examProp.PRACTICE_TYPE;
      }

      exam.freezeTime =
        examProp.FREEZE_TIME && JSON.parse(examProp.FREEZE_TIME);
      this.logger({
        page: "答题页面",
        examRecordDataId: this.$route.params.examRecordDataId,
        faceCheckEnabled: faceCheckEnabled,
        faceLivenessEnabled: faceLivenessEnabled,
        WEIXIN_ANSWER_ENABLED: exam.WEIXIN_ANSWER_ENABLED,
        SNAPSHOT_INTERVAL: examProp.SNAPSHOT_INTERVAL,
        PRACTICE_TYPE: examProp.PRACTICE_TYPE,
        FREEZE_TIME: examProp.FREEZE_TIME,
      });

      // parentQuestionBody
      //     questionUnitWrapperList
      //         questionBody  => from examQuestionList
      //         questionUnitList =>
      //         studentAnswer
      //         rightAnswer

      // init subNumber
      let questionId = null;
      let i = 1;

      examQuestionList = examQuestionList.map((eq) => {
        if (questionId == eq.questionId) {
          eq.subNumber = i++;
        } else {
          i = 1;
          questionId = eq.questionId;
          eq.subNumber = i++;
        }
        return eq;
      });

      let groupOrder = 1;
      let mainNumber = 0;
      examQuestionList = examQuestionList.map((eq) => {
        if (mainNumber == eq.mainNumber) {
          eq.groupOrder = groupOrder++;
        } else {
          mainNumber = eq.mainNumber;
          groupOrder = 1;
          eq.groupOrder = groupOrder++;
        }

        const questionWrapperList =
          paperStruct.defaultPaper.questionGroupList[eq.mainNumber - 1]
            .questionWrapperList;
        const groupName =
          paperStruct.defaultPaper.questionGroupList[eq.mainNumber - 1]
            .groupName;
        const groupTotal = questionWrapperList.reduce(
          (accumulator, questionWrapper) =>
            accumulator + questionWrapper.questionUnitWrapperList.length,
          0
        );

        eq.groupName = groupName;
        eq.groupTotal = groupTotal;
        return eq;
      });

      examQuestionList = examQuestionList.map((eq) => {
        const paperStructQuestion = paperStruct.defaultPaper.questionGroupList[
          eq.mainNumber - 1
        ].questionWrapperList.find((q) => q.questionId === eq.questionId);
        return Object.assign(eq, {
          limitedPlayTimes: paperStructQuestion.limitedPlayTimes,
        });
      });

      this.updateExamState({
        exam: exam,
        paperStruct: paperStruct,
        examQuestionList: examQuestionList,
        allAudioPlayTimes: JSON.parse(examQuestionList[0].audioPlayTimes) || [],
        questionAnswerFileUrl: [],
        pictureAnswer: {},
      });
      // console.log(examQuestionList);
      // console.log(examQuestionList.find(v => v.answerType === "SINGLE_AUDIO"));

      const shouldOpenWS = exam.WEIXIN_ANSWER_ENABLED;

      if (shouldOpenWS) {
        // console.log("have single");
        const examRecordDataId = this.$route.params.examRecordDataId;
        openWS({ examRecordDataId });
      }
    },
    updateQuestion: async function (next) {
      // 初始化套题的答案，为回填部分选项做准备
      // for (let q of this.examQuestionList) {
      //   if (q.subQuestionList.length > 0) {
      //     q.studentAnswer = [];
      //     for (let sq of q.subQuestionList) {
      //       q.studentAnswer.push(sq.studentAnswer);
      //     }
      //   }
      // }

      next && next();
      if (!this.exam) return;
    },
    async initFaceLiveness() {
      let faceVerifyMinute = null;
      let identificationOfLivingBodyScheme = null;

      {
        const examRecordDataId = this.$route.params.examRecordDataId;

        for (let i = 0; i < 100; i++) {
          try {
            const faceBiopsyBaseInfoData = await this.$http.get(
              "/api/ecs_oe_student/faceBiopsy/getFaceBiopsyBaseInfo?examRecordDataId=" +
                examRecordDataId
            );
            console.log(faceBiopsyBaseInfoData.data);

            faceVerifyMinute = faceBiopsyBaseInfoData.data.faceVerifyMinute;
            identificationOfLivingBodyScheme =
              faceBiopsyBaseInfoData.data.identificationOfLivingBodyScheme;
            break;
          } catch (error) {
            console.log(error);
            if (!error.response) {
              await new Promise((resolve) => setTimeout(resolve, 1000));
              continue; // 网络不通
            } else {
              break;
            }
          }
        }
      }

      if (identificationOfLivingBodyScheme === null) {
        return false;
      }

      // 仅在线上使用活体检测
      // if (process.env.NODE_ENV === "production" && faceVerifyMinute) {
      if (faceVerifyMinute) {
        // 第二次开启活检时肯定有 this.remainTime 了。注意断点续考时没有这项检查
        this.$nextTick(async () => {
          await new Promise((r) =>
            setTimeout(() => {
              r();
            }, 10 * 1000)
          );
          console.log("活检定时");
          this.logger({ action: "活检定时", detail: faceVerifyMinute });
          const enoughTimeForFaceId = this.remainTime // 如果remainTime取到了的话
            ? this.remainTime / (60 * 1000) - 1 > faceVerifyMinute
            : true;
          if (!enoughTimeForFaceId) return;

          this.faceIdMsgTimeout = setTimeout(() => {
            this.logger({
              action: "答题页面",
              detail: "活体检测前抓拍",
            });
            this.toggleSnapNow();
            this.$Message.info({
              content: "30秒后开始指定动作检测",
              duration: 15,
              closable: true,
            });
          }, faceVerifyMinute * 60 * 1000 - 30 * 1000); // 活体检测提醒
          this.faceIdDivTimeout = setTimeout(() => {
            if (identificationOfLivingBodyScheme === "S1") {
              this.showFaceId = true;
            } else if (identificationOfLivingBodyScheme === "S2") {
              this.showFaceMotion = true;
            }
          }, faceVerifyMinute * 60 * 1000); // 定时做活体检测
          // }, 1 * 1000); // 定时做活体检测
        });
      }

      // for test
      // setTimeout(() => {
      //   this.showFaceId = true;
      //   // this.$Modal.remove();
      //   // }, this.$route.query.faceVerifyMinute * 60 * 1000); // 定时做活体检测
      // }, 5 * 1000); // 定时做活体检测

      return true;
    },
    closeFaceId() {
      this.showFaceId = false;
    },
    async closeFaceMotion(faceLiveResult) {
      this.showFaceMotion = false;
      console.log(faceLiveResult);
      if (faceLiveResult.endExam) {
        this.logout("?LogoutReason=活检后台交卷");
      } else if (faceLiveResult.needNextVerify) {
        const initFaceLivenessResult = await this.initFaceLiveness();
        if (initFaceLivenessResult === false) {
          this.logger({
            action: "答题页面",
            detail: "活检接口再次获取失败-退出",
          });
          window._hmt.push([
            "_trackEvent",
            "答题页面",
            "活检接口再次获取失败-退出",
          ]);
          this.$Message.error({
            content: "获取考试和试卷信息失败，退出登录",
            duration: 15,
            closable: true,
          });
          this.logout("?LogoutReason=活检接口再次获取失败-退出");
        }
      }
    },
    async answerAllQuestions(ignoreDirty) {
      const answers = this.examQuestionList
        .filter((eq) => (ignoreDirty ? true : eq.dirty))
        .filter((eq) => eq.getQuestionContent)
        .map((eq) => {
          return Object.assign(
            {
              order: eq.order,
              studentAnswer: eq.studentAnswer,
            },
            eq.audioPlayTimes && { audioPlayTimes: eq.audioPlayTimes },
            eq.isSign && { isSign: eq.isSign }
          );
        });
      if (answers.length > 0) {
        try {
          await this.$http.post(
            "/api/ecs_oe_student/examQuestion/submitQuestionAnswer",
            answers
          );
          this.resetExamQuestionDirty();
          // 提交成功，返回true，供最后提交时判断。自动提交失败，不暂停。
          return true;
        } catch (error) {
          console.log(error);
          this.logger({
            action: "提交答案失败",
            errorJSON: JSON.stringify(error, (key, value) =>
              key === "token" ? "" : value
            ),
            errorName: error.name,
            errorMessage: error.message,
            errorStack: error.stack,
          });
          this.$Message.error({
            content: "提交答案失败",
            duration: 15,
            closable: true,
          });
          window._hmt.push([
            "_trackEvent",
            "答题页面",
            "提交答案失败",
            error.message +
              " |||| " +
              (((error.response || {}).data || {}).desc || ""),
          ]);
        }
      }
    },
    async submitPaper() {
      this.logger({ action: "学生点击交卷" });
      try {
        // 交卷前强制提交所有答案
        const ret = await this.answerAllQuestions(true);
        if (!ret) {
          // 提交答案失败，停止交卷逻辑。
          return;
        }
      } catch (error) {
        return;
      }

      if (
        this.exam.freezeTime &&
        this.remainTime >
          (this.exam.duration - this.exam.freezeTime) * 60 * 1000
      ) {
        this.$Message.info({
          content: `考试开始${this.exam.freezeTime}分钟后才允许交卷。`,
          duration: 5,
          closable: true,
        });
        return;
      }

      const answered = this.examQuestionList.filter(
        (q) => q.studentAnswer !== null
      ).length;
      const unanswered = this.examQuestionList.filter(
        (q) => q.studentAnswer === null
      ).length;
      const signed = this.examQuestionList.filter((q) => q.isSign).length;
      const showConfirmTime = Date.now();
      this.$Modal.confirm({
        title: "确认交卷",
        content: `<p>已答题目：${answered}</p><p>未答题目：${unanswered}</p><p>标记题目：${signed}</p>`,
        onOk: () => {
          this.realSubmitPaper(showConfirmTime);
        },
      });
    },
    async realSubmitPaper(showConfirmTime = 0) {
      this.__submitPaperStartTime = Date.now();
      this.$Spin.show({
        render: () => {
          return <div style="font-size: 44px">正在交卷，请耐心等待...</div>;
        },
      });
      this.logger({ action: "正在交卷，请耐心等待..." });
      if (this.faceEnable) {
        this.logger({ action: "交卷前抓拍" });
        this.toggleSnapNow();
      }
      // 确保抓拍指令在交卷前执行，同时确保5秒间隔提交答案的指令执行了
      let delay = 5 - (Date.now() - showConfirmTime) / 1000;
      if (delay < 0) {
        // 如果用户已经看确认框超过5秒，或者不是由确认框进来的，不延迟
        delay = 0;
      }
      // 给抓拍照片多一秒处理时间
      delay = delay + 1;
      // 和下行注释的sleep语句不是一样的。sleep之后还可以执行。加上clearTimeout则可拒绝。
      // await new Promise(resolve => setTimeout(() => resolve(), delay * 1000));
      setTimeout(() => this.realSubmitPaperStep2(), delay * 1000);
      this.submitCount = 1;
    },
    async realSubmitPaperStep2() {
      if (this.snapProcessingCount > 0) {
        this.submitCount++;
        if (this.submitCount < 200) {
          // 一分钟后，强制交卷
          console.log("一分钟后，强制交卷");
          setTimeout(() => this.realSubmitPaperStep2(), 300);
          return;
        }
      }
      if (this.submitLock) {
        return;
      } else {
        this.submitLock = true;
      }

      if (this.$route.name !== "OnlineExamingHome") {
        // 非考试页，不再交卷
        this.$Spin.hide();
        return;
      }
      try {
        const examId = this.$route.params.examId;
        const examRecordDataId = this.$route.params.examRecordDataId;
        const res = await this.$http.get(
          "/api/ecs_oe_student/examControl/endExam"
        );
        if (res.status === 200) {
          this.$router.replace({
            path: `/online-exam/exam/${examId}/examRecordData/${examRecordDataId}/end`,
          });
          // 确保交卷成功后，不会再次交卷
          this.submitLock = true;
          this.$Spin.hide();
          this.__submitPaperEndTime = Date.now();
          window._hmt.push([
            "_trackEvent",
            "交卷耗时",
            Number(
              (this.__submitPaperEndTime - this.__submitPaperStartTime) / 1000
            ).toPrecision(1) + "秒",
          ]);
          this.logger({
            action: "交卷成功",
            cost: this.__submitPaperEndTime - this.__submitPaperStartTime,
            UA: navigator.userAgent,
          });
          return;
        } else {
          this.$Message.error({
            content: "交卷失败",
            duration: 15,
            closable: true,
          });
          this.logger({
            action: "交卷失败",
            detail: "endExam response status is not 200",
          });
        }
        this.submitLock = false;
      } catch (e) {
        this.$Message.error({
          content: "交卷失败",
          duration: 15,
          closable: true,
        });
        console.log(e);
        this.logger({
          action: "交卷失败",
          errorJSON: JSON.stringify(e, (key, value) =>
            key === "token" ? "" : value
          ),
          errorName: e.name,
          errorMessage: e.message,
          errorStack: e.stack,
        });
      }
      this.submitLock = false;
      this.$Spin.hide();
    },
    examQuestion() {
      return (
        this.examQuestionList &&
        this.examQuestionList.find(
          (eq) => eq.order == this.$route.params.order // number == string
        )
      );
    },
    reloadPage() {
      window._hmt.push(["_trackEvent", "答题页面", "页面加载失败", "reload"]);
      this.logger({ page: "答题页面", button: "重试按钮", action: "点击" });
      window.location.reload();
    },
    async checkRemoteAppClicked() {
      this.logger({
        page: "答题页面",
        button: "确认已关闭远程桌面软件",
        action: "点击",
      });
      this.checkRemoteApp();
    },
    async checkRemoteApp() {
      if (typeof nodeRequire == "undefined") {
        return;
      }

      async function checkRemoteAppTxt() {
        let applicationNames;
        try {
          const fs = window.nodeRequire("fs");
          try {
            applicationNames = fs.readFileSync(
              "remoteApplication.txt",
              "utf-8"
            );
          } catch (error) {
            console.log(error);
            window._hmt.push([
              "_trackEvent",
              "答题页面",
              "读取remoteApplication.txt出错--0",
            ]);
            await new Promise((resolve2) => setTimeout(() => resolve2(), 3000));
            applicationNames = fs.readFileSync(
              "remoteApplication.txt",
              "utf-8"
            );
          }
        } catch (error) {
          console.log(error);
          // this.logger({
          //   currentPage: "答题页面",
          //   errorType: "e-01",
          //   error: error.message,
          //   detail: applicationNames,
          // });
          window._hmt.push([
            "_trackEvent",
            "答题页面",
            "读取remoteApplication.txt出错",
          ]);
          // this.$Message.error({
          //   content: "系统检测出错(e-01)，请退出程序后重试！",
          //   duration: 2 * 24 * 60 * 60,
          // });
          return;
        }
        // 为避免考试过程中卡顿，不在考试过程中同步检测远程桌面软件
        // if (typeof nodeRequire !== "undefined") {
        //   const hasSun = nodeCheckRemoteDesktop();
        //   if (hasSun) {
        //     if (applicationNames) {
        //       applicationNames += ",sunloginclient";
        //     } else {
        //       applicationNames = "sunloginclient";
        //     }
        //   }
        // }
        if (applicationNames && applicationNames.trim()) {
          let names = applicationNames
            .replace("qq", "QQ")
            .replace("teamviewer", "TeamViewer")
            .replace("lookmypc", "LookMyPC")
            .replace("xt", "协通")
            .replace("winaw32", "Symantec PCAnywhere")
            .replace("pcaquickconnect", "Symantec PCAnywhere")
            .replace("sessioncontroller", "Symantec PCAnywhere")
            .replace(/sunloginclient/gi, "向日葵")
            .replace(/sunloginremote/gi, "向日葵")
            .replace("wemeetapp", "腾讯会议")
            .replace("wechat", "微信");

          names = [...new Set(names.split(",").map((v) => v.trim()))].join(
            "，"
          );
          this.disableExamingBecauseRemoteApp = true;
          this.$Message.info({
            content: "在考试期间，请关掉" + names + "软件，诚信考试。",
            duration: 30,
          });
        } else {
          this.disableExamingBecauseRemoteApp = false;
        }
      }
      //如果配置中配置了 DISABLE_REMOTE_ASSISTANCE
      if (
        this.QECSConfig.PREVENT_CHEATING_CONFIG.includes(
          "DISABLE_REMOTE_ASSISTANCE"
        )
      ) {
        let exe = "Project1.exe";
        if (fileExists("Project2.exe")) {
          const remoteAppName = REMOTE_APP_NAME;
          exe = `Project2.exe "${remoteAppName}" `;
        }
        const fs = window.nodeRequire("fs");
        try {
          fs.unlinkSync("remoteApplication.txt");
        } catch (error) {
          console.log(error);
        }
        await nativeExe(exe, checkRemoteAppTxt.bind(this));
      } else {
        this.disableExamingBecauseRemoteApp = false;
      }
    },
  },
};
</script>

<style scoped>
.container {
  display: grid;
  grid-template-areas:
    "header header"
    "main side";
  grid-template-rows: 80px minmax(0, 1fr);
  grid-template-columns: 1fr 400px;

  height: 100vh;
  width: 100vw;
}

.header {
  display: grid;
  align-items: center;
  justify-items: center;
  grid-template-columns: 200px 280px 1fr 300px 100px;
  grid-area: header;
  height: 80px;
  background-color: #f5f5f5;
}

.main {
  display: grid;
  grid-area: main;
  grid-template-rows: 1fr 50px;
}

.side {
  display: grid;
  grid-area: side;
  grid-template-rows: 1fr;
  background-color: #f5f5f5;
}

.question-nav {
  overflow-y: scroll;
}

.camera {
  z-index: 100;
  height: 300px;
}

@media screen and (max-height: 768px) {
  .container {
    grid-template-rows: 50px minmax(0, 1fr);
  }
  .header {
    height: 50px;
  }
}

@media screen and (max-width: 960px) {
  .header {
    overflow-x: scroll;
  }
}
</style>

<style>
#examing-home-question img {
  max-width: 100%;
  height: auto !important;
}

.hide-body-scroll {
  overflow: hidden !important;
}
</style>
