const mpHands = window;
const drawingUtils = window;
const controls = window;
const controls3d = window;



async function loadModel() {
    const model = await tf.loadLayersModel('./models/model.json');
    return model;
}

async function makePrediction(model, data) {
    // console.log(model)
    const prediction = model.predict(data);
    const result = await prediction.data();
    return result;
}

function processResult(result) {
    const squeezed = tf.squeeze(result);
    const argmax = squeezed.argMax().dataSync()[0];
    return argmax;
}
const model = await loadModel();



function pre_process_landmark(landmarks) {
    let temp_landmark_list = []

    let base_x = landmarks[0].x
    let base_y = landmarks[0].y
    for (let index = 0; index < landmarks.length; index++) {
        temp_landmark_list.push(landmarks[index].x - base_x)
        temp_landmark_list.push(landmarks[index].y - base_y)
    }
    let abs_list = temp_landmark_list.map(Math.abs)
    let max_val = Math.max(...abs_list)

    return temp_landmark_list.map(value => value / max_val)

}

let landmarks_ = null


// Our input frames will come from here.
const videoElement = document.getElementsByClassName('input_video')[0];
const canvasElement = document.getElementsByClassName('output_canvas')[0];
const controlsElement = document.getElementsByClassName('control-panel')[0];
const canvasCtx = canvasElement.getContext('2d');
const config = {
    locateFile: (file) => {
        return `https://cdn.jsdelivr.net/npm/@mediapipe/hands@${mpHands.VERSION}/${file}`;
    }
};
// We'll add this to our control panel later, but we'll save it here so we can
// call tick() each time the graph runs.
const fpsControl = new controls.FPS();
// Optimization: Turn off animated spinner after its hiding animation is done.
const spinner = document.querySelector('.loading');
spinner.ontransitionend = () => {
    spinner.style.display = 'none';
};
const landmarkContainer = document.getElementsByClassName('landmark-grid-container')[0];
const grid = new controls3d.LandmarkGrid(landmarkContainer, {
    connectionColor: 0xCCCCCC,
    definedColors: [{ name: 'Left', value: 0xffa500 }, { name: 'Right', value: 0x00ffff }],
    range: 0.2,
    fitToGrid: false,
    labelSuffix: 'm',
    landmarkSize: 2,
    numCellsPerAxis: 4,
    showHidden: false,
    centered: false,
});

setInterval(async () => {
    if (!landmarks_) {
        console.clear()
        console.log("No hand detected!")
        return
    }
    const processed_landmark = pre_process_landmark(landmarks_)

    const data = tf.tensor2d(processed_landmark, [1, 42]);

    const result = await makePrediction(model, data);
    const argmax = processResult(result);

    let label_name = ["Open", "Close", "Pointer", "OK", "Six-Up", "Six-Down", "Yeah"]
    console.log(`Predicted class: ${argmax}`, `handgesture: ${label_name[argmax]}`)
}, 200)


function onResults(results) {
    // Hide the spinner.
    document.body.classList.add('loaded');
    // Update the frame rate.
    fpsControl.tick();
    // Draw the overlays.
    canvasCtx.save();
    canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
    canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
    if (results.multiHandLandmarks.length < 1) {
        landmarks_ = null
        return
    }
    if (results.multiHandLandmarks && results.multiHandedness) {
        for (let index = 0; index < results.multiHandLandmarks.length; index++) {
            const classification = results.multiHandedness[index];
            const isRightHand = classification.label === 'Right';
            const landmarks = results.multiHandLandmarks[index];
            landmarks_ = landmarks
            // console.log(landmarks)

            // const processed_landmark = pre_process_landmark(landmarks)

            // const data = tf.tensor2d(processed_landmark, [1, 42]);

            // const result = await makePrediction(model, data);
            // console.log(result);
            // const argmax = processResult(result);

            // console.log(`Predicted class: ${argmax}`);


            drawingUtils.drawConnectors(canvasCtx, landmarks, mpHands.HAND_CONNECTIONS, { color: isRightHand ? '#00FF00' : '#FF0000' });
            drawingUtils.drawLandmarks(canvasCtx, landmarks, {
                color: isRightHand ? '#00FF00' : '#FF0000',
                fillColor: isRightHand ? '#FF0000' : '#00FF00',
                radius: (data) => {
                    return drawingUtils.lerp(data.from.z, -0.15, .1, 10, 1);
                }
            });


        }
    }
    canvasCtx.restore();
    if (results.multiHandWorldLandmarks) {
        // We only get to call updateLandmarks once, so we need to cook the data to
        // fit. The landmarks just merge, but the connections need to be offset.
        const landmarks = results.multiHandWorldLandmarks.reduce((prev, current) => [...prev, ...current], []);
        const colors = [];
        let connections = [];
        for (let loop = 0; loop < results.multiHandWorldLandmarks.length; ++loop) {
            const offset = loop * mpHands.HAND_CONNECTIONS.length;
            const offsetConnections = mpHands.HAND_CONNECTIONS.map((connection) => [connection[0] + offset, connection[1] + offset]);
            connections = connections.concat(offsetConnections);
            const classification = results.multiHandedness[loop];
            colors.push({
                list: offsetConnections.map((unused, i) => i + offset),
                color: classification.label,
            });
        }
        grid.updateLandmarks(landmarks, connections, colors);
    }
    else {
        grid.updateLandmarks([]);
    }
}
const hands = new mpHands.Hands(config);
hands.onResults(onResults);
// Present a control panel through which the user can manipulate the solution
// options.
new controls
    .ControlPanel(controlsElement, {
        selfieMode: true,
        maxNumHands: 1,
        modelComplexity: 0,
        minDetectionConfidence: 0.5,
        minTrackingConfidence: 0.5
    })
    .add([
        new controls.StaticText({ title: '手势识别配置' }),
        fpsControl,
        new controls.Toggle({ title: '自拍模式', field: 'selfieMode' }),
        new controls.SourcePicker({
            onFrame: async (input, size) => {
                const aspect = size.height / size.width;
                let width, height;
                if (window.innerWidth > window.innerHeight) {
                    height = window.innerHeight;
                    width = height / aspect;
                }
                else {
                    width = window.innerWidth;
                    height = width * aspect;
                }
                canvasElement.width = width;
                canvasElement.height = height;
                await hands.send({ image: input });
            },
        }),
        new controls.Slider({
            title: '最大可识别手掌',
            field: 'maxNumHands',
            range: [1, 4],
            step: 1
        }),
        new controls.Slider({
            title: '模型复杂度',
            field: 'modelComplexity',
            discrete: ['精简版', '高级版'],
        }),
        new controls.Slider({
            title: '最小检测置信度',
            field: 'minDetectionConfidence',
            range: [0, 1],
            step: 0.01
        }),
        new controls.Slider({
            title: '最小跟踪置信度',
            field: 'minTrackingConfidence',
            range: [0, 1],
            step: 0.01
        }),
    ])
    .on(x => {
        const options = x;
        videoElement.classList.toggle('selfie', options.selfieMode);
        hands.setOptions(options);
    });
