<script setup lang="ts">
import * as faceapi from "face-api.js";
import { FACE_API_MODEL_PATH } from "@/constants/constants";
import { isThisMachineOwnByStudent } from "@/utils/utils";
import { onMounted, onUnmounted } from "vue";
import { useTimers } from "@/setups/useTimers";
import { store } from "@/store/store";
import { throttle } from "lodash-es";
import { MessageReactive } from "naive-ui";

const { addTimeout, addInterval } = useTimers();
// window.faceapi = faceapi;

// const os = (function() {
//   const ua = navigator.userAgent.toLowerCase();
//   return {
//     isWin2K: /windows nt 5.0/.test(ua),
//     isXP: /windows nt 5.1/.test(ua),
//     isVista: /windows nt 6.0/.test(ua),
//     isWin7: /windows nt 6.1/.test(ua),
//     isWin8: /windows nt 6.2/.test(ua),
//     isWin81: /windows nt 6.3/.test(ua),
//     isWin10: /windows nt 10.0/.test(ua),
//   };
// })();

onMounted(async () => {
  try {
    await faceapi.nets.tinyFaceDetector.load(FACE_API_MODEL_PATH);
    // faceapi.nets.faceRecognitionNet.load(modelsPath);
    await faceapi.loadFaceLandmarkModel(FACE_API_MODEL_PATH);
  } catch (error) {
    logger({
      cnl: ["server"],
      key: "FaceTracking",
      act: "loading faceapi error",
      possibleError: error,
    });
    return;
  }
  faceapi.tf.ENV.set("WEBGL_PACK", false);

  async function trackHead() {
    const video = <HTMLVideoElement>document.getElementById("video");
    if (video?.readyState === 4 && faceapi.nets.tinyFaceDetector.params) {
      clearInterval(trackHeadInterval);
    } else {
      logger({
        cnl: ["server"],
        key: "FaceTracking",
        act: "未达到实时人脸开启条件",
      });
      return;
    }
    logger({
      cnl: ["server", "console"],
      key: "FaceTracking",
      act: "start tracking ... ",
    });
    await detectTest();

    await detectFaces();
  }

  // 重复启动头部追踪，直到成功启动
  const trackHeadInterval = addInterval(trackHead, 1000);
});

//#region webgl 参数
let __cache4WebglAvailable: boolean | null = null;
function webgl_available() {
  if (__cache4WebglAvailable !== null) return __cache4WebglAvailable;

  var canvas = document.createElement("canvas");
  var gl = canvas.getContext("webgl");
  __cache4WebglAvailable = !!(gl && gl instanceof WebGLRenderingContext);
  return __cache4WebglAvailable;
}

let __cache4TensorFlowWebPackStatus: number | boolean | null = null;
function tensorFlowWebPackStatus() {
  if (__cache4TensorFlowWebPackStatus !== null)
    return __cache4TensorFlowWebPackStatus;

  try {
    __cache4TensorFlowWebPackStatus = faceapi.tf.ENV.get("WEBGL_PACK");
  } finally {
    if (__cache4TensorFlowWebPackStatus !== true) {
      __cache4TensorFlowWebPackStatus = false;
    }
  }
  return __cache4TensorFlowWebPackStatus;
}
//#endregion

let bestInputSize = 128;
let disableFaceTracking = false;

/** 测试学生电脑适合的参数 */
async function detectTest() {
  const inputSizeList = [128, 160, 224, 320, 416, 512, 608];
  const succRate = [0, 0, 0, 0, 0, 0, 0];
  const detectTimes = 6;

  try {
    const detectStartTime = performance.now();
    const videoEl = <HTMLVideoElement>document.getElementById("video");
    if (!videoEl) return;
    const options = new faceapi.TinyFaceDetectorOptions({
      inputSize: 128,
      scoreThreshold: 0.5,
    });
    const result: any = await Promise.race([
      faceapi.detectAllFaces(videoEl, options),
      new Promise((resolve) => setTimeout(resolve, 10 * 1000)),
    ]);
    const detectEndTime = performance.now();
    if (!result || detectEndTime - detectStartTime > 2 * 1000) {
      disableFaceTracking = true;
      _hmt.push(["_trackEvent", "答题页面", "启动检测耗时过长：停止实时"]);
      logger({
        cnl: ["server"],
        key: "FaceTracking",
        act: "实时人脸检测",
        dtl: "启动检测耗时过长：停止实时",
        ext: {
          result: JSON.stringify(result),
          cost: detectEndTime - detectStartTime,
        },
      });
      return;
    }
  } catch (error) {
    disableFaceTracking = true;
    _hmt.push(["_trackEvent", "答题页面", "启动检测错误：停止实时"]);
    logger({
      cnl: ["server"],
      key: "FaceTracking",
      act: "启动检测错误：停止实时",
      possibleError: error,
    });
    return;
  }

  let singleCost = 0;
  for (let idx = 0; idx < inputSizeList.length; idx++) {
    for (let n = 0; n < detectTimes; n++) {
      await new Promise((resolve) =>
        setTimeout(resolve, Math.min(singleCost * 2, 3 * 1000))
      );
      if (store.exam.isDoingFaceLiveness) {
        logger({
          cnl: ["server", "console"],
          key: "FaceTracking",
          act: "正在活检，暂停实时人脸",
        });
        await new Promise((resolve) => setTimeout(resolve, 120 * 1000));
      }
      const inputSize = inputSizeList[idx];
      const videoEl = <HTMLVideoElement>document.getElementById("video");
      if (!videoEl) return;
      try {
        const detectStartTime = performance.now();
        const options = new faceapi.TinyFaceDetectorOptions({
          inputSize: inputSize,
          scoreThreshold: 0.5,
        });
        // const result = await faceapi.detectAllFaces(videoEl, options);
        // console.log(result);
        const result: any = await Promise.race([
          faceapi.detectAllFaces(videoEl, options),
          new Promise((resolve) => setTimeout(resolve, 10 * 1000)),
        ]);
        const detectEndTime = performance.now();
        singleCost = detectStartTime - detectEndTime;
        if (singleCost > 0.2 * 1000) {
          disableFaceTracking = true;
          _hmt.push(["_trackEvent", "答题页面", "单次检测耗时过长：停止实时"]);
          logger({
            cnl: ["server"],
            key: "FaceTracking",
            act: "单次检测耗时过长：停止实时",
            ext: { cost: singleCost },
          });
          return;
        }

        if (result && result.length >= 1) {
          logger({
            cnl: ["server"],
            key: "FaceTracking",
            dtl: `inputSize: ${inputSize} ${result.length}`,
          });
          succRate[idx]++;
        } else {
          logger({
            cnl: ["server"],
            key: "FaceTracking",
            act: "FT检测失败",
            dtl: `inputSize: ${inputSize}`,
          });
        }
      } catch (error) {
        logger({
          cnl: ["server"],
          key: "FaceTracking",
          act: "FT检测失败-异常",
          dtl: `inputSize: ${inputSize}`,
          possibleError: error,
        });
      }
    }

    if (succRate[idx] === detectTimes) {
      logger({
        cnl: ["server"],
        key: "FaceTracking",
        act: "FT提前选中",
        dtl: `inputSize: ${inputSizeList[idx]}`,
      });
      break;
    }
  }

  const max = Math.max(...succRate);

  const idx = succRate.indexOf(max);

  bestInputSize = inputSizeList[idx];
  logger({
    cnl: ["server", "local"],
    key: "FaceTracking",
    pgn: "实时人脸检测",
    dtl: "最好的 inputSize 为:" + bestInputSize,
    ext: { succRate },
  });

  return bestInputSize;
}

function getFaceDetectorOptions() {
  return new faceapi.TinyFaceDetectorOptions({
    inputSize: bestInputSize || 128,
    scoreThreshold: 0.5,
  });

  // return new faceapi.SsdMobilenetv1Options({ minConfidence: 0.8 });
  // return new faceapi.MtcnnOptions({ minFaceSize: 200, scaleFactor: 0.8 });
}

let displayedMsg: MessageReactive | null = null;
const indepentExamingMsg = throttle(
  () =>
    document.getElementById("video") &&
    (displayedMsg = $message.warning("请独立完成考试")),
  20 * 1000
);
const posureExamingMsg = throttle(
  () =>
    document.getElementById("video") &&
    (displayedMsg = $message.warning("请调整坐姿，诚信考试")),
  20 * 1000
);
const bgCheckExamingMsg = throttle(
  () =>
    document.getElementById("video") &&
    $message.warning("请保持正确坐姿，确保脸部在摄像头内，背景无强光。"),
  20 * 1000
);

let singleTimeUsage = 0;
let multipleTimeUsage = 0;
const detectTimeArray: number[] = [];
let failTimes = 0;

let detectFacesTimeout: number;

/** 定时检测人脸 */
async function detectFaces() {
  if (import.meta.env.DEV) return;
  if (
    disableFaceTracking ||
    singleTimeUsage > 10 * 1000 ||
    multipleTimeUsage > 0.5 * 1000
  ) {
    logger({
      cnl: ["server"],
      key: "FaceTracking",
      act: "关闭实时人脸检测，因为耗时过长",
      ext: { multipleTimeUsage },
    });
    _hmt.push(["_trackEvent", "答题页面", "关闭实时人脸检测，因为耗时过长"]);
    return;
  }
  if (store.exam.isDoingFaceLiveness) {
    logger({
      cnl: ["server"],
      key: "FaceTracking",
      pgn: "实时人脸检测",
      act: "正在活检，暂停实时人脸",
    });
    clearTimeout(detectFacesTimeout);
    detectFacesTimeout = addTimeout(() => void detectFaces(), 10 * 1000);
    return;
  }

  const videoEl = <HTMLVideoElement>document.getElementById("video");
  const detectStartTime = performance.now();

  const options = getFaceDetectorOptions();
  let result;

  try {
    logger({
      cnl: ["server"],
      lvl: "debug",
      key: "FaceTracking",
      act: "开始一次人脸检测",
    });
    result = await faceapi
      // .detectSingleFace(videoEl, options)
      .detectAllFaces(videoEl, options);
  } catch (e) {
    _hmt.push(["_trackEvent", "答题页面", "实时人脸检测失败"]);
    logger({
      cnl: ["server"],
      key: "FaceTracking",
      act: "实时人脸检测失败",
      possibleError: e,
    });
    throw e;
  }

  if (!result) {
    try {
      logger({
        cnl: ["server"],
        key: "不可能的事情发生了",
        dtl: "人脸检测结果格式不符合预期",
        stk: JSON.stringify(result || {}),
      });
    } catch (error) {
      logger({
        cnl: ["server"],
        key: "不可能的事情发生了",
        dtl: "人脸检测结果stringify错误",
      });
    }
  }

  const detectEndTime = performance.now();
  logger({
    cnl: ["server", "console"],
    lvl: "debug",
    key: "FaceTracking",
    pgn: "实时人脸检测",
    act: "做完一次人脸检测，准备统计...",
    ext: {
      resultLen: result.length,
      WebGL: webgl_available(),
      WEBGL_PACK: tensorFlowWebPackStatus(),
      "single detect time": detectEndTime - detectStartTime,
    },
  });
  singleTimeUsage = detectEndTime - detectStartTime;

  if (detectTimeArray.length < 6) {
    // 仅捕获一部分检测次数
    detectTimeArray.push(detectEndTime - detectStartTime);
  }
  if (detectTimeArray.length === 6) {
    detectTimeArray.shift();
    const avg =
      detectTimeArray.reduce((a, b) => a + b, 0) / detectTimeArray.length;
    const roundAvg = Math.round(avg / 100) * 100;
    logger({
      cnl: ["server"],
      key: "FaceTracking",
      pgn: "实时人脸检测",
      ext: {
        detectTimeArray,
        roundAvg: roundAvg + "ms",
        computer: isThisMachineOwnByStudent() ? "学生电脑" : "学习中心电脑",
      },
    });
    detectTimeArray.push(0, 0); // 避免再次达到push条件和上传条件

    // FIXME: 上线初期停止统计此类信息，过于零散
    // const roundAvg100 = Math.round(avg / 100) * 100;
    // const osType = os.isWin7 ? "win7" : os.isWin10 ? "win10" : "other";
    // const stats = `webgl: ${webgl_available()}; tf_backend: ${faceapi.tf.getBackend()}; os: ${osType}; cpu: ${getCPUModel()}`;
    // window._hmt.push([
    //   "_trackEvent",
    //   "答题页面",
    //   "实时人脸检测统计" + roundAvg100 + "ms",
    //   stats,
    // ]);

    multipleTimeUsage = roundAvg;
  }

  if (result.length >= 2) {
    indepentExamingMsg();
  }

  if (result.length === 0) {
    posureExamingMsg();
    failTimes++;
  }

  if (result.length === 1) {
    try {
      // 在失败一次后马上调整坐姿的话，会主动去除警告
      displayedMsg?.destroy();
    } catch (error) {
      displayedMsg = null;
      console.debug("ignore", error);
    }
    failTimes = 0;
  }

  if (result.length !== 1 && !videoEl.classList.contains("video-warning")) {
    videoEl.classList.add("video-warning");
    addTimeout(() => videoEl.classList.remove("video-warning"), 3000);
  }

  clearTimeout(detectFacesTimeout);
  logger({
    cnl: ["server"],
    key: "FaceTracking",
    lvl: "debug",
    act: "准备下次人脸检测",
  });

  const machineCap = 10 * multipleTimeUsage;
  // 最小1秒，最大10秒
  let desiredInterval = Math.min(10 * 1000, Math.max(1000, machineCap));
  detectFacesTimeout = addTimeout(async () => {
    if (failTimes >= 10) {
      bgCheckExamingMsg();
      failTimes = 0;
      await detectTest();
    }
    await detectFaces();
  }, desiredInterval);
}

onUnmounted(() => {
  clearTimeout(detectFacesTimeout);
});
</script>

<template>
  <div></div>
</template>

<style>
@keyframes warning-people {
  0% {
    /* border: solid 5px white; */
    box-shadow: 0 0 20px white;
  }
  100% {
    /* border: solid 5px red; */
    box-shadow: 0 0 20px gold;
  }
}

.video-warning {
  animation: warning-people 3s infinite;
}
</style>
