import { FilesetResolver, HandLandmarker, GestureRecognizer } from '/ai/vision_bundle.js';

import { log } from './logbar.js';

const {stringify} = JSON
export async function HandRecogniser (video, reconProps = {}) {
    
    
	const HOLD_THRESHOLD = 250;
	const ACTION_THRESHOLD = 4200;
    const SWIPE_STEPS = 3;
    
    let prevGestureName = '',
        possibleGestureName = '',
        gestureCompleted,
        holdStarts = 0,
        holdCurrent = 0,
        lastTime,
        gesturePercentage = 0,
    actionCurrentTime = 0,
    actionRunning = false,
    // actionCurrentGesture = '',
    actionPrevGesture = '',
    actionSequence = [],
		gestureRecon,
    evtTimeElapsed = 0,
    prevVideoTime = 0,
    landmarksStart,
    landmarksEnd,
    swipeX = 0,
    swipeY = 0,
    swipeCoords = [],
		animId
    ;
    
    
    
    async function loadRecognisers(progressCb) {
        const prefix = '' // navigator.onLine ? 'https://devignia.site' : '' 
        const vision = await FilesetResolver.forVisionTasks(
            prefix + '/ai/wasm'
        );
		
		if(typeof progressCb == 'function')
			progressCb('vision');
		
        const recon = await GestureRecognizer.createFromOptions(
            vision,
            {
                baseOptions: {
                    modelAssetPath: prefix + "/ai/models/gesture_recognizer.task",
                    delegate: 'GPU'
                },
                numHands: 2,
                runningMode: 'VIDEO'
            }
        );
		
		if(typeof progressCb == 'function')
			progressCb('recogniser')
        return recon;
    }
    
    function confirmGesture(gesture, videoTime = 0) {
        
        
        
        if (gesture != prevGestureName && holdStarts != 0) {
            holdStarts = 0;
            holdCurrent = 0;
            prevGestureName = '';
            possibleGestureName = '';
            return;
        }
        
        if (holdStarts == 0)
            holdStarts = videoTime;
        
        // Currents
        holdCurrent = videoTime;
        prevGestureName = gesture;
        
        gesturePercentage = ((((holdCurrent - holdStarts) * 1000) / HOLD_THRESHOLD) * 100)
        
        // the maybe
        if (holdCurrent > 0 && possibleGestureName == '') {
            possibleGestureName = gesture;
        }
        
        // the confirmed
        if (((holdCurrent - holdStarts) * 1000) >= HOLD_THRESHOLD) {
            holdStarts = 0;
            possibleGestureName = '';
            return gesture;
        }
        
        // no confirmation
        return;
    }
    
    function getRotation(lm) {
        const { sqrt, pow, asin, abs, PI } = Math,
            rad = (PI / 180)
        ;
        let z = lm[9].z - lm[0].z, // depth, when lookin from the front
            y = lm[9].y - lm[0].y, // height
            x = lm[9].x - lm[0].x, // width
            zyHipot = sqrt(pow(z, 2) + pow(y, 2)),
            zxHipot = sqrt(pow(z, 2) + pow(x, 2)),
            yxHipot = sqrt(pow(y, 2) + pow(x, 2))
        ;
        return { x: asin(z/zyHipot) / rad, y: asin(z/zxHipot) / rad, z: asin(y/yxHipot) / rad }
    }
    
    
    function resetEvtParams() {
        evtTimeElapsed = 0
            prevVideoTime = 0
            
            
            actionCurrentTime = 0
            actionSequence = []
        landmarksStart = []
        landmarksEnd = []
        swipeX = 0
        swipeY = 0
        swipeCoords = []
            //log('reset')
    }
    
    // detects and confirms if desired actions perform, true or false
    function confirmAction(asAction, gesture, videoTime = 0, landmarks) {
        
        
        if(
            evtTimeElapsed * 1000 >= ACTION_THRESHOLD
            // gestures are filtered before event detection
            // || !['POINTING_UP', 'CLOSED_FIST', 'OPEN_PALM'].includes(gesture)
        
            // some gestures bypass timer for its well functioning
            && asAction != 'holdopenpalm'
        ) {
            resetEvtParams()
            return false;
        }
        
        if(prevVideoTime != 0 )
            evtTimeElapsed += videoTime - prevVideoTime
        
        prevVideoTime = videoTime
        
        //log(evtTimeElapsed)
        
        if(actionSequence[actionSequence.length - 1] != gesture)
            actionSequence.push(gesture)
        
        //log(['asaction:'+asAction,gesture,videoTime].join(','))
        //log(actionSequence.join(','))
        
        switch(asAction) {
            // handblink is a closed-open-closed sequence
            case 'handblink':
                if(
                    actionSequence.join(',') == 'CLOSED_FIST,OPEN_PALM,CLOSED_FIST'
                ) {
                    resetEvtParams()
                    return true;
                }
                    
            
                return false;
            break;
            case 'holdopenpalm':
                
                let coords = [],
                    result = {
                        coords: [],
                        rotation: {}
                    }            
                ;
                //log(stringify(actionSequence))
                if(actionSequence[0] == 'OPEN_PALM' && actionSequence.length == 1) {
                    result.rotation = getRotation(landmarks);
                    [4, 8, 12, 16, 20].forEach((index) => {
                        result.coords.push(landmarks[index])
                    })
                    return result
                }
                log('reset')
                resetEvtParams()
                return false;
            break;
            case 'swipe':
            case 'swipeleft':
            case 'swiperight':
                let x = ((landmarks[8].x + landmarks[12].x) / 2)
                let y = ((landmarks[8].y + landmarks[12].y) / 2)
            
                swipeCoords.push( { x, y } );
                log(swipeCoords.length)
                if(actionSequence[0] == 'VICTORY' && swipeCoords.length >= SWIPE_STEPS) {
                    let last = swipeCoords[swipeCoords.length - 1],
                        first = swipeCoords[0],
                        swipeX = last.x - first.x,
                        swipeY = last.y - first.y
                    ;
                    resetEvtParams()
                    return { swipeX, swipeY }
                }
                return false;
            break;
            
        }
        
    }
    
    async function recogniseOnVideo(gesture, cb) {
        
        let startTime = performance.now(),
            categoryName = '',
            result
        		;
        
        		gesture = gesture.toUpperCase()
        		
        if (lastTime !== video.currentTime) {
            lastTime = video.currentTime;
            result = await gestureRecon.recognizeForVideo(video, startTime);
        }
        
        if (result && result.gestures.length > 0) {
            categoryName = result.gestures[0][0].categoryName.toUpperCase()
            //log(categoryName)
            switch(categoryName) {
                
                case gesture:
                    
                    if(confirmGesture(categoryName, video.currentTime))
                        cb(result)
                break;
                
                case 'CLOSED_FIST':
                case 'OPEN_PALM':
                
                    if('onhandblink' in reconProps) {
                        // finish some previous holdopenpalm event if any
                        // reconProps.onholdopenpalmend()
                        let didAction = confirmAction('handblink', categoryName, video.currentTime)
                        if(didAction) 
                            reconProps.onhandblink()    
                    }
                    if('onholdopenpalm' in reconProps && categoryName == 'OPEN_PALM') {
                        let action = confirmAction('holdopenpalm', categoryName, video.currentTime, result.landmarks[0])
                        //log(stringify(action))
                        if(action) {
                            reconProps.onholdopenpalm(action)        
                        } else {
                            reconProps.onholdopenpalmend()
                        }
                    }
                break;
                case 'VICTORY':
                    if(
                        'onswipeleft' in reconProps
                    ) {
                        const evtResult = confirmAction('swipeleft', categoryName, video.currentTime, result.landmarks[0])
                        if(evtResult)
                            reconProps.onswipeleft(evtResult)
                    }
                    if(
                        'onswiperight' in reconProps
                    ) {
                        const evtResult = confirmAction('swiperight', categoryName, video.currentTime, result.landmarks[0])
                        if(evtResult)
                            reconProps.onswiperight(evtResult)
                    }
                    if(
                        'onswipe' in reconProps
                    ) {
                        const evtResult = confirmAction('swipe', categoryName, video.currentTime, result.landmarks[0])
                        if(evtResult)
                            reconProps.onswipe(evtResult)
                    }
                break;
            }
            /*
            if (gestureCompleted == gesture) {
                cb(result)
            }
            */
        } 
        
        animId = requestAnimationFrame(() => {
            recogniseOnVideo(reconProps.gesture, reconProps.onrecognised)
        })
    }
    
    
        
    function stopCamera() {
        video.pause();
        video.removeAttribute('src')
        video.width = video.dataset.previousWidth;
        video.height = video.dataset.previousHeight;
        cancelAnimationFrame(animId)
    }
    
    function startCamera() {
        
        let videoWidth = window.innerWidth * 0.32,
            videoHeight = window.innerHeight * 0.32;
        
        // Width and Height be inverted in constraints to optimal proportion (?)
        const videoConstraints = {
            video: {
                facingMode: 'user',
            }
        };
        
        navigator.mediaDevices.getUserMedia(videoConstraints).then((stream) => {
            video.removeAttribute('hidden')
            video.dataset.previousWidth = video.width;
            video.dataset.previousHeight = video.height;
            video.width = videoWidth;
            video.height = videoHeight;
            
            // after ease css transition for optimal rendering
            video.srcObject = stream;
			
			
            video.addEventListener('loadeddata', () => {
                recogniseOnVideo(reconProps.gesture, reconProps.onrecognised)
                log('recon started')
            })
			video.addEventListener('loadedmetadata', () => {
				video.play()
				video.dataset.isPlaying = true
			})
        })
        
        
        
        // toggle webcam play/stop
        video.addEventListener('click', () => {
            if (video.dataset.isPlaying)
                video.pause();
            else
                video.resume();
			
            
            video.dataset.isPlaying = !video.dataset.isPlaying;
            
        });
        
    }
    
	
    gestureRecon = await loadRecognisers(log);
	
	return {
		startCamera,
		stopCamera
	}
    
};

