/*
 * main.js
 * - Startet die Kamera
 * - Lässt MediaPipe Pose (Google) ein Skelett aus dem Kamerabild extrahieren
 * - Zeichnet ein Mandelbrot-Fraktal hinter den Kopf der Person
 * - Zeichnet das Skelett als Overlay darüber
 * - Speist Pose-Daten in das Fraktal-Modul (webgl.js) ein
 */

function mulberry32(seed) {
  // Seedable Random Generator
    return function() {
      let t = seed += 0x6D2B79F5;
      t = Math.imul(t ^ (t >>> 15), t | 1);
      t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    }
}



// Hilfsfunktion: Versucht einen Parameter als Zahl zu parsen, nimmt sonst den Default
function getQueryParamNumber( key, defaultValue) {
    const params = new URLSearchParams(window.location.search);

    const val = params.get(key);
    if (val === null || val.trim() === '' || Number.isNaN(Number(val))) {
      console.warn("using default value",key,defaultValue)
        return defaultValue;
    }
      console.warn("using query value",key,Number(val))
    return Number(val);
}

(function () {
  "use strict";
  const MIN_VISIBILITY = 0.6;
  const video = document.getElementById("video");
  const overlay = document.getElementById("overlay");
  const ctx = overlay.getContext("2d");
  const statusEl = document.getElementById("status");
  const toggleCamBtn = document.getElementById("toggleCam");
  const toggleSkelBtn = document.getElementById("toggleSkel");

  let lastWrist = null; // für Bewegungs-/Glow-Berechnung

  function setStatus(text) {
    statusEl.textContent = text;
  }

  function resizeOverlay() {
    overlay.width = window.innerWidth;
    overlay.height = window.innerHeight;
  }
  window.addEventListener("resize", resizeOverlay);
  resizeOverlay();

  // Kamera-Sichtbarkeit umschalten
  toggleCamBtn.addEventListener("click", function () {
    const visible = video.classList.toggle("visible");
    toggleCamBtn.textContent = visible
      ? "Kamera ausblenden"
      : "Kamera anzeigen";
  });

  // Skelett-Sichtbarkeit umschalten
  toggleSkelBtn.addEventListener("click", function () {
    const visible = overlay.classList.toggle("invisible");
    toggleSkelBtn.textContent = visible
      ? "Skelett ausblenden"
      : "Skelett anzeigen";
  });

  // Qualität / Render-Auflösung umschalten
  const qualitySel = document.getElementById("quality");
  qualitySel.addEventListener("change", function () {
    if (window.Fractal && window.Fractal.setQuality) {
      window.Fractal.setQuality(qualitySel.value);
    }
  });

  // Hilfsfunktionen für 3D-Vektorarithmetik
  const vecSub = (v1, v2) => [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]];
  const vecDot = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
  const vecLength = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);

  const vecNormalize = (v) => {
    const len = vecLength(v);
    return len === 0 ? [0, 0, 0] : [v[0] / len, v[1] / len, v[2] / len];
  };

  const vecCross = (v1, v2) => [
    v1[1] * v2[2] - v1[2] * v2[1],
    v1[2] * v2[0] - v1[0] * v2[2],
    v1[0] * v2[1] - v1[1] * v2[0],
  ];

  let lastTime = performance.now();
  let t = 0;

  function getDeltTimeDt(currentTime) {
    let dt = (currentTime - lastTime) / 1000;
    lastTime = currentTime;
    if (dt > 0.1) dt = 0.1;
    return dt;
  }

  function berechneRotationsWinkel(achse, vReferenz, vAktuell) {
    const aN = vecNormalize(achse);
    const dotRef = vecDot(vReferenz, aN);
    const vRefProj = vecNormalize([
      vReferenz[0] - dotRef * aN[0],
      vReferenz[1] - dotRef * aN[1],
      vReferenz[2] - dotRef * aN[2],
    ]);

    const dotAkt = vecDot(vAktuell, aN);
    const vAktProj = vecNormalize([
      vAktuell[0] - dotAkt * aN[0],
      vAktuell[1] - dotAkt * aN[1],
      vAktuell[2] - dotAkt * aN[2],
    ]);

    const cross = vecCross(vRefProj, vAktProj);
    const dot = vecDot(vRefProj, vAktProj);
    const sign = vecDot(cross, aN);
    const winkelRad = Math.atan2(sign, dot);

    return winkelRad * (180 / Math.PI);
  }

  /**
   * Berechnet den Mittelpunkt des Kopfes und die geschätzte Kopfgröße
   */
  function getHeadTransform(lm) {
    const nose = lm[POSE_LANDMARKS.NOSE];
    const leftEar = lm[POSE_LANDMARKS.LEFT_EAR];
    const rightEar = lm[POSE_LANDMARKS.RIGHT_EAR];

    if (!nose || nose.visibility < MIN_VISIBILITY) {
      return null;
    }

    // Kopfzentrum X/Y in normierten Bildkoordinaten (0 bis 1)
    let headX = nose.x;
    let headY = nose.y;
    let headScale = 0.2; // Fallback-Skalierung

    if (
      leftEar &&
      rightEar &&
      leftEar.visibility >= MIN_VISIBILITY &&
      rightEar.visibility >= MIN_VISIBILITY
    ) {
      headX = (leftEar.x + rightEar.x) / 2;
      headY = (leftEar.y + rightEar.y) / 2;

      // Abstand zwischen den Ohren bestimmt die Skalierung (Zoom/Größe)
      const dx = leftEar.x - rightEar.x;
      const dy = leftEar.y - rightEar.y;
      headScale = Math.hypot(dx, dy) * 1.5;
    }

    return {
      x: headX * overlay.width,
      y: headY * overlay.height,
      normX: headX,
      normY: headY,
      scale: headScale,
    };
  }

  /**
   * Zeichnet ein Standard-Mandelbrot (z^2 + c) als Halo/Aura direkt hinter den Kopf auf den 2D Canvas
   */
  function drawMandelbrotBehindHead(head) {
    if (!head) return;

    ctx.save();
    ctx.beginPath();
    
    // Leuchtende Aura hinter dem Kopf als optisches Backup
    const radius = Math.max(40, head.scale * overlay.width * 0.8);
    const gradient = ctx.createRadialGradient(
      head.x,
      head.y,
      radius * 0.1,
      head.x,
      head.y,
      radius
    );

    gradient.addColorStop(0, "rgba(255, 0, 128, 0.8)");
    gradient.addColorStop(0.5, "rgba(0, 230, 255, 0.4)");
    gradient.addColorStop(1, "rgba(0, 0, 0, 0)");

    ctx.fillStyle = gradient;
    ctx.arc(head.x, head.y, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  // --- Pose-Ergebnisse verarbeiten ---
  function onResults(results) {
    const dt = getDeltTimeDt(performance.now());
    ctx.clearRect(0, 0, overlay.width, overlay.height);

    if (!results.poseLandmarks) {
      setStatus("Keine Person erkannt – stell dich vor die Kamera.");
      return;
    }

    setStatus("Skelett erkannt.");

    function unifyMultipersons(landmarks) {
      if (Array.isArray(landmarks[0])) {       
        return landmarks[0];
      } else {
        return landmarks;
      }
    }

    const lm = unifyMultipersons(results.poseLandmarks);

    // 1. Kopf-Position ermitteln
    const head = getHeadTransform(lm);

    // 2. Mandelbrot-Aura / Hintergrund ERST zeichnen (liegt damit HINTER dem Skelett)
    if (head) {
      drawMandelbrotBehindHead(head);
    }

    // 3. Skelett ZUVERLÄSSIG DARÜBER zeichnen
    if (window.drawConnectors && window.POSE_CONNECTIONS) {
      window.drawConnectors(ctx, lm, window.POSE_CONNECTIONS, {
        color: "rgba(0, 255, 200, 0.9)",
        lineWidth: 4,
      });
    }
    if (window.drawLandmarks) {
      window.drawLandmarks(ctx, lm, {
        color: "rgba(255, 80, 180, 0.95)",
        fillColor: "rgba(255, 255, 255, 0.9)",
        lineWidth: 2,
        radius: 4,
      });
    }

    // 4. Fraktal-Parameter inklusive der Kopf-Daten an WebGL übergeben
    updateFractalFromPose(lm, dt, head);
  }

  // --- Vektor-Helfer (3D) ---
  function vec(p) {
    return [p.x, p.y, p.z !== undefined ? p.z : 0];
  }
  function sub(a, b) {
    return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
  }
  function length(a) {
    return Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
  }
 
  const connectionTable = [
/**
 *   // Full Body
    [POSE_LANDMARKS.RIGHT_ELBOW, POSE_LANDMARKS.RIGHT_WRIST],
    [POSE_LANDMARKS.LEFT_ELBOW, POSE_LANDMARKS.LEFT_WRIST],
    [POSE_LANDMARKS.LEFT_EAR, POSE_LANDMARKS.RIGHT_EAR],
    [POSE_LANDMARKS.RIGHT_SHOULDER, POSE_LANDMARKS.LEFT_SHOULDER],
    [POSE_LANDMARKS.RIGHT_HIP, POSE_LANDMARKS.LEFT_HIP],
    [POSE_LANDMARKS.LEFT_KNEE, POSE_LANDMARKS.LEFT_ANKLE],
    [POSE_LANDMARKS.RIGHT_KNEE, POSE_LANDMARKS.RIGHT_ANKLE],
    [POSE_LANDMARKS.RIGHT_KNEE, POSE_LANDMARKS.RIGHT_ANKLE], 

  */
    // Upper Body
    [POSE_LANDMARKS.LEFT_WRIST, POSE_LANDMARKS.RIGHT_WRIST],

    [POSE_LANDMARKS.LEFT_EAR, POSE_LANDMARKS.RIGHT_EAR],
    [POSE_LANDMARKS.LEFT_SHOULDER, POSE_LANDMARKS.RIGHT_SHOULDER],

    [POSE_LANDMARKS.LEFT_HIP, POSE_LANDMARKS.RIGHT_HIP],

    // More deep and more irrelevant angles, 
    [POSE_LANDMARKS.LEFT_SHOULDER, POSE_LANDMARKS.LEFT_ELBOW],
    [POSE_LANDMARKS.RIGHT_SHOULDER, POSE_LANDMARKS.RIGHT_ELBOW],
    
    [POSE_LANDMARKS.RIGHT_WRIST, POSE_LANDMARKS.RIGHT_INDEX], 
    [POSE_LANDMARKS.LEFT_WRIST, POSE_LANDMARKS.LEFT_INDEX],

  ];
const lastAxises={}
  function getAxisForNodes(lm, index1, index2) {
    const p1 = lm[index1];
    const p2 = lm[index2];

    if (p1 && p2 && p1.visibility && p2.visibility) {
      if (p1.visibility < MIN_VISIBILITY || p2.visibility < MIN_VISIBILITY) {
        return lastAxises[`${index1}-${index2}`]??[1, 0, 0];
      }
      const v = sub(vec(p1), vec(p2));
      const len = Math.hypot(v[0], v[1], v[2]);
      if (len === 0) return lastAxises[`${index1}-${index2}`]??[1, 0, 0];
      const result= [v[0] / len, v[1] / len, v[2] / len];
lastAxises[`${index1}-${index2}`]=result;
      return result
    } else {
      return lastAxises[`${index1}-${index2}`]??[1, 0, 0];
    }
  }
const random=mulberry32(getQueryParamNumber("seed",Math.floor(Math.random()*Number.MAX_SAFE_INTEGER)))
  const additions = Array.from(
    { length: 8 },
    () => random() * Math.PI * 0.1
  );
  const angles = Array.from({ length:8 }, () => 0.0);
  let matchTime = null;
  let normalizedDist;

  function handleAnglesOnContactPlus(lm, n1, n2, treshold = 0.1, dt) {
    const p1 = vec(lm[n1]);
    const p2 = vec(lm[n2]);
    const dist = length(sub(p1, p2));

    const m1Layer = document.getElementById("message1");
    const m2Layer = document.getElementById("message2");

    if (dist < treshold) {
      if (!matchTime) {
        matchTime = Date.now();
      }
      normalizedDist = (Date.now() - matchTime) / 3000;

      if (normalizedDist > 1) {
        if (m1Layer) {
          m1Layer.classList.remove("visible");
          m1Layer.classList.add("invisible");
        }
        if (m2Layer) {
          m2Layer.classList.add("visible");
          m2Layer.classList.remove("invisible");
        }

        const titleLayer = document.getElementById("overlay_title");
        titleLayer.classList.add("invisible");
        const averageY = (lm[n1].y + lm[n2].y) / 2;
        const direction = averageY < 0.5 ? 1.5 : -1.5;

        for (let i = 0; i < 32; i++) {
          angles[i] = angles[i] + additions[i] * direction * dt * 2.0;
        }
      } else if (normalizedDist > 0.5) {
        if (m1Layer) {
          m1Layer.classList.remove("invisible");
          m1Layer.classList.add("visible");
        }
        if (m2Layer) {
          m2Layer.classList.add("invisible");
          m2Layer.classList.remove("visible");
        }
      }
    } else {
      matchTime = null;
      if (m1Layer) {
        m1Layer.classList.remove("visible");
        m1Layer.classList.add("invisible");
      }
      if (m2Layer) {
        m2Layer.classList.remove("visible");
        m2Layer.classList.add("invisible");
      }
    }
  }

  // --- Pose -> Fraktal-Parameter ---
  function updateFractalFromPose(lm, dt, head) {
    handleAnglesOnContactPlus(
      lm,
      POSE_LANDMARKS.RIGHT_WRIST,
      POSE_LANDMARKS.LEFT_WRIST,
      0.25,
      dt
    );

    const params = {
      fractalType: "mandelbrot_z2", // Standard z^2 Power 2
      headX: head ? head.normX : 0.5, // Normierte X-Position des Kopfes (0..1)
      headY: head ? head.normY : 0.5, // Normierte Y-Position des Kopfes (0..1)
      headScale: head ? head.scale : 0.2, // Kopf-Skalierung für Zoom
      angle: 0.23,
      rotX: 1,
      rotY: 1,
      glow: 1,
      angles: angles,
      axisses: [],
    };

    connectionTable.forEach((connection, index) => {
      params.axisses[index] = getAxisForNodes(
        lm,
        connection[0],
        connection[1]
      );
    });

    if (window.Fractal) {
      window.Fractal.setParams(params);
    }
  }
  
  document.addEventListener('keydown', function(event) {
      // Prüft, ob die Leertaste gedrückt wurde
      if (event.code === 'Space') {
        // Verhindert das automatische Herunterscrollen der Seite
        event.preventDefault(); 
        
        const div = document.getElementById('hud');
        div.classList.toggle('invisible');
      }
    });
  
  
    // --- Initialisierung ---
  function start() {
    if (!window.Fractal || !window.Fractal.init()) {
      setStatus("WebGL could not be initialised.");
      return;
    }

    window.Fractal.setQuality("med");

    if (typeof Pose === "undefined") {
      setStatus(
        "MediaPipe Pose could not be loaded."
      );
      return;
    }

    const pose = new Pose({
      locateFile: function (file) {
        return "https://cdn.jsdelivr.net/npm/@mediapipe/pose/" + file;
      },
    });

    pose.setOptions({
      modelComplexity: 1,
      smoothLandmarks: true,
      enableSegmentation: false,
      minDetectionConfidence: 0.6,
      minTrackingConfidence: 0.4,
    });

    pose.onResults(onResults);

    if (typeof Camera === "undefined") {
      setStatus("MediaPipe Camera Utils could not be loaded.");
      return;
    }

    const camera = new Camera(video, {
      onFrame: async function () {
        await pose.send({ image: video });
      },
      width: 1920/2,
      height: 1080/2,
    });

    camera
      .start()
      .then(function () {
        setStatus("Camera running – position yourself in front of it.");
      })
      .catch(function (err) {
        setStatus(
          "Camera denies acces or is not available: " + err.message
        );
      });
  }

  window.addEventListener("load", start);
})();