<script setup lang="ts">
import AtlasRenderArgument from "../entities/AtlasRenderArgument.ts";
import {computed, onMounted, onUnmounted, ref} from "vue";
import {
  AnimationState, AnimationStateData,
  AssetManager, AtlasAttachmentLoader,
  ManagedWebGLRenderingContext, Matrix4, Physics,
  PolygonBatcher,
  Shader, ShapeRenderer, Skeleton, SkeletonDebugRenderer, SkeletonJson,
  SkeletonRenderer, Vector2
} from "@esotericsoftware/spine-webgl";
import {RenderArgument} from "../entities/RenderArgument.ts";

type Props = {
  atlas: AtlasRenderArgument[],
  premultipliedAlpha: boolean,
  debug: boolean
}


const props = defineProps<Props>();
const emit = defineEmits(['edit']);
const mainCanvas = ref<HTMLCanvasElement>();
let mounted = false;


const webGlCtx = computed<WebGLRenderingContext>(() => mainCanvas.value?.getContext("webgl") as WebGLRenderingContext);
const managedCtx = computed(() => new ManagedWebGLRenderingContext(webGlCtx.value));
const assetManager = computed<AssetManager>(() => new AssetManager(managedCtx.value));
const shader = computed<Shader>(() => Shader.newTwoColoredTextured(managedCtx.value));
const batcher = computed<PolygonBatcher>(() => new PolygonBatcher(managedCtx.value));
const renderer = computed<SkeletonRenderer>(() => new SkeletonRenderer(managedCtx.value));
const rendererArguments = computed<RenderArgument[]>(() => {
  return props.atlas.map((argument): RenderArgument => {
    const attachmentLoader = new AtlasAttachmentLoader(assetManager.value.require(argument.texture));
    const skeletonLoader = new SkeletonJson(attachmentLoader);
    skeletonLoader.scale = argument.scale;
    const skeletonData = skeletonLoader.readSkeletonData(assetManager.value.require(argument.skel));
    const skeleton = new Skeleton(skeletonData);
    skeleton.x += argument.x;
    skeleton.y += argument.y;
    skeleton.setToSetupPose();
    skeleton.updateWorldTransform(Physics.update);
    let offset = new Vector2();
    let size = new Vector2();
    skeleton.getBounds(offset, size, []);
    const setupBound: Bound = {width: size.x, height: size.y, x: offset.x, y: offset.y};
    const animationStateData = new AnimationStateData(skeletonData);
    const animationState = new AnimationState(animationStateData);
    animationState.setAnimation(0, argument.animation, true);
    return {skel: skeleton, setupBound, state: animationState, edit: !!argument.edit, originalRender: argument};
  })
})
let timer = Date.now() / 1000;
let mvp = new Matrix4();

// debugger
const debugRenderer = computed<SkeletonDebugRenderer>(() => {
  const result = new SkeletonDebugRenderer(managedCtx.value);
  result.drawRegionAttachments = true;
  result.drawBoundingBoxes = true;
  result.drawMeshHull = true;
  result.drawMeshTriangles = true;
  result.drawPaths = true;
  return result;
});
const debugShader = computed<Shader>(() => Shader.newColored(managedCtx.value));
const shape = computed<ShapeRenderer>(() => new ShapeRenderer(managedCtx.value));


const loadingResources = async () => {
  props.atlas.forEach((argument) => {
    for (let originalImageName in argument.textureImage) {
      const blobUrl = argument.textureImage[originalImageName];
      const filenameSplit = argument.texture.split("/");
      const filename = filenameSplit[filenameSplit.length - 1];
      const defaultUrl = argument.texture.replace(filename, originalImageName);
      console.log(defaultUrl, filename, originalImageName)
      assetManager.value.setRawDataURI(defaultUrl, blobUrl);
    }
    assetManager.value.loadJson(argument.skel);
    assetManager.value.loadTextureAtlas(argument.texture);
  });
  await assetManager.value.loadAll();
};
const initializationRender = () => {
  if (!mainCanvas.value){
    console.error("浏览器不支持canvas");
    return;
  }
  mainCanvas.value.getContext("webgl");
}
let viewport: {width: number, height: number} = {width: 0, height: 0};
let selectSkel: {skel: Skeleton, obj: RenderArgument} | null = null;
let isDragging: boolean = false;
const resize = () => {
  if (!mainCanvas.value){
    return;
  }
  const w = mainCanvas.value.clientWidth;
  const h = mainCanvas.value.clientHeight;
  if (mainCanvas.value.width != w || mainCanvas.value.height != h) {
    mainCanvas.value.width = w;
    mainCanvas.value.height = h;
  }
  const width = w * 2;
  const height = h * 2;

  mvp.ortho2d(-(width / 2), -(height / 2), width, height);
  viewport.width = w;
  viewport.height = h;
  managedCtx.value.gl.viewport(0, 0, w, h);
}
const createShaderProgram = (r: number, g: number, b: number) => {
  const vsSource = `
        attribute vec2 a_position;
        void main() {
            gl_Position = vec4(a_position, 0.0, 1.0);
        }
    `;
  const fsSource = `
        void main() {
            gl_FragColor = vec4(${r}, ${g}, ${b}, 1.0);
        }
    `;
  const vertexShader = webGlCtx.value.createShader(webGlCtx.value.VERTEX_SHADER) as WebGLShader;
  webGlCtx.value.shaderSource(vertexShader, vsSource);
  webGlCtx.value.compileShader(vertexShader);

  const fragmentShader = webGlCtx.value.createShader(webGlCtx.value.FRAGMENT_SHADER) as WebGLShader;
  webGlCtx.value.shaderSource(fragmentShader, fsSource);
  webGlCtx.value.compileShader(fragmentShader);

  const shaderProgram = webGlCtx.value.createProgram() as WebGLProgram;
  webGlCtx.value.attachShader(shaderProgram, vertexShader);
  webGlCtx.value.attachShader(shaderProgram, fragmentShader);
  webGlCtx.value.linkProgram(shaderProgram);

  if (!webGlCtx.value.getProgramParameter(shaderProgram, webGlCtx.value.LINK_STATUS)) {
    alert('Unable to initialize the shader program: ' + webGlCtx.value.getProgramInfoLog(shaderProgram));
    return null;
  }

  webGlCtx.value.useProgram(shaderProgram);

  return shaderProgram;
};
const initBuffers = (x: number, y: number, width: number, height: number, r: number, g: number, b: number) => {
  // 创建一个缓冲区对象
  const vertexBuffer = webGlCtx.value.createBuffer();
  // 绑定缓冲区
  webGlCtx.value.bindBuffer(webGlCtx.value.ARRAY_BUFFER, vertexBuffer);
  const relativeX = x / viewport.width;
  const relativeY = y / viewport.height;
  const leftX = relativeX;
  const topY = relativeY;
  const rightX = relativeX + width / viewport.width;
  const bottomY = relativeY + height / viewport.height;
  // 定义四个顶点的位置
  const vertices = new Float32Array([
    leftX,  topY,   // 第一个顶点（x, y）
    rightX, topY,   // 第二个顶点
    rightX,  bottomY,   // 第三个顶点
    leftX,  bottomY    // 第四个顶点
  ]);

  // 将顶点数据传到缓冲区
  webGlCtx.value.bufferData(webGlCtx.value.ARRAY_BUFFER, vertices, webGlCtx.value.STATIC_DRAW);
  const shaderProgram = createShaderProgram(r, g, b) as WebGLProgram;
  // 获取顶点着色器的位置属性
  const positionAttribLocation = webGlCtx.value.getAttribLocation(shaderProgram, 'a_position');
  webGlCtx.value.enableVertexAttribArray(positionAttribLocation);
  webGlCtx.value.vertexAttribPointer(positionAttribLocation, 2, webGlCtx.value.FLOAT, false, 0, 0);

  return vertexBuffer;
};
const render = () => {
  if (!mainCanvas.value){
    console.error("浏览器不支持canvas");
    return;
  }
  if (!mounted){
    return;
  }
  resize();
  let now = Date.now() / 1000;
  let delta = now - timer;
  timer = now;
  webGlCtx.value.clearColor(0, 0, 0, 0);
  webGlCtx.value.clear(webGlCtx.value.COLOR_BUFFER_BIT);

  for (let renderObj of rendererArguments.value) {
    let skeleton = renderObj.skel;
    let state = renderObj.state;
    // let bounds = renderObj.setupBound;
    state.update(delta);
    state.apply(skeleton);
    skeleton.updateWorldTransform(Physics.update);
    if (renderObj.edit){
      let offset = new Vector2();
      let size = new Vector2();
      skeleton.getBounds(offset, size, []);
      renderObj.setupBound = {width: size.x, height: size.y, x: offset.x, y: offset.y};
    }

    shader.value.bind();
    shader.value.setUniformi(Shader.SAMPLER, 0);
    shader.value.setUniform4x4f(Shader.MVP_MATRIX, mvp.values);
    batcher.value.begin(shader.value);
    renderer.value.premultipliedAlpha = props.premultipliedAlpha;
    renderer.value.draw(batcher.value, skeleton);
    batcher.value.end();
    shader.value.unbind();
    if (props.debug) {
      debugShader.value.bind();
      debugShader.value.setUniform4x4f(Shader.MVP_MATRIX, mvp.values);
      debugRenderer.value.premultipliedAlpha = props.premultipliedAlpha;
      shape.value.begin(debugShader.value);
      debugRenderer.value.draw(shape.value, skeleton);
      shape.value.end();
      debugShader.value.unbind();
    }
  }
  let hasTouched = false;
  selectSkel = null;
  for (let renderObj of rendererArguments.value) {
    if (!renderObj.edit){
      continue;
    }
    const xMin = Math.min(renderObj.setupBound.x + renderObj.setupBound.width, renderObj.setupBound.x);
    const yMin = Math.min(renderObj.setupBound.y + renderObj.setupBound.height, renderObj.setupBound.y);
    const xMax = Math.max(renderObj.setupBound.x + renderObj.setupBound.width, renderObj.setupBound.x);
    const yMax = Math.max(renderObj.setupBound.y + renderObj.setupBound.height, renderObj.setupBound.y);
    if (mousePosition.x > xMin && mousePosition.x < xMax && mousePosition.y > yMin && mousePosition.y < yMax) {
      if (!hasTouched){
        selectSkel = {skel: renderObj.skel, obj: renderObj};
        webGlCtx.value.bindBuffer(webGlCtx.value.ARRAY_BUFFER, initBuffers(renderObj.setupBound.x,renderObj.setupBound.y,renderObj.setupBound.width,renderObj.setupBound.height, 0, 0, 0));
        webGlCtx.value.drawArrays(webGlCtx.value.LINE_LOOP, 0, 4);
      }
    }
  }
  requestAnimationFrame(render);
}
onMounted(async () => {
  if (!mainCanvas.value){
    return;
  }
  mainCanvas.value.addEventListener("mousemove", mouseMove);
  mainCanvas.value.addEventListener("mousedown", mouseDown);
  mainCanvas.value.addEventListener("mouseup", mouseUp);
  window.addEventListener("wheel", wheel, {passive: false});
  initializationRender()
  await loadingResources();
  mounted = true;
  requestAnimationFrame(render);
})
onUnmounted(() => {
  mounted = false;
});
const mousePosition = new Vector2(0,0);
const mouseMove = (evt: MouseEvent) => {
  if (viewport.height === 0 || viewport.width === 0){
    return;
  }
  if (!mainCanvas.value){
    return;
  }
  mousePosition.x = (evt.offsetX - mainCanvas.value.clientWidth / 2) * 2;
  mousePosition.y = (-(evt.offsetY - mainCanvas.value.clientHeight / 2)) * 2;
  if (isDragging){
    const mX = evt.movementX;
    const mY = evt.movementY;
    if (selectSkel){
      selectSkel.skel.x += mX * 2;
      selectSkel.skel.y -= mY * 2;
      emit("edit", selectSkel);
    }
  }
}
const mouseDown = () => {
  isDragging = true;
}
const mouseUp = () => {
  isDragging = false;
}
const wheel = (evt: WheelEvent) => {
  if (evt.ctrlKey){
    evt.preventDefault();
    evt.stopPropagation();
    if (selectSkel){
      const deltaScale = -evt.deltaY / 1000;
      selectSkel.skel.scaleX += deltaScale;
      if (selectSkel.skel.scaleX < 0.1){
        selectSkel.skel.scaleX = 0.1;
      }
      if (selectSkel.skel.scaleX > 10){
        selectSkel.skel.scaleX = 10;
      }
      selectSkel.skel.scaleY += deltaScale;
      if (selectSkel.skel.scaleY < 0.1){
        selectSkel.skel.scaleY = 0.1;
      }
      if (selectSkel.skel.scaleY > 10){
        selectSkel.skel.scaleY = 10;
      }
      emit("edit", selectSkel);
    }
  }
}
</script>

<template>
  <canvas id="mainCanvas" ref="mainCanvas" v-once></canvas>
</template>

<style scoped>
</style>