<template>
  <div class="scan-paper">
    <div class="part-box part-box-pad part-box-flex scan-head">
      <div>
        <h2>课程（代码）：{{ task.courseName }}（{{ task.courseCode }}）</h2>
      </div>
      <div>
        <el-button :disabled="!hasSelectedData" type="primary" @click="toBind"
          >重新绑定</el-button
        >
        <el-button :disabled="!canClear" type="danger" @click="clearStage"
          >清空</el-button
        >
        <el-button
          :disabled="!canSave"
          :loading="saving"
          type="primary"
          @click="toSave"
          >保存</el-button
        >
        <el-button
          type="primary"
          :loading="scanStatus === 'SCAN'"
          :disabled="!canScan"
          @click="toScan"
        >
          {{ statusDesc[scanStatus] }}
        </el-button>
      </div>
    </div>

    <div class="scan-body">
      <div class="scan-result">
        <div class="mb-4 tab-btns scan-result-head">
          <el-button
            size="medium"
            :type="curTab === 'normal' ? 'primary' : 'default'"
            @click="switchTab('normal')"
            >正常 <span>[{{ normalCount }}]</span></el-button
          >
          <el-button
            size="medium"
            :type="curTab === 'error' ? 'danger' : 'default'"
            @click="switchTab('error')"
            >异常 <span>[{{ errorCount }}]</span></el-button
          >
        </div>
        <div class="scan-result-body">
          <scan-result-table
            v-if="isNormalTab"
            ref="scanResultTableRef"
            :table-data.sync="scanStageList"
            tab="normal"
            @row-click="rowClickHandle"
            @select-change="selectChange"
            @delete-paper="deletePaperHandle"
          ></scan-result-table>
          <scan-result-table
            v-else
            ref="scanResultTableRef"
            :table-data.sync="errorStageList"
            tab="error"
            @row-click="rowClickHandle"
            @select-change="selectChange"
            @delete-paper="deletePaperHandle"
          ></scan-result-table>
        </div>
        <div v-if="isNormalTab" class="scan-result-foot">
          <span>共</span>
          <span class="color-primary mlr-1">{{ studentCount }}</span>
          <span>人，</span>
          <span class="color-primary mr-1">{{ paperCount }}</span>
          <span>张图片</span>
        </div>
      </div>
      <div class="scan-content">
        <image-contain
          v-if="curPaper && curPaper.url"
          ref="ImageContain"
          :image="curPaper"
          :show-rotate="false"
          @on-prev="toPrevPaper"
          @on-next="toNextPaper"
        ></image-contain>
      </div>
    </div>

    <!-- ManualBindDialog -->
    <manual-bind-dialog
      ref="ManualBindDialog"
      :datas="selectList"
      :task="task"
      @confirm="bindConfirm"
    ></manual-bind-dialog>
    <!-- SelectBatchNoDialog -->
    <select-batch-no-dialog
      ref="SelectBatchNoDialog"
      @confirm="saveScanData"
    ></select-batch-no-dialog>
  </div>
</template>

<script>
import { mapState } from "vuex";
import {
  getPreUploadFiles,
  clearDir,
  deleteFiles,
  decodeImageCode,
  getDirScanFile,
  batchSaveImages,
} from "../../../plugins/imageOcr";
import db from "../../../plugins/db";
import { evokeScanner } from "../../../plugins/scanner";
import ImageContain from "@/components/ImageContain.vue";
import ScanResultTable from "../components/ScanResultTable.vue";
import ManualBindDialog from "../components/ManualBindDialog.vue";
import SelectBatchNoDialog from "../components/SelectBatchNoDialog.vue";

import timeMixins from "@/mixins/setTimeMixins";
import { getStudentInfo } from "../api";
import log4js from "@/plugins/logger";
import { randomCode, calcSum } from "@/plugins/utils";
import { getStageDir } from "@/plugins/env";
const logger = log4js.getLogger("scan");

export default {
  name: "scan-paper",
  mixins: [timeMixins],
  components: {
    ImageContain,
    ScanResultTable,
    ManualBindDialog,
    SelectBatchNoDialog,
  },
  data() {
    return {
      task: this.$ls.get("task", {}),
      scanStatus: "INIT",
      scanStageList: [],
      errorStageList: [],
      selectList: [],
      statusDesc: {
        INIT: "开始扫描",
        SCAN: "扫描中",
        FINISH: "继续扫描",
      },
      user: this.$ls.get("user", {}),
      saving: false,
      maxCacheCount: 120,
      lastStudentCode: "",
      scanCount: 0,
      menus: [
        { code: "normal", name: "正常" },
        { code: "error", name: "异常" },
      ],
      curTab: "normal",
      // 非等待模式：delayMode:0
      looping: false,
      // 图片预览
      curPapers: [],
      curPaperIndex: 0,
      curPaper: { url: "" },
      stageDir: getStageDir(),
    };
  },
  computed: {
    ...mapState("client", ["ocrArea"]),
    canSave() {
      return (
        this.scanStatus === "FINISH" &&
        this.normalCount > 0 &&
        this.errorCount === 0
      );
    },
    canScan() {
      return this.errorCount + this.normalCount <= this.maxCacheCount;
    },
    canClear() {
      return this.studentCount > 0;
    },
    hasSelectedData() {
      return Boolean(this.selectList.length);
    },
    IS_DELAY_MODE() {
      return this.GLOBAL.delayMode === 1;
    },
    errorCount() {
      return calcSum(this.errorStageList.map((item) => item.papers.length));
    },
    normalCount() {
      return calcSum(this.scanStageList.map((item) => item.papers.length));
    },
    isNormalTab() {
      return this.curTab === "normal";
    },
    studentCount() {
      return this.isNormalTab
        ? this.scanStageList.length
        : this.errorStageList.length;
    },
    paperCount() {
      return this.isNormalTab ? this.normalCount : this.errorCount;
    },
  },
  created() {
    this.$on("go-back", this.goBackHandle);
  },
  beforeDestroy() {
    this.stopLoopScaningFile();
    this.clearFiles();
  },
  methods: {
    initData() {
      this.lastStudentCode = "";
      this.scanStageList = [];
      this.errorStageList = [];
      this.scanStatus = "INIT";
      this.curPapers = [];
      this.curPaperIndex = 0;
      this.curPaper = { url: "" };
      this.scanCount = 0;
    },
    clearFiles() {
      clearDir(this.stageDir);
    },
    async goBackHandle() {
      if (this.scanStageList.length) {
        const res = await this.$confirm(
          `当前存在未保存的扫描数据，确定要退出吗?`,
          "警告",
          {
            type: "warning",
          }
        ).catch(() => {});
        if (res !== "confirm") return;
      }

      this.$router.go(-1);
      logger.info(`99退出扫描`);
    },
    switchTab(tab) {
      this.curTab = tab;
      this.selectList = [];
    },
    // scan
    toScan() {
      if (!this.canScan) {
        this.$message.error("已超过最大缓存数量，请先保存数据再继续扫描！");
        return;
      }
      if (this.scanStatus === "INIT") {
        this.startTask();
      } else {
        this.continueTask();
      }
    },
    startTask() {
      logger.info(`01开始扫描`);
      this.continueTask();
    },
    continueTask() {
      this.scanStatus = "SCAN";
      if (this.IS_DELAY_MODE) {
        this.evokeScanExe();
      } else {
        this.evokeScanExeNotDelay();
      }
    },
    async evokeScanExe() {
      logger.info("02唤起扫描仪");
      await evokeScanner(this.GLOBAL.input).catch((error) => {
        console.error(error);
      });

      // 缓存已扫描的数据
      const res = getPreUploadFiles(this.GLOBAL.input, true);
      if (!res.succeed) {
        logger.error(`03扫描仪停止，故障：${res.errorMsg}`);
        this.$message.error(res.errorMsg);
        this.scanStatus = "FINISH";
        return;
      }
      logger.info(`03扫描仪停止，扫描数：${res.data.length}`);
      await this.stageScanImage(res.data);
      this.scanStatus = "FINISH";
      logger.info(`03-1完成条码解析`);
    },
    async evokeScanExeNotDelay() {
      logger.info("02唤起扫描仪");
      this.looping = true;
      this.loopScaningFile();

      await evokeScanner(this.GLOBAL.input).catch((error) => {
        console.error(error);
      });
      this.stopLoopScaningFile();
      await this.getScaningFile();

      const scanCount = this.scanStageList.length - this.scanCount;
      this.scanCount = this.scanStageList.length;

      // 已扫描的数据
      const res = getPreUploadFiles(this.GLOBAL.input);
      this.scanStatus = "FINISH";
      if (!res.succeed) {
        logger.error(
          `03扫描仪停止，扫描数：${scanCount}，故障：${res.errorMsg}`
        );
        this.$message.error(res.errorMsg);
        return;
      }
      logger.info(`03扫描仪停止，扫描数：${scanCount}`);
    },
    async stageScanImage(imageList) {
      const ocrAreaContent = JSON.stringify(this.ocrArea);
      for (let i = 0, len = imageList.length; i < len; i++) {
        const fileInfo = {
          id: "",
          taskId: this.task.id,
          schoolId: this.task.schoolId,
          semesterId: this.task.semesterId,
          examId: this.task.examId,
          courseCode: this.task.courseCode,
          courseName: this.task.courseName,
          frontOriginImgPath: imageList[i].frontFile,
          versoOriginImgPath: imageList[i].versoFile,
          isFormal: 1,
          studentName: "",
          studentCode: "",
          ocrArea: ocrAreaContent,
          fileTypeId: "0",
          fileTypeName: "答题卡",
          roomOrClass: "",
          batchNo: "",
          clientUserId: this.user.id,
          clientUsername: this.user.loginName,
          clientUserLoginTime: this.user.loginTime,
          select: false,
        };

        const code = await decodeImageCode(
          fileInfo.frontOriginImgPath,
          this.ocrArea
        ).catch((err) => {
          console.error(err);
          logger.error(`03条码解析失败，${err}`);
        });
        fileInfo.studentCode = code || this.lastStudentCode;
        // 按照识别空自动绑定前一张code的规则，无论识别到的code是否合法，都应该作为最后一次识别的code
        // 否则，第一张进异常，后面空白条码页会自动进正常页面，对后续处理带来一定困扰
        if (fileInfo.studentCode) {
          this.lastStudentCode = fileInfo.studentCode;
        }

        fileInfo.id = `${fileInfo.studentCode || 0}-${randomCode(16)}`;

        const studentStage = this.scanStageList.find(
          (item) => item.studentCode === fileInfo.studentCode
        );
        if (studentStage) {
          studentStage.papers.push(fileInfo);
          continue;
        }

        if (fileInfo.studentCode) {
          const res = await getStudentInfo({
            examId: this.task.examId,
            courseCode: this.task.courseCode,
            studentCode: fileInfo.studentCode,
          }).catch(() => {});
          if (res) {
            fileInfo.studentName = res.studentName;
            this.scanStageList.push({
              id: res.id,
              studentCode: res.studentCode,
              studentName: res.studentName,
              courseCode: res.courseCode,
              courseName: res.courseName,
              teacher: res.teacher,
              teachClass: res.teachClass,
              collegeName: res.collegeName,
              majorName: res.majorName,
              className: res.className,
              score: res.score,
              remark: res.remark,
              select: false,
              papers: [fileInfo],
            });
            continue;
          }
        }

        const errorStudentStage = this.errorStageList.find(
          (item) => item.studentCode === fileInfo.studentCode
        );
        if (errorStudentStage) {
          errorStudentStage.papers.push(fileInfo);
          continue;
        }

        this.errorStageList.push({
          id: `none-${randomCode(16)}`,
          studentCode: fileInfo.studentCode,
          select: false,
          papers: [fileInfo],
        });
      }

      if (imageList.length) {
        const lastImg = imageList.pop();
        this.curPapers = [lastImg.frontFile, lastImg.versoFile];
        this.curPaperIndex = 0;
        this.setCurPaper(0);
      }
    },
    toSave() {
      if (this.errorStageList.length) {
        this.$message.error("还有异常数据未处理！");
        return;
      }

      if (!this.scanStageList.length) {
        this.$message.error("当前无要保存的数据！");
        return;
      }
      this.$refs.SelectBatchNoDialog.open();
    },
    async saveScanData(batchNo) {
      if (this.saving) return;
      this.saving = true;

      this.clearViewPapers();
      logger.info(`04-1开始保存数据`);
      try {
        let datas = this.scanStageList
          .map((item) => item.papers)
          .flat()
          .map((item) => {
            item.batchNo = batchNo;
            return item;
          });
        datas = await batchSaveImages(datas, this.task.courseCode);
        await db.batchSaveUploadInfo(datas);
      } catch (err) {
        console.error(err);
        logger.error(`04-1保存数据错误，${err}`);
        this.saving = false;
        this.$message.error("保存数据错误，请重新尝试！");
        return Promise.reject();
      }

      this.$message.success("保存成功！");
      this.saving = false;
      logger.info(`04-2保存数据成功`);
      this.clearFiles();
      this.initData();
    },
    // delay mode
    // 实时获取扫描图片
    async loopScaningFile() {
      this.clearSetTs();
      if (!this.looping) return;
      // const st = Date.now();
      await this.getScaningFile();
      // console.log(`耗时：${Date.now() - st}ms`);

      this.addSetTime(this.loopScaningFile, 1 * 1000);
    },
    stopLoopScaningFile() {
      this.clearSetTs();
      this.looping = false;
    },
    async getScaningFile() {
      const newScanFiles = getDirScanFile(this.GLOBAL.input);
      await this.stageScanImage(newScanFiles);
    },
    // table action
    toBind() {
      if (!this.selectList.length) return;
      this.$refs.ManualBindDialog.open();
    },
    bindConfirm(studentInfo) {
      if (this.isNormalTab) {
        this.normalBind(studentInfo);
      } else {
        this.errorBind(studentInfo);
      }
      this.clearViewPapers();
      this.$refs.scanResultTableRef.clearSelection();
    },
    normalBind(studentInfo) {
      const selectPaperIds = this.selectList
        .map((item) => item.papers.map((p) => p.id))
        .flat();
      let prevIndex = this.scanStageList.findIndex(
        (row) => row.id === this.selectList[0].id
      );

      // 删除选择的数据
      this.scanStageList.forEach((row) => {
        row.papers = row.papers.filter((p) => !selectPaperIds.includes(p.id));
      });

      this.scanStageList = this.scanStageList.filter(
        (row) => row.papers.length
      );

      // 绑定逻辑
      const preAddPapers = this.selectList
        .map((row) => {
          return row.papers.map((p) => {
            return {
              ...p,
              select: false,
              studentCode: studentInfo.studentCode,
              studentName: studentInfo.studentName,
              id: `${studentInfo.studentCode}-${randomCode(16)}`,
            };
          });
        })
        .flat();

      const stageStudent = this.scanStageList.find(
        (row) => row.studentCode === studentInfo.studentCode
      );
      if (stageStudent) {
        stageStudent.papers.push(...preAddPapers);
        return;
      }

      prevIndex = Math.max(
        1,
        Math.min(prevIndex + 1, this.scanStageList.length)
      );

      this.scanStageList.splice(prevIndex, 0, {
        ...studentInfo,
        select: false,
        papers: preAddPapers,
      });
    },
    errorBind(studentInfo) {
      const selectPaperIds = this.selectList
        .map((item) => item.papers.map((p) => p.id))
        .flat();

      // 删除选择的数据
      this.errorStageList.forEach((row) => {
        row.papers = row.papers.filter((p) => !selectPaperIds.includes(p.id));
      });

      this.errorStageList = this.errorStageList.filter(
        (row) => row.papers.length
      );

      // 绑定逻辑
      const preAddPapers = this.selectList
        .map((row) => {
          return row.papers.map((p) => {
            return {
              ...p,
              select: false,
              studentCode: studentInfo.studentCode,
              studentName: studentInfo.studentName,
              id: `${studentInfo.studentCode}-${randomCode(16)}`,
            };
          });
        })
        .flat();

      const stageStudent = this.scanStageList.find(
        (row) => row.studentCode === studentInfo.studentCode
      );
      if (stageStudent) {
        stageStudent.papers.push(...preAddPapers);
        return;
      }

      this.scanStageList.push({
        ...studentInfo,
        select: false,
        papers: preAddPapers,
      });
    },
    async clearStage() {
      const name = this.isNormalTab ? "正常" : "异常";
      const res = await this.$confirm(
        `确定要清空所有【${name}】数据吗?`,
        "警告",
        {
          type: "warning",
        }
      ).catch(() => {});
      if (res !== "confirm") return;

      if (this.isNormalTab) {
        const files = this.scanStageList
          .map((row) =>
            row.papers.map((p) => [p.frontOriginImgPath, p.versoOriginImgPath])
          )
          .flat(2);
        deleteFiles(files);
        this.scanStageList = [];
      } else {
        const files = this.errorStageList
          .map((row) =>
            row.papers.map((p) => [p.frontOriginImgPath, p.versoOriginImgPath])
          )
          .flat(2);
        deleteFiles(files);
        this.errorStageList = [];
      }

      this.curPapers = [];
      this.curPaperIndex = 0;
      this.curPaper = { url: "" };

      logger.info(`99数据清空`);
      this.$message.success("数据已清空！");
    },
    selectChange(data) {
      this.selectList = data;
    },
    deletePaperHandle(deletedPapers) {
      this.curPapers = this.curPapers.filter((p) => !deletedPapers.includes(p));
      if (!this.curPapers.length) {
        this.curPaperIndex = 0;
        this.curPaper = { url: "" };
        return;
      }

      if (deletedPapers.includes(this.curPaper.url)) {
        this.curPaperIndex = 0;
        this.setCurPaper(0);
        return;
      }

      this.curPaperIndex = this.curPapers.indexOf(this.curPaper.url);
    },
    // image-preview
    rowClickHandle({ curPapers, curPaperIndex }) {
      this.curPapers = curPapers;
      this.curPaperIndex = curPaperIndex;
      this.setCurPaper(curPaperIndex);
    },
    clearViewPapers() {
      this.curPapers = [];
      this.curPaperIndex = 0;
      this.curPaper = { url: "" };
    },
    setCurPaper(index) {
      this.curPaper = { url: `file:///${this.curPapers[index]}` };
    },
    toPrevPaper() {
      if (this.curPaperIndex <= 0) {
        this.$message.error("没有上一页了");
        return;
      }
      this.setCurPaper(--this.curPaperIndex);
    },
    toNextPaper() {
      if (this.curPaperIndex >= this.curPapers.length - 1) {
        this.$message.error("没有下一页了");
        return;
      }
      this.setCurPaper(++this.curPaperIndex);
    },
  },
};
</script>
