'use strict';

// import FingerprintJs from "@fingerprintjs/fingerprintjs";
// import FingerprintJsPro from "@fingerprintjs/fingerprintjs-pro";
import {getFingerPrint} from "../modules/wt-fingerprint";

const wait = function(ms) { return new Promise(resolve => setTimeout(resolve, ms))};

const median = function(values) {

    if(values.length ===0) return 0;

    values.sort(function(a,b){
        return a-b;
    });

    var half = Math.floor(values.length / 2);

    if (values.length % 2)
        return values[half];

    return (values[half - 1] + values[half]) / 2.0;
}


// const getFingerPrint = function(instanceType, salt) {
//
//     let fingerprint = FingerprintJs.load({
//         monitoring: false
//     })
//
//     return fingerprint
//         .then(fp => fp.get())
//         .then(result => {
//             const components = {
//                 ...result.components,
//                 instanceType: { value: instanceType + '_' + salt },
//             }
//             return [8,13,18,23].reduce((acc, cur) => {
//                 return acc.slice(0,cur) + '-' + acc.slice(cur)
//             }, FingerprintJs.hashComponents(components)).substring(0,36)
//         })
// }
//
// const getPreciseFingerPrint = function() {
//
//     let fingerprintPro = FingerprintJsPro.load({
//         monitoring: false,
//         apiKey: '5UHdpSuX3wHr3CjyEiSP',
//         endpoint: "https://fingerprint.reactoo.com"
//     })
//
//     return fingerprintPro
//         .then(fp => fp.get())
//         .then(result => {
//             let id = result.visitorId.padEnd(32, '0');
//             return [8,13,18,23].reduce((acc, cur) => {
//                 return acc.slice(0,cur) + '-' + acc.slice(cur)
//             }, id).substring(0,36)
//         })
// }


const getBrowserFingerprint = function (instanceType = '', usePrecise = false, salt = '') {
    // if(!usePrecise) {
    //     return getFingerPrint(instanceType, salt)
    // }
    // else {
    //     return getPreciseFingerPrint()
    //         .catch(() => getFingerPrint(instanceType, salt))
    // }

    return getFingerPrint(instanceType, salt);
};

let generateUUID = function () {
    var d = Date.now();
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = (d + Math.random()*16)%16 | 0;
        d = Math.floor(d/16);
        return (c=='x' ? r : (r&0x3|0x8)).toString(16);
    });
};


const janusDisplayDelimiter = ',';
const decodeJanusDisplay = display => {
    let output = { userId: null, role: "participant", start: Date.now(), displayName: "?" };
    if(display && typeof display === "string") {
        if(display.indexOf(janusDisplayDelimiter) >= 0) {
            let values = display
                .split( new RegExp(`\\${janusDisplayDelimiter}(?=(?:(?:[^"]*"){2})*[^"]*$)`, 'mi') )
                .map(v => (v && v.startsWith('"') || v.endsWith('"')) ? v.substring(1, v.length-1) : ( /^\d+$/.test(v) ? parseInt(v) : v ));
            Object.keys(output).forEach((key, i) => values[i] ? output[key] = values[i] : null);
        } else {
            output.userId = display;
        }
        return output;
    } else if(display && typeof display === "object") {
        return Object.assign({}, output, display);
    }
    return null;
}

const setExactTimeout = function(callback, duration, resolution) {
    const start = (new Date()).getTime();
    const timeout = setInterval(function(){
        if ((new Date()).getTime() - start > duration) {
            callback();
            clearInterval(timeout);
        }
    }, resolution);

    return timeout;
};

const clearExactTimeout = function(timeout) {
    clearInterval(timeout);
};


const maxJitter = (x) => {
    // A function that returns a value between 0.3 and 0.5 depending on the input x
    // The function is based on a logistic curve with parameters a, b, c, and d
    // The parameters are chosen such that f(30) = 0.3 and f(2) = 0.5
    let a = 0.2; // The maximum value of the function
    let b = -0.1; // The growth rate of the function
    let c = 16; // The inflection point of the function
    let d = 0.3; // The minimum value of the function
    return a / (1 + Math.exp(-b * (x - c))) + d; // Fixed the typo here
}

const chunkArray = (array = [], chunkSize) => {
    array = [...array];
    if (!array?.length) return [[]];

    let results = [];
    while (array.length) {
        results.push(array.splice(0, chunkSize));
    }
    return results;
}


const getUserMediaConstraints = ({

                            hasVideo,
                            hasAudio = true,
                            aDeviceId,
                            vDeviceId,
                            autoGainControl = false,
                            echoCancellation = true,
                            noiseSuppression = true,
                            channelCount = 1,
                            width = 1920,
                            height = 1080,

                        }) => {

    // optional: Array (6)
    // • 0: {googEchoCancellation: true}
    // • 1: {googEchoCancellation2: true}
    // • 2: {googAutoGainControl: true}
    // • 3: {googNoiseSuppression: true}
    // » 4: {googHighpassFilter: true}
    // • 5: {googAudioMirroring: true}
    // googTypingNoiseDetection


    let audioOnlyConstraints = {
        audio: {
            ...(aDeviceId && {deviceId: {exact:aDeviceId}}),
            autoGainControl,
            echoCancellation,
            noiseSuppression,
            channelCount
        }
    };

    let videoOnlyConstraints = {
        video: {
            ...(vDeviceId && {deviceId: {exact:vDeviceId}}),
            facingMode: {ideal: "user"},
            frameRate: { ideal: 30, max: 30 },
            width: {ideal: width},
            height: {ideal: height},
        }
    };

    return {
        ...(hasAudio ? audioOnlyConstraints : {audio: false}),
        ...(hasVideo ? videoOnlyConstraints : {video: false})
    };

};


const applyConstraints = ({
    autoGainControl = false,
    echoCancellation = true,
    noiseSuppression = true,
    channelCount = 1,
    stream = null,
    width, height
}) => {

    if(stream) {

        let fullConstraints = getUserMediaConstraints({
            hasVideo: true,
            hasAudio: true,
            autoGainControl, echoCancellation, noiseSuppression, channelCount, width, height
        });
        let returnPromises = [];

        const videoTrack = stream.getVideoTracks()[0];
        if(videoTrack) {
            returnPromises.push(videoTrack.applyConstraints(fullConstraints.video));
        }

        const audioTrack = stream.getAudioTracks()[0];
        if(audioTrack) {
            returnPromises.push(audioTrack.applyConstraints(fullConstraints.audio));
        }

        return Promise.all(returnPromises).then(() => {
            return stream;
        });
    }

    else return Promise.resolve(null);


};
const getUserStream = ({
        hasVideo,
        hasAudio = true,
        aDeviceId,
        vDeviceId,
        autoGainControl = false,
        echoCancellation = true,
        noiseSuppression = true,
        channelCount = 1,
        muteAudio = false,
        muteVideo = false,
        width = 1920,
        height = 1080
    } = {}
) => {

    let fullConstraints = getUserMediaConstraints({
        hasVideo, hasAudio, aDeviceId, vDeviceId, autoGainControl, echoCancellation, noiseSuppression, channelCount, width, height
    });

    return navigator.mediaDevices.getUserMedia(fullConstraints)
        .then(stream => {
            stream.getAudioTracks().forEach(track => track.enabled = !muteAudio);
            stream.getVideoTracks().forEach(track => track.enabled = !muteVideo);
            return stream;
        });
};
const getHostStream = ({
        hasAudio,
        hasVideo,
        aDeviceId,
        vDeviceId,
        autoGainControl = false,
        echoCancellation = false,
        noiseSuppression = false,
        channelCount = 1,
        width = 1920,
        height = 1080,
    } = {}
) => {

    let audioOnlyConstraints = {
        audio: {
            ...(aDeviceId && {deviceId: {exact:aDeviceId}}),
            autoGainControl,
            echoCancellation,
            noiseSuppression,
            channelCount
        }
    };

    let videoOnlyConstraints = {
        video: {
            ...(vDeviceId && {deviceId: {exact:vDeviceId}}),
            frameRate: {ideal: 30, max: 30},
            width, height
        }
    };

    let fullConstraints = {
        ...(hasAudio ? audioOnlyConstraints : {audio: false}),
        ...(hasVideo ? videoOnlyConstraints : {video: false})
    };

    return navigator.mediaDevices.getUserMedia(fullConstraints);
};
const getDisplayMedia = () =>{
    const constraints = {
        video: {
            cursor: "always",
            displaySurface: "monitor",
            logicalSurface: false
        },
        surfaceSwitching: "include",
        selfBrowserSurface: "exclude",
        systemAudio: "exclude",
    };

    return navigator.mediaDevices.getDisplayMedia(constraints);
};



export {
    //TODO: add more constraints
    getDisplayMedia, getHostStream, getUserStream, applyConstraints,
    wait, getBrowserFingerprint, generateUUID, decodeJanusDisplay, setExactTimeout, clearExactTimeout, median, maxJitter, chunkArray
}
