<template>
  <div>
    <video
      id="video"
      ref="video"
      :width="width"
      :height="height"
      autoplay
    ></video>
    <div
      v-if="showRecognizeButton"
      style="position: absolute; width: 400px; text-align: center; margin-top: -50px; color: #232323;"
    >
      <button
        :class="['verify-button', disableSnap && 'disable-verify-button']"
        :disabled="disableSnap"
        @click="snap"
      >
        {{ msg }}
      </button>
    </div>
  </div>
</template>

<script>
import MD5 from "js-md5";
import { mapState as globalMapState } from "vuex";
import { createNamespacedHelpers } from "vuex";
const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");

export default {
  name: "FaceRecognition",
  props: {
    width: { type: String, default: "400" },
    height: { type: String, default: "300" },
    showRecognizeButton: Boolean,
    closeCamera: Boolean, // optional
  },
  data() {
    return { disableSnap: true, msg: "开始识别" };
  },
  computed: {
    ...globalMapState(["user"]),
    ...mapState(["snapNow"]),
  },
  watch: {
    snapNow(val) {
      if (val) {
        if (!this.lastSnapTime || Date.now() - this.lastSnapTime > 60 * 1000) {
          this.lastSnapTime = Date.now();
          this.snapTimer();
        } else {
          // this.serverLog(
          //   "debug/S-002001",
          //   "上次的抓拍未超过1分钟，本次抓拍指令取消"
          // );
          window._hmt.push([
            "_trackEvent",
            "摄像头框",
            "上次的抓拍未超过1分钟，本次抓拍指令取消",
          ]);
          this.decreaseSnapCount();
        }
        this.toggleSnapNow();
      }
    },
    closeCamera: function(newValue) {
      if (newValue) {
        console.log("关闭摄像头");
        if (this.$refs.video.srcObject) {
          this.$refs.video.srcObject.getTracks().forEach(function(track) {
            track.stop();
          });
          this.$refs.video.srcObject.srcObject = null;
        }
      } else {
        this.openCamera();
      }
    },
  },
  async mounted() {
    this.openCamera();

    this.checkFaceDetectorTimeout = setTimeout(() => {
      this.nativeFaceDetectorStats();
    }, 60 * 1000);
  },
  beforeDestroy() {
    clearTimeout(this.retrySnapTimeout);
    clearTimeout(this.showSnapResultTimeout);
    clearTimeout(this.checkFaceDetectorTimeout);
    if (this.$refs.video.srcObject) {
      this.$refs.video.srcObject.getTracks().forEach(function(track) {
        track.stop();
      });
      this.$refs.video.srcObject.srcObject = null;
    }
  },
  methods: {
    ...mapMutations(["toggleSnapNow", "decreaseSnapCount"]),
    async openCamera() {
      const video = this.$refs.video;
      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        try {
          console.log("启动摄像头");
          const stream = await navigator.mediaDevices.getUserMedia({
            video: {
              facingMode: "user",
              aspectRatio: 4 / 3,
              // width: 400,
              // height: this.showRecognizeButton ? 300 : 250
            },
          });
          if (stream) {
            video.srcObject = stream;
            try {
              await video.play();
              this.disableSnap = false;
            } catch (error) {
              console.log("摄像头没有正常启用", error);
              this.$Message.error({
                content: "摄像头没有正常启用",
                duration: 15,
                closable: true,
              });
            }
          } else {
            this.$Message.error({
              content: "没有可用的视频流",
              duration: 15,
              closable: true,
            });
            window._hmt.push([
              "_trackEvent",
              "摄像头框",
              "摄像头状态",
              "没有可用的视频流",
            ]);
          }
        } catch (error) {
          console.log("无法启用摄像头", error);
          this.$Message.error({
            content: "无法启用摄像头",
            duration: 15,
            closable: true,
          });
          window._hmt.push([
            "_trackEvent",
            "摄像头框",
            "摄像头状态",
            "无法启用摄像头",
          ]);
        }
      } else {
        this.$Message.error({
          content: "没有找到可用的摄像头",
          duration: 15,
          closable: true,
        });
        window._hmt.push([
          "_trackEvent",
          "摄像头框",
          "摄像头状态",
          "没有找到可用的摄像头",
        ]);
      }
    },
    async snapTimer() {
      try {
        const examRecordDataId = this.$route.params.examRecordDataId;
        const captureBlob = await this.getSnapShot();
        this.videoStartPlay();
        console.log("抓拍照片的大小：" + captureBlob.size);
        // this.serverLog("debug/S-004001", "抓拍照片的大小：" + captureBlob.size);
        const [captureFilePath, signIdentifier] = await this.uploadToServer(
          captureBlob
        );
        await this.faceCompare(
          captureFilePath,
          signIdentifier,
          examRecordDataId
        );
      } catch (error) {
        // FIXME: more processing
        console.log("定时抓拍流程失败");
        window._hmt.push([
          "_trackEvent",
          "摄像头框",
          "定时抓拍流程失败",
          (this.lastSnapTime ? "(非初次抓拍)" : "") + "将再次抓拍",
        ]);
        this.retrySnapTimeout = setTimeout(() => {
          this.toggleSnapNow();
        }, 60 * 1000);
      } finally {
        this.videoStartPlay();
        this.decreaseSnapCount();
      }
    },
    videoStartPlay() {
      const video = this.$refs.video;
      video && video.play();
    },
    async snap() {
      // TODO: chrome 70. FaceDetector检测人脸
      // var canvas = document.createElement("canvas");
      // canvas.width = 220;
      // canvas.height = 165;

      // var context = canvas.getContext("2d");
      // context.drawImage(this.$refs.video, 0, 0, 220, 165);
      // var f = new FaceDetector();
      // const v = await f.detect(canvas);
      // console.log(v);
      // return;

      this.$Message.destroy();
      try {
        this.disableSnap = true;
        // console.log("disableSnap: " + this.disableSnap);
        // await new Promise(resolve =>
        //   setTimeout(() => {
        //     console.log(new Date());
        //     resolve();
        //   }, 3000)
        // );
        // return;
        // if(this.disableSnap) return; // 避免界面没有更新。
        this.msg = "拍照中...";
        const captureBlob = await this.getSnapShot();
        this.videoStartPlay();
        this.msg = "上传照片中...";
        const [captureFilePath, signIdentifier] = await this.uploadToServer(
          captureBlob
        );
        this.msg = "人脸比对中...";
        await this.faceCompareSync(captureFilePath, signIdentifier);
      } catch (error) {
        // FIXME: more processing
        console.log("同步照片比对流程失败");
        throw error;
      } finally {
        this.videoStartPlay();
        this.msg = "开始识别";
        this.disableSnap = false;
      }
    },
    async getSnapShot() {
      return new Promise((resolve, reject) => {
        const video = this.$refs.video;
        if (video.readyState !== 4 || !video.srcObject.active) {
          this.$Message.error({
            content: "摄像头没有正常启用",
            duration: 5,
            closable: true,
          });
          window._hmt.push([
            "_trackEvent",
            "摄像头框",
            "摄像头状态",
            "摄像头没有正常启用-退出" +
              (this.lastSnapTime ? "(非初次抓拍)" : ""),
          ]);
          reject("摄像头没有正常启用");
          this.logout(
            "?LogoutReason=" +
              "摄像头没有正常启用-退出" +
              (this.lastSnapTime ? "(非初次抓拍)" : "")
          );
          return;
        }
        video.pause();
        var canvas = document.createElement("canvas");
        canvas.width = 220;
        canvas.height = 165;

        var context = canvas.getContext("2d");
        context.drawImage(video, 0, 0, 220, 165);

        canvas.toBlob(resolve, "image/png", 0.95);
      });
    },
    async uploadToServer(captureBlob) {
      async function blobToArray(blob) {
        return new Promise(resolve => {
          var reader = new FileReader();
          reader.addEventListener("loadend", function() {
            // reader.result contains the contents of blob as a typed array
            resolve(reader.result);
          });
          reader.readAsArrayBuffer(blob);
        });
      }
      //保存抓拍照片到服务器
      let resultUrl, signIdentifier;
      try {
        const buffer = await blobToArray(captureBlob);

        // console.log(buffer);
        // var view1 = new Uint8Array(buffer);
        // console.log(buffer[0], buffer[1], buffer[429721]);
        const fileMd5 = MD5(buffer);
        console.log(fileMd5);

        const params = new URLSearchParams();
        params.append("fileSuffix", "png");
        params.append("fileMd5", fileMd5);
        const res = await this.$http.get(
          "/api/ecs_oe_student/examControl/getCapturePhotoUpYunSign?" + params
        );

        // console.log(res);

        // let myHeaders = new Headers();
        // for (let [k, v] of Object.entries(res.data.headers)) {
        //   // console.log(k, v);
        //   if (k.includes("tion") || k.includes("Date") || k.includes("MD5")) {
        //     if (k === "Date") k = "x-date";
        //     myHeaders.append(k, v);
        //   }
        let myFormData = new FormData();
        for (let [k, v] of Object.entries(res.data.formParams)) {
          myFormData.append(k, v);
        }
        myFormData.append("file", captureBlob);

        try {
          await fetch(res.data.formUrl, {
            method: "POST",
            body: myFormData,
          });
        } catch (error) {
          window._hmt.push([
            "_trackEvent",
            "摄像头框",
            "抓拍照片保存失败--upyun",
            error.name + ": " + error.message,
          ]);
          throw error;
        }

        // console.log(response);
        resultUrl = res.data.accessUrl;
        signIdentifier = res.data.signIdentifier;
        // this.serverLog("debug/S-005001", "抓拍照片保存成功：");
        window._hmt.push(["_trackEvent", "摄像头框", "抓拍照片保存成功"]);
      } catch (e) {
        console.log(e);
        // this.serverLog("debug/S-006001", "抓拍照片保存失败");
        window._hmt.push(["_trackEvent", "摄像头框", "抓拍照片保存失败"]);
        this.$Message.error({
          content: "保存抓拍照片到服务器失败！",
          duration: 15,
          closable: true,
        });
        throw "保存抓拍照片到服务器失败！";
      }

      // let UPYUN_URL;
      // try {
      //   UPYUN_URL = (await this.$http.get("/api/ecs_oe_student_face/upyun"))
      //     .data.downloadPrefix;
      // } catch (error) {
      //   this.$Message.error({ content: "获取照片下载前缀失败！", duration: 15,  closable: true});
      //   throw "获取照片下载前缀失败！";
      // }
      return [resultUrl, signIdentifier];
    },
    async faceCompareSync(captureFilePath, signIdentifier) {
      try {
        const res = await this.$http.post(
          "/api/ecs_oe_student_face/examCaptureQueue/compareFaceSync?signIdentifier=" +
            signIdentifier +
            "&fileUrl=" +
            encodeURIComponent(captureFilePath)
        );

        // TODO: 识别成功、失败的通知或跳转
        this.$emit("on-recognize-result", {
          error: null,
          pass: res.data.isPass,
          stranger: res.data.isStranger,
        });
      } catch (e) {
        console.log(e);
        // this.$Message.error(e.message);
        throw "同步照片比较失败！";
      }
    },
    async faceCompare(captureFilePath, signIdentifier, examRecordDataId) {
      try {
        let cameraInfos;
        let hasVirtualCamera = false;
        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 {
                    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);
                      this.serverLog("debug/S-001001", cameraInfos);
                    }
                    if (cameraInfos.includes('""')) {
                      hasVirtualCamera = true;
                    }
                    resolve();
                  } catch (error) {
                    window._hmt.push([
                      "_trackEvent",
                      "摄像头框",
                      "虚拟摄像头-读取摄像头列表失败",
                    ]);
                    reject("读取摄像头列表失败");
                  }
                });
              });
            }
          } catch (error) {
            console.log(error);
          }
        }

        let body = {
          fileUrl: captureFilePath,
          signIdentifier,
          examRecordDataId,
        };

        if (cameraInfos) {
          body.cameraInfos = cameraInfos;
          body.hasVirtualCamera = hasVirtualCamera;
        }
        const res = await this.$http.post(
          "/api/ecs_oe_student_face/examCaptureQueue/uploadExamCapture",
          body
        );
        const fileName = res.data;
        try {
          await this.showSnapResult(fileName, examRecordDataId);
        } catch (error) {
          this.$Message.error({
            content: "设置获取抓拍结果失败！",
            duration: 15,
            closable: true,
          });
        }
      } catch (e) {
        console.log(e);
        // this.$Message.error(e.message);
        throw "异步比较抓拍照片失败";
      }
    },
    async showSnapResult(fileName, examRecordDataId) {
      if (!fileName) return; // 交卷后提交照片会得不到照片名称
      if (this.$route.name !== "OnlineExamingHome") {
        // 非考试页，不显示结果，也不继续查询
        return;
      }

      try {
        // 获取抓拍结果
        const snapRes =
          (await this.$http.get(
            "/api/ecs_oe_student_face/examCaptureQueue/getExamCaptureResult?fileName=" +
              fileName +
              "&examRecordDataId=" +
              examRecordDataId
          )).data || {};
        if (snapRes.isCompleted) {
          if (snapRes.isStranger) {
            this.$Message.error({
              content: "请独立完成考试",
              duration: 5,
              closable: true,
            });
          } else if (!snapRes.isPass) {
            this.$Message.error({
              content: "请调整坐姿，诚信考试",
              duration: 5,
              closable: true,
            });
          }
        } else {
          this.showSnapResultTimeout = setTimeout(
            this.showSnapResult.bind(this, fileName, examRecordDataId),
            30 * 1000
          );
        }
      } catch (e) {
        console.log(e);
        if (this.$route.name !== "OnlineExamingHome") {
          // 非考试页，不显示结果，也不继续查询
          return;
        }
        this.$Message.error(e.message);
        throw e.message;
      }
    },
    async nativeFaceDetectorStats() {
      // 如果不存在 window.FaceDetector ，则不统计。chrome 76+ 在win7上是有window.FaceDetector的，但是不能执行成功。
      if (!window.FaceDetector) {
        window._hmt.push([
          "_trackEvent",
          "摄像头框",
          "native FaceDetector",
          "没有能力",
        ]);
        return;
      }

      try {
        var canvas = document.createElement("canvas");
        canvas.width = 220;
        canvas.height = 165;

        var context = canvas.getContext("2d");
        context.drawImage(this.$refs.video, 0, 0, 220, 165);
        var f = new window.FaceDetector();
        const v = await f.detect(canvas);
        console.log("nativeFaceDetector", v);
        window._hmt.push([
          "_trackEvent",
          "摄像头框",
          "native FaceDetector",
          "成功",
        ]);
      } catch (e) {
        console.log(e);
        window._hmt.push([
          "_trackEvent",
          "摄像头框",
          "native FaceDetector",
          "失败",
        ]);
      }
    },
  },
};
</script>

<style scoped>
.verify-button {
  font-size: 16px;
  background-color: #ffcc00;
  display: inline-block;
  padding: 6px 16px;
  border-radius: 6px;
}

.verify-button:hover {
  color: #444444;
  cursor: pointer;
}

.disable-verify-button {
  background-color: #f7f7f7;
  color: #c5c8ce;
}

.disable-verify-button:hover {
  cursor: not-allowed;
  color: #c5c8ce;
}
</style>
