import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { detectSingleFace, loadFaceExpressionModel, loadFaceLandmarkModel, loadFaceLandmarkTinyModel, loadFaceRecognitionModel, loadSsdMobilenetv1Model, loadTinyFaceDetectorModel, TinyFaceDetectorOptions } from 'face-api.js';

@Component({
    selector: 'app-ar-fan-cam',
    templateUrl: './ar-fan-cam.page.html',
    styleUrls: ['./ar-fan-cam.page.scss'],
})
export class ArFanCamPage implements OnInit {
    @ViewChild('videoElement', null) videoElement: ElementRef<HTMLVideoElement>;
    mediaStream: MediaStream|null = null;

    glassProperties: {
        [property: string]: any,
    } = {
        transform: 'scale(0)',
    };

    areNeuralNetsLoaded = false;
    isDetecting = false;

    leftEyeX = 0;
    leftEyeY = 0;

    rightEyeX = 0;
    rightEyeY = 0;

    width = 360;
    height = 270;

    constructor() { }

    loadNeuralNetModels = async () => {
        await loadTinyFaceDetectorModel('/assets/weights');
        await loadSsdMobilenetv1Model('/assets/weights');

        await loadFaceLandmarkTinyModel('/assets/weights');
        await loadFaceLandmarkModel('/assets/weights');

        await loadFaceRecognitionModel('/assets/weights');
        await loadFaceExpressionModel('/assets/weights');

        this.areNeuralNetsLoaded = true;
    }

    getCameraStream = async () => {
        const stream = await window.navigator.mediaDevices.getUserMedia({
            video: {
                facingMode: 'user',
                width: this.width,
            },
        });

        this.mediaStream = stream;
        this.videoElement.nativeElement.srcObject = stream;
    };

    stopCameraStream = async () => {
        if (this.mediaStream) {
            this.mediaStream.getVideoTracks().forEach(track => track.stop());
            this.mediaStream = null;
        }
    }

    detectAndDrawFace = async () => {
        const tinyFaceDetectorOptions = new TinyFaceDetectorOptions();

        let detectionWithLandmarks = await detectSingleFace(this.videoElement.nativeElement, tinyFaceDetectorOptions).withFaceLandmarks(true);
        
        if (detectionWithLandmarks) {
            const leftEye = detectionWithLandmarks.landmarks.getLeftEye();
            const rightEye = detectionWithLandmarks.landmarks.getRightEye();

            this.leftEyeX = leftEye[0].x;
            this.leftEyeY = leftEye[0].y;

            this.rightEyeX = rightEye[0].x;
            this.rightEyeY = rightEye[0].y;

            const distanceBetweenEyes = Math.sqrt(Math.pow(this.rightEyeX - this.leftEyeX, 2) + Math.pow(this.rightEyeY - this.leftEyeY, 2));
            const angleOfRotation =  (Math.atan2(this.rightEyeY - this.leftEyeY, this.rightEyeX - this.leftEyeX) * 180) / Math.PI;

            const scaleMultiplier = distanceBetweenEyes / 130;

            this.glassProperties = {
                transform: `translate(calc(-14.5% + ${this.leftEyeX}px), calc(-45% + ${this.leftEyeY}px)) scale(${scaleMultiplier}) rotate(${angleOfRotation}deg)`,
            }
        }

        requestAnimationFrame(this.detectAndDrawFace);
    }

    toggleDetection = () => {
        if (!this.isDetecting) {
            this.isDetecting = true;
            this.detectAndDrawFace();
        } else {
            this.isDetecting = false;
        }
    }

    ngOnInit() {
        this.loadNeuralNetModels();

        const feedWidth = window.innerWidth > 600 ? 600 : window.innerWidth;
        const feedHeight = feedWidth * 0.75;

        this.width = feedWidth;
        this.height = feedHeight;
    }

}
