const monitor = [];
function eachMonitor(p, f) {
  monitor.forEach((m) => {
    if (m[0] === p[0] && m[1] === p[1]) {
      f(m);
    }
  });
}
function isMonitored(p) {
  let result = false;
  eachMonitor(p, (m) => {
    result = true;
  });
  return result;
}


// 2D projection
function worldToLocal(pointA, pointB, pointC) {
  const vectorAB = [pointB[0] - pointA[0], pointB[1] - pointA[1]];
  const vectorAC = [pointC[0] - pointA[0], pointC[1] - pointA[1]];
  const dot1 = dot2D(vectorAB, vectorAC);
  const dot2 = dot2D(vectorAB, vectorAB);
  const localX = dot1 / dot2;
  const pointD = [pointA[0] + localX * vectorAB[0], pointA[1] + localX * vectorAB[1]];

  // localY = distance from pointC to pointD
  const vectorCD = [pointD[0] - pointC[0], pointD[1] - pointC[1]];
  let localY = Math.sqrt(dot2D(vectorCD, vectorCD));

  // if pointC is on the right side of vectorAB, localY is negative
  const cross = cross2D(vectorAB, vectorAC);
  if (0 < cross) {
      localY = -localY;
  }

  return [localX, localY];
}

function distPointFromSeg(pointA, pointB, pointC, lx, ly) {
  if (lx < 0) {
    const vectorAC = [pointC[0] - pointA[0], pointC[1] - pointA[1]];
    return Math.sqrt(dot2D(vectorAC, vectorAC));
  }
  if (1.0 < lx) {
    const vectorBC = [pointC[0] - pointB[0], pointC[1] - pointB[1]];
    return Math.sqrt(dot2D(vectorBC, vectorBC));
  }
  return Math.abs(ly);
}

function localToWorld([pointA, pointB], pointC) { // C is local coordinate
  const vectorAB = [pointB[0] - pointA[0], pointB[1] - pointA[1]];
  // pointD = pointA + C.x * vectorAB(垂線との交点)
  const pointD = [pointA[0] + pointC[0] * vectorAB[0], pointA[1] + pointC[0] * vectorAB[1]];
  const v = perpendicular2D(vectorAB);
  const vLength = Math.sqrt(dot2D(v, v));
  if (vLength < 0.0001) { return pointD; }

  const newLength = pointC[1];
  const factor = newLength / vLength;
  return [pointD[0] + factor * v[0], pointD[1] + factor * v[1]];
}

function makeEdgeSegments(pose) {
  return limbSeq.map((segment) => {
    const p0 = pose[segment[0]];
    const p1 = pose[segment[1]];
    return [p0, p1];
  });
}

function makeNormalizeEdgeSegments(edges) {
  return edges.map((edge) => normalize2D([edge[1][0] - edge[0][0], edge[1][1] - edge[0][1]]));
}

const fieldDepth = 5;
const FO0i = 0;
const FO0t = 1;
const FO0u = 2;
const FO0d = 3;

// c current
// p prev
// n next
// 0 primary
// 1 secondary
// e edge
// s segment
// i in
// o out
// t time
// l local
// q point
// m main
// d distance
// w weight

function calcJointRange(
  edgeSegments, normalizedEdgeSegments, 
  edgeSegmentsForSideDecision,
  ep, ec, m) {
  // parent, child
  const np = normalizedEdgeSegments[ep];
  const nc = normalizedEdgeSegments[ec];
  let pnp = perpendicular2D(np);
  let pnc = perpendicular2D(nc);
  let side = getSide(edgeSegmentsForSideDecision[ep][0], edgeSegmentsForSideDecision[ep][1], edgeSegmentsForSideDecision[ec][1], m);
  if (side < 0) {
    pnp = reverse2D(pnp);
    pnc = reverse2D(pnc);
  }
  return [pnp, pnc];
}

function calcJointRangeWithOrder(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments, e0, m, order) {
  if (order < 0) {
    const e1 = findPrevEdgeIndex(e0);
    if (e1 < 0) { return null; }
    const [pn1, pn0] = calcJointRange(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments,e1, e0, m);
    const oldPivot = oldEdgeSegments[e0][0];
    const newPivot = newEdgeSegments[e0][0];
    return [pn0, pn1, oldPivot, newPivot, e1];
  } else if (0 < order) {
    const e1 = findNextEdgeIndex(e0);
    if (e1 < 0) { return null; }
    const [pn0, pn1] = calcJointRange(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments, e0, e1, m);
    const oldPivot = oldEdgeSegments[e0][1];
    const newPivot = newEdgeSegments[e0][1];
    return [pn0, pn1, oldPivot, newPivot, e1];
  } else {
    return null;
  }
}

function buildWeightMap(size, pose) {
  const threshold = 512;

  const [w, h] = size;

  // この状態で、canvasに対して、  
  // 同じ大きさの3次元配列[x,y,z]を作成する
  // x,yは画像の2次元座標

  // 各Pixelに対して、
  // そのPixelに最も近いエッジのindexを[0]に、
  // そのときのエッジ座標系におけるtを[1]に記録する
  // どの骨格とも近くないものは[0]=255とする

  const field = new Float32Array(w * h * fieldDepth)
  function fieldIndex(x, y) {
    return (y * w + x) * fieldDepth;
  }
  
  const edgeSegments = makeEdgeSegments(pose);
  const normalizedEdgeSegments = makeNormalizeEdgeSegments(edgeSegments);

  // 各Pixelに対して、一番近いエッジを探す
  // またそのエッジ座標系におけるtを保存する
  for (let y = 0 ; y < h ; y++) {
    for (let x = 0 ; x < w ; x++) {
      const m = [x, y];
      let minDist = threshold;
      const fidx = fieldIndex(x, y);
      field[fidx+FO0i] = -1;
      for (let i = 0 ; i < limbSeq.length ; i++) {
        // 線分から点への距離を計算
        const s = edgeSegments[i];
        const lq = worldToLocal(s[0], s[1], m);
        const dist = distPointFromSeg(s[0], s[1], m, lq[0], lq[1]);
        if (dist < minDist) {
          minDist = dist;
          field[fidx+FO0i] = i;
          field[fidx+FO0t] = lq[0];
          field[fidx+FO0u] = lq[1];
          field[fidx+FO0d] = dist;
        }
      }
    }
  }

  return field;
}

function renderField(canvas2, size, field) {
  function blend(c0, w0, c1, w1) {
    const r = (c0[0] * w0 + c1[0] * w1);
    const g = (c0[1] * w0 + c1[1] * w1);
    const b = (c0[2] * w0 + c1[2] * w1);
    const a = (c0[3] * w0 + c1[3] * w1);
    return [r, g, b, a];
  }

  const [w, h] = size;
  function fieldIndex(x, y) {
    return (y * w + x) * fieldDepth;
  }

  canvas2.width = w;
  canvas2.height = h;
  const ctx2 = canvas2.getContext('2d');

  bindings = ctx2.createImageData(w, h);
  const data = bindings.data;
  for (let y = 0 ; y < h ; y++) {
    for (let x = 0 ; x < w ; x++) {
      const fidx = fieldIndex(x, y);
      const oi = (y * w + x) * 4;

      let c = [0, 0, 0, 64];

      const e0 = field[fidx+FO0i];
      if (0 <= e0) { 
        c = colors[e0];
        c[3] = 255;
        // c[3] = 255 - field[fidx+FO0d] * 4;
      }
      data[oi + 0] = c[0];
      data[oi + 1] = c[1];
      data[oi + 2] = c[2];
      data[oi + 3] = c[3];
    }
  }
  ctx2.putImageData(bindings, 0, 0);

  return field;
}

function makeImageDataFromPicture(canvas3, size, picture) {
  const [w, h] = size;
  canvas3.width = w;
  canvas3.height = h;

  const ctx3 = canvas3.getContext("2d");
  // ctx3.drawImage(picture, sampleOffset[0], sampleOffset[1]);
  ctx3.drawImage(picture, 0, 0);

  return new ImageData(new Uint8ClampedArray(ctx3.getImageData(0, 0, w, h).data), w, h);
}

function animatePicture(canvas4, size, oldPose, newPose, srcImageData, initialPoseField) {
  const srcData = srcImageData.data;

  const [w, h] = size;

  const ctx4 = canvas4.getContext("2d");
  canvas4.width = w;
  canvas4.height = h;
  const dstCtx = ctx4;
  const dstImageData = dstCtx.createImageData(w, h);
  const dstData = dstImageData.data;

  const field = buildWeightMap(size, newPose);
  const canvas2 = document.getElementById("canvas2");
  // renderField(canvas2, size, field);
  drawBodyPoseTo(canvas2.getContext("2d"), [newPose]);

  function fieldIndex(x, y) {
    return (y * w + x) * fieldDepth;
  }

  const oldEdgeSegments = makeEdgeSegments(oldPose);
  const oldNormalizedEdgeSegments = makeNormalizeEdgeSegments(oldEdgeSegments);
  const newEdgeSegments = makeEdgeSegments(newPose);

  function oldEdgePoint(edgeIndex, p) {
    const v = oldEdgeSegments[edgeIndex];
    return localToWorld(v, p);
  }

  function newEdgePoint(edgeIndex, p) {
    const v = newEdgeSegments[edgeIndex];
    return localToWorld(v, p);
  }

  debugLines = [];

  for (let y = 0 ; y < h ; y++) {
    for (let x = 0 ; x < w ; x++) {
      // 各点に対して、fieldから近隣エッジ座標系でのローカル座標を取得する
      const oi = (y * w + x) * 4;

      const fidx = fieldIndex(x, y);
      const e0 = field[fidx+FO0i];
      if (e0 < 0) { 
        dstData[oi + 0] = 0;
        dstData[oi + 1] = 0;
        dstData[oi + 2] = 0;
        dstData[oi + 3] = 255;
        continue; 
      }

      const lq = [field[fidx+FO0t], field[fidx+FO0u]];
      let e1 = -1;
      let order = 0; // 1 == forward, -1 == backward
      if (lq[0] < 0.5) {
        e1 = findPrevEdgeIndex(e0);
      } else if (0.5 <= lq[0]) {
        e1 = findNextEdgeIndex(e0);
      }
      if (0 <= e1) {
        if (lq[0] < 0) {
          order = -1;
        } else if (1 < lq[0]) {
          order = 1;
        }
      }

      let iq = oldEdgePoint(e0, lq);

      if (0 != order) {
        // サイド判定は新ポーズで行う
        const mNew = [x,y];
        const jointRange = calcJointRangeWithOrder(
          oldEdgeSegments, oldNormalizedEdgeSegments, 
          newEdgeSegments,
          e0, mNew, order);
        const [pn0, pn1, oldPivot, newPivot, _] = jointRange;

        const pivotMNew = [x - newPivot[0], y - newPivot[1]];
        const [w0, w1]= [angleBetween(pn0, pivotMNew), angleBetween(pivotMNew, pn1)];

        let d0 = field[fidx+FO0d];
        let mt = slerp2D(pn0, pn1, w0 / (w0 + w1));
        iq = [oldPivot[0] + mt[0] * d0, oldPivot[1] + mt[1] * d0];
        eachMonitor([x,y], () => {
          console.log('animate', x, y, ix, iy, e1, w0, w1, pn0, pn1);
          debugLines.push([oldPivot, pn0]);
          debugLines.push([oldPivot, pn1]);
        });
      }

      iq = [Math.round(iq[0]), Math.round(iq[1])];

      var initialOwner = initialPoseField[fieldIndex(iq[0], iq[1]) + FO0i];
      if (0 <= initialOwner && initialOwner != e0 && initialOwner != e1) {
        // ほかの領土
        dstData[oi + 0] = 0;
        dstData[oi + 1] = 0;
        dstData[oi + 2] = 0;
        dstData[oi + 3] = 0;
      } else {
        // canvas3の[ix,iy]の色を取得して、canvas4の[x,y]に描画する
        const si = (iq[1] * w + iq[0]) * 4;
        dstData[oi + 0] = srcData[si + 0];
        dstData[oi + 1] = srcData[si + 1];
        dstData[oi + 2] = srcData[si + 2];
        dstData[oi + 3] = srcData[si + 3];
      }
    }
  }

  dstCtx.putImageData(dstImageData, 0, 0);
}

function handleMicroscopeMouseMove(e) {
  const [x,y] = mouseCursor;

  const imageData = ctx.getImageData(x - 2, y - 2, 5, 5);
  const data = imageData.data;

  const microscope = document.getElementById('microscope');
  const ctx2 = microscope.getContext('2d');
  ctx2.clearRect(0, 0, microscope.width, microscope.height);
  for (let i = 0 ; i < 5 ; i++) {
    for (let j = 0 ; j < 5 ; j++) {
      const idx = (i * 5 + j) * 4;
      ctx2.fillStyle = `rgba(${data[idx]}, ${data[idx+1]}, ${data[idx+2]}, ${data[idx+3]})`;
      ctx2.fillRect(j * 10, i * 10, 10, 10);
    }
  }
}

function initMicroscope() {
  // canvas.addEventListener('mousemove', handleMicroscopeMouseMove);
  // poseData[0][6] = [329, 148];
  // poseData[0][7] = [329, 194];
}