<template>
  <div class="mark-body-container tw-flex-auto tw-p-2" ref="dragContainer">
    <a-spin
      :spinning="rendering"
      size="large"
      tip="Loading..."
      style="margin-top: 50px"
    >
      <div v-if="!store.currentTask" class="tw-text-center">
        {{ store.message }}
      </div>
      <div v-else :style="{ width: answerPaperScale }">
        <div
          v-for="(item, index) in sliceImagesWithTrackList"
          :key="index"
          class="single-image-container"
        >
          <img :src="item.url" draggable="false" />
          <MarkDrawTrack
            :track-list="item.trackList"
            :special-tag-list="item.tagList"
            :original-image="item.originalImage"
            :slice-image="item.sliceImage"
            :dx="item.dx"
            :dy="item.dy"
          />
          <hr class="image-seperator" />
        </div>
      </div>
    </a-spin>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, reactive, ref, watchEffect } from "vue";
import { store } from "./store";
import filters from "@/filters";
import MarkDrawTrack from "./MarkDrawTrack.vue";
import { SpecialTag, Track } from "@/types";
import { useTimers } from "@/setups/useTimers";
import {
  getDataUrlForSliceConfig,
  getDataUrlForSplitConfig,
  loadImage,
} from "@/utils/utils";
import { dragImage } from "@/features/mark/use/draggable";

interface SliceImage {
  url: string;
  indexInSliceUrls: number;
  trackList: Array<Track>;
  tagList: Array<SpecialTag>;
  originalImage: HTMLImageElement;
  sliceImage: HTMLImageElement;
  dx: number;
  dy: number;
  accumTopHeight: number;
  effectiveWidth: number;
}
// should not render twice at the same time
let __lock = false;
let __currentStudentId = -1; // save __currentStudentIdof lock
export default defineComponent({
  name: "MarkBody",
  components: { MarkDrawTrack },
  emits: ["error"],
  setup(props, { emit }) {
    const { dragContainer } = dragImage();

    const { addTimeout } = useTimers();

    function hasSliceConfig() {
      return store.currentTask?.sliceConfig?.length;
    }

    let rendering = ref(false);
    let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
    let maxSliceWidth = 0; // 最大的裁切块宽度，图片容器以此为准
    let theFinalHeight = 0; // 最终宽度，用来定位轨迹在第几张图片，不包括image-seperator高度

    async function getImageUsingDataUrl(
      dataUrl: string
    ): Promise<HTMLImageElement> {
      return new Promise((resolve) => {
        const image = new Image();
        image.src = dataUrl;
        image.onload = function () {
          resolve(image);
        };
      });
    }

    async function processSliceConfig() {
      if (!store.currentTask) return;

      const images = [];
      const urls = [];
      // 必须要先加载一遍，把“选择整图”的宽高重置后，再算总高度
      for (const sliceConfig of store.currentTask.sliceConfig) {
        const url = filters.toCompleteUrlWithFileServer(
          store.setting.fileServer,
          store.currentTask.sliceUrls[sliceConfig.i - 1]
        );
        const image = await loadImage(url);
        images.push(image);
        urls.push(url);
        if (sliceConfig.w === 0 && sliceConfig.h === 0) {
          // 选择整图时，w/h 为0
          sliceConfig.w = image.naturalWidth;
          sliceConfig.h = image.naturalHeight;
        }
      }

      theFinalHeight = store.currentTask.sliceConfig
        .map((v) => v.h)
        .reduce((acc, v) => (acc += v));
      maxSliceWidth = Math.max(
        ...store.currentTask.sliceConfig.map((v) => v.w)
      );

      // 用来保存sliceImage在整个图片容器中(不包括image-seperator)的高度范围
      let accumTopHeight = 0;
      let accumBottomHeight = 0;
      const tempSliceImagesWithTrackList = [] as Array<SliceImage>;
      for (const sliceConfig of store.currentTask.sliceConfig) {
        accumBottomHeight += sliceConfig.h;
        const url = filters.toCompleteUrlWithFileServer(
          store.setting.fileServer,
          store.currentTask.sliceUrls[sliceConfig.i - 1]
        );
        const indexInSliceUrls = sliceConfig.i;
        const image = images[indexInSliceUrls - 1];

        const dataUrl = getDataUrlForSliceConfig(
          image,
          sliceConfig,
          maxSliceWidth,
          url
        );
        const trackLists = store.currentTask.questionList
          .map((q) => q.trackList)
          .reduce((acc, t) => {
            acc = acc.concat(t);
            return acc;
          }, [] as Array<Track>);
        const thisImageTrackList = trackLists.filter(
          (t) => t.offsetIndex === indexInSliceUrls
        );
        const thisImageTagList = (
          store.currentTask.specialTagList ?? []
        ).filter((t) => t.offsetIndex === indexInSliceUrls);

        const sliceImage = await getImageUsingDataUrl(dataUrl);
        tempSliceImagesWithTrackList.push({
          url: dataUrl,
          indexInSliceUrls,
          // 通过positionY来定位是第几张slice的还原，并过滤出相应的track
          trackList: thisImageTrackList.filter(
            (t) =>
              t.positionY >= accumTopHeight / theFinalHeight &&
              t.positionY < accumBottomHeight / theFinalHeight
          ),
          tagList: thisImageTagList.filter(
            (t) =>
              t.positionY >= accumTopHeight / theFinalHeight &&
              t.positionY < accumBottomHeight / theFinalHeight
          ),
          originalImage: image,
          sliceImage,
          dx: sliceConfig.x,
          dy: sliceConfig.y,
          accumTopHeight,
          effectiveWidth: sliceConfig.w,
        });
        accumTopHeight = accumBottomHeight;
      }
      sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
    }

    async function processSplitConfig() {
      if (!store.currentTask) return;

      const images = [];
      for (const url of store.currentTask.sliceUrls) {
        const image = await loadImage(
          filters.toCompleteUrlWithFileServer(store.setting.fileServer, url)
        );
        images.push(image);
      }

      const splitConfigPairs = store.setting.splitConfig
        .map((v, index, ary) => (index % 2 === 0 ? [v, ary[index + 1]] : false))
        .filter((v) => v) as unknown as Array<[number, number]>;

      const maxSplitConfig = Math.max(...store.setting.splitConfig);
      maxSliceWidth =
        Math.max(...images.map((v) => v.naturalWidth)) * maxSplitConfig;

      theFinalHeight =
        splitConfigPairs.length *
        images.reduce((acc, v) => (acc += v.naturalHeight), 0);

      let accumTopHeight = 0;
      let accumBottomHeight = 0;
      const tempSliceImagesWithTrackList = [] as Array<SliceImage>;
      for (const url of store.currentTask.sliceUrls) {
        for (const config of splitConfigPairs) {
          const indexInSliceUrls = store.currentTask.sliceUrls.indexOf(url) + 1;
          const image = images[indexInSliceUrls - 1];

          accumBottomHeight += image.naturalHeight;

          const dataUrl = getDataUrlForSplitConfig(
            image,
            config,
            maxSliceWidth,
            url
          );

          const trackLists = store.currentTask.questionList
            .map((q) => q.trackList)
            .reduce((acc, t) => {
              acc = acc.concat(t);
              return acc;
            }, [] as Array<Track>);
          const thisImageTrackList = trackLists.filter(
            (t) => t.offsetIndex === indexInSliceUrls
          );
          const thisImageTagList = (
            store.currentTask.specialTagList ?? []
          ).filter((t) => t.offsetIndex === indexInSliceUrls);
          const sliceImage = await getImageUsingDataUrl(dataUrl);
          tempSliceImagesWithTrackList.push({
            url: dataUrl,
            indexInSliceUrls: store.currentTask.sliceUrls.indexOf(url) + 1,
            trackList: thisImageTrackList.filter(
              (t) =>
                t.positionY >= accumTopHeight / theFinalHeight &&
                t.positionY < accumBottomHeight / theFinalHeight
            ),
            tagList: thisImageTagList.filter(
              (t) =>
                t.positionY >= accumTopHeight / theFinalHeight &&
                t.positionY < accumBottomHeight / theFinalHeight
            ),
            originalImage: image,
            sliceImage,
            dx: image.naturalWidth * config[0],
            dy: 0,
            accumTopHeight,
            effectiveWidth: image.naturalWidth * config[1],
          });
          accumTopHeight = accumBottomHeight;
        }
      }
      sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
    }
    const renderPaperAndMark = async () => {
      if (__lock) {
        if (store.currentTask?.studentId === __currentStudentId) {
          console.log("重复渲染，返回");
          return;
        }
        console.log("上个任务还未渲染完毕，稍等一秒再尝试渲染");
        await new Promise((res) => setTimeout(res, 1000));
        await renderPaperAndMark();
        return;
      }
      __lock = true;
      __currentStudentId = store.currentTask?.studentId ?? -1;
      sliceImagesWithTrackList.splice(0);

      if (!store.currentTask) {
        __lock = false;
        return;
      }

      try {
        rendering.value = true;
        if (hasSliceConfig()) {
          await processSliceConfig();
        } else {
          await processSplitConfig();
        }
      } catch (error) {
        sliceImagesWithTrackList.splice(0);
        console.log("render error ", error);
        // 图片加载出错，自动加载下一个任务
        emit("error");
      } finally {
        __lock = false;
        rendering.value = false;
      }
    };

    watchEffect(renderPaperAndMark);

    const answerPaperScale = computed(() => {
      // 放大、缩小不影响页面之前的滚动条定位
      let percentWidth = 0;
      let percentTop = 0;
      const container = document.querySelector(
        ".mark-body-container"
      ) as HTMLDivElement;
      if (container) {
        const { scrollLeft, scrollTop, scrollWidth, scrollHeight } = container;
        percentWidth = scrollLeft / scrollWidth;
        percentTop = scrollTop / scrollHeight;
      }

      addTimeout(() => {
        if (container) {
          const { scrollWidth, scrollHeight } = container;
          container.scrollTo({
            left: scrollWidth * percentWidth,
            top: scrollHeight * percentTop,
          });
        }
      }, 10);
      const scale = store.setting.uiSetting["answer.paper.scale"];
      return scale * 100 + "%";
    });

    return {
      dragContainer,
      store,
      rendering,
      sliceImagesWithTrackList,
      answerPaperScale,
    };
  },
});
</script>

<style scoped>
.mark-body-container {
  height: calc(100vh - 41px);
  overflow: scroll;
  background-size: 8px 8px;
  background-image: linear-gradient(to right, #e7e7e7 4px, transparent 4px),
    linear-gradient(to bottom, transparent 4px, #e7e7e7 4px);
}
.mark-body-container img {
  width: 100%;
}
.single-image-container {
  position: relative;
}
.image-seperator {
  border: 2px solid rgba(120, 120, 120, 0.1);
}
</style>
