const cameras = require('@neuro-city/cameras');
const { Camera } = require('@neuro-city/cameras');
const faceID = require('../index.js');
const path = require('path');
const fs = require('fs');

//Размеры body для динамического изменения
let bodyHeight = '0px';
let bodyWidth = '0px';

//Выводит сообщение на страницу
const showMessage = (type, ...args) => {
    args = ['[neuro-id]', ...args];
    let message = '';
    if (Array.isArray(args)) args.forEach(arg => {
        if (typeof arg === 'object') {
            message += JSON.stringify(arg, null, '\t');
        } else {
            message += arg;
        };
        message += ' ';
    });

    switch (type) {
        case 'info':
            console.log(message);
            break;
        case 'warn':
            console.warn(message);
            break;
        case 'error':
            console.error(message);
            break;        
        default:
            console.log(message);
            break;
    };

    //Если удалось найти HTML элементы для сообщений, дублируем сообщения в них
    const messageElem = document.getElementById('message');
    const messageConteiner= document.getElementById('message-conteiner');
    if (messageElem && messageConteiner) {
        messageConteiner.classList.add('show');
        messageElem.innerText += '\n\n' + message;
        messageElem.classList = type;
        messageConteiner.scrollTo(0, messageConteiner.scrollHeight);
    };
};

//Логгер
const log = {
    info: (...args) => showMessage('info', ...args),
    warn: (...args) => showMessage('warn', ...args),
    error: (...args) => showMessage('error', ...args)
};

//Подписываемся на события сообщений
cameras.on('info', (info) => {
    log.info(info);
});
cameras.on('error', (error) => {
    log.error(error);
});
cameras.on('stop-all', () => {
    log.info(`Стримы со всех камер успешно остановлены`);
});

document.addEventListener('DOMContentLoaded', () => {
    //Следим за изменением размера body
    bodyHeight = document.body.offsetHeight + 'px';
    bodyWidth = document.body.offsetWidth + 'px';
    const resizeObserver = new ResizeObserver(entries => {
        bodyHeight = document.body.offsetHeight + 'px';
        bodyWidth = document.body.offsetWidth + 'px';
        const controlsConteiner = document.getElementById('controls-conteiner');
        const detectorsConteiner = document.getElementById('detectors-loding-status-conteiner');
        const addPhotoConteiner = document.getElementById('add-photo-conteiner');
        if (controlsConteiner && detectorsConteiner) detectorsConteiner.style.top = controlsConteiner.clientHeight + document.body.offsetHeight * 0.03 + 'px';
        if (controlsConteiner && detectorsConteiner && addPhotoConteiner) addPhotoConteiner.style.top = controlsConteiner.clientHeight + detectorsConteiner.clientHeight + document.body.offsetHeight * 0.04 + 'px';
        setVideoContainer();
    });
    resizeObserver.observe(document.body);

    const video = document.getElementById('camera');
    const camSelect = document.getElementById('camera-name');

    const camOptions = {
        rotate: 0,
        verticalInvert: false,
        horizontalInvert: false
    };

    //Получим список подключенных камер
    cameras.getCameras().then(list => {
        log.info('Список подключенных камер:', list);

        if (list.length === 0) {
            log.error(`Нет подключенных камер. Работа модуля невозможна`);
            return;
        };

        //Добавляем полученные камеры в select выбора камеры
        camSelect.options.length = 0;
        camSelect.options[camSelect.options.length] = new Option(list[0].name);

        //Найдём необходимую камеру (для примера возьмём первую из списка подключенных)
        cameras.findCamera(list[0]).then(result => {
            switch (result.status) {
                case 'found':
                    log.info(result.info);

                    //Создаём экземпляр первой камеры
                    const camera = new Camera;

                    //Подписываемся на события сообщений
                    camera.on('info', (info) => {
                        log.info(info);
                    });
                    camera.on('error', (error) => {
                        log.error(error);
                    });
                    camera.on('stop', (camera) => {
                        log.info(`Стрим с камеры "${camera.name}" (${camera.id}) успешно остановлен`);
                    });
                    camera.on('disconnect', (camera) => {
                        log.error(`Связь с камерой "${camera.name}" (${camera.id}) потеряна`);
                    });

                    //Подписываемся на событие подключения к камере
                    camera.on('stream', (stream) => {

                        if (video) {
                            //Вещаем стрим в видеоэлемент
                            video.srcObject = stream;
                            //Поворачиваем видео элемент по заданным настройкам
                            setVideoContainer();

                            //Создаём список ожидания загрузки детекторов
                            const controlsConteiner = document.getElementById('controls-conteiner');
                            const detectorsConteiner = document.createElement('div');
                            detectorsConteiner.id = 'detectors-loding-status-conteiner';
                            detectorsConteiner.style.top = controlsConteiner.clientHeight + document.body.offsetHeight * 0.03 + 'px';
                            document.body.appendChild(detectorsConteiner);

                            const conteiner = document.createElement('div');
                            conteiner.innerHTML = '';
                            conteiner.style.display = 'flex';
                            conteiner.style.alignItems = 'flex-end';
                            detectorsConteiner.appendChild(conteiner);
                            //Точки или галочка
                            const loader = document.createElement('div');
                            loader.id = 'ssdMobilenetv1';
                            loader.classList.add('loading');
                            //Название детектора
                            const label = document.createElement('label');
                            label.innerHTML = 'ssdMobilenetv1';
                            label.htmlFor = 'ssdMobilenetv1';
                            //Среднее время обнаружения
                            const detectTime = document.createElement('div');
                            detectTime.id = 'ssdMobilenetv1-time';
                            detectTime.innerHTML = 'Среднее время: неизвестно'
                            //Статус обнаружения лица
                            const face = document.createElement('div');
                            face.classList.add('face-detect-status');
                            face.id = 'ssdMobilenetv1-face';

                            conteiner.appendChild(label);
                            conteiner.appendChild(loader);
                            conteiner.appendChild(detectTime);
                            conteiner.appendChild(face);

                            const canvas = document.getElementById('canvas');
                            const context = canvas.getContext('2d');

                            //Кнопка старта распознавания лиц
                            const recognizeSwitchButton = document.createElement('div');
                            recognizeSwitchButton.classList.add('control-button');
                            recognizeSwitchButton.id = 'recognize-switch-button';
                            recognizeSwitchButton.dataset.status = "on";

                            detectorsConteiner.appendChild(recognizeSwitchButton);
                            recognizeSwitchButton.addEventListener('click', () => {
                                if (recognizeSwitchButton.dataset.status === 'on') {
                                    recognizeSwitchButton.dataset.status = 'off';
                                    recognizeSwitchButton.style.backgroundImage = 'url("./img/eye-hidden.png")';
                                    //Останавливаем обнаружение лиц
                                    faceID.stop();
                                    //Отрисовываем рамку на canvas
                                    context.clearRect(0, 0, canvas.width, canvas.height);
                                } else {
                                    recognizeSwitchButton.dataset.status = 'on';
                                    recognizeSwitchButton.style.backgroundImage = 'url("./img/eye-view.png")';
                                    //Запускаем обнаружение лиц
                                    faceID.start(video, camOptions);
                                };
                            });

                            //Подписываемся на события
                            faceID.on('detected', (data) => {
                                const time = document.getElementById('ssdMobilenetv1-time');
                                if (time) time.innerHTML = 'Среднее время: ' + data.time;
                                const face = document.getElementById('ssdMobilenetv1-face');
                                //Отрисовываем рамку на canvas
                                context.clearRect(0, 0, canvas.width, canvas.height);
                                if (face) {
                                    if (data.face || (Array.isArray(data.faces) && data.faces.length > 0)) {
                                        face.style.backgroundColor = 'green';
                                    } else {
                                        face.style.backgroundColor = 'red';
                                    };
                                };

                                //Если есть ближайшее лицо, отрисовываем его
                                if (data.face) {
                                    context.strokeStyle = 'green';
                                    context.lineWidth = Math.round(data.face.width / 50);
                                    context.beginPath();
                                    const x = canvas.width * data.face.x;
                                    const y = canvas.height * data.face.y;
                                    const width = canvas.width * data.face.width;
                                    const height = canvas.height * data.face.height;
                                    context.strokeRect(
                                        x,
                                        y,
                                        width,
                                        height
                                    );

                                    //Отображаем имя пользователя, если нашёлся
                                    if (data.face.name) {
                                        context.strokeStyle = "green";
                                        context.font = 'bold 30px sans-serif';
                                        var textWidth = context.measureText(data.face.name).width;
                                        context.strokeText(data.face.name, x + width / 2 - textWidth / 2, y - height * 0.1);
                                    };
                                };

                                //Если есть ещё лица, отрисовываем их тоже
                                if (Array.isArray(data.faces) && data.faces.length > 0) {
                                    data.faces.forEach(face => {
                                        context.strokeStyle = 'red';
                                        context.lineWidth = Math.round(face.width / 50);
                                        context.beginPath();
                                        context.strokeRect(
                                            canvas.width * face.x,
                                            canvas.height * face.y,
                                            canvas.width * face.width,
                                            canvas.height * face.height
                                        );
                                    });
                                };
                            })
                            //Вывод данных
                            faceID.on('data', data => {
                                log.info(data);
                            });
                            //Вывод ошибок
                            faceID.on('error', error => {
                                log.error(error);
                            });

                            //Расчёт десктритора
                            faceID.on('calculated', data => {
                                const userName = document.getElementById('user-name');
                                faceID.addDescriptor(data, userName ? userName.value : 'Тестовый пользователь');
                            });

                            faceID.on('ready', (message) => {
                                const loader = document.getElementById('ssdMobilenetv1');
                                loader.classList = 'loaded';

                                log.info(message);

                                //Отображаем форму добавления фото для расчёта дескрипторов
                                const addPhotoConteiner = document.createElement('div');
                                addPhotoConteiner.id = 'add-photo-conteiner';
                                addPhotoConteiner.style.top = controlsConteiner.clientHeight + detectorsConteiner.clientHeight + document.body.offsetHeight * 0.04 + 'px';
                                const userName = document.createElement('input');
                                userName.id = 'user-name';
                                userName.value = 'Тестовый пользователь';
                                addPhotoConteiner.appendChild(userName);
                                const addPhotoButton = document.createElement('input');
                                addPhotoButton.id = 'add-photo-button';
                                addPhotoButton.type = 'file';
                                addPhotoButton.accept = '.jpg, .jpeg, .png, .bmp';
                                addPhotoButton.classList.add('control-button');
                                addPhotoButton.onchange = () => {
                                    if (addPhotoButton.files?.length && addPhotoButton.files.length > 0 && addPhotoButton.files[0].path) {
                                        const image = document.createElement('img');
                                        image.src = addPhotoButton.files[0].path;
                                        image.onload = () => {
                                            //Расчитываем дескрипторы по фото
                                            faceID.calculateDescriptor(image);
                                        };
                                    };
                                };
                                addPhotoConteiner.appendChild(addPhotoButton);
                                document.body.appendChild(addPhotoConteiner);

                                //Запускаем обнаружение лиц
                                faceID.start(video, camOptions);
                                recognizeSwitchButton.style.display = 'block';
                            })

                            //Инициализируем модуль распознавания лиц с загруженными дескрипторами
                            const descriptorsPath = path.resolve('./face-descriptors.json');
                            const descriptors = 
                                fs.existsSync(descriptorsPath) ?
                                JSON.parse(fs.readFileSync(descriptorsPath, "utf8")) :
                                [];
                            faceID.init(descriptors, 0.5);
                        } else {
                            log.error('HTML элемент видео "camera" не найден')
                        };
                    });

                    //Если камера найдена, пытаемся подключиться к ней
                    camera.start(result.camera);

                    break;
                case 'dublicate':
                    log.warn(result.info);
                    //При дублирывании камеры мы можем попытаться подключиться к ней,
                    //но не сможем точьно сказать к какой именно удасться подключиться
                    break;
                case 'not found':
                    log.error(result.info);
                    break;
                default:
                    log.error(`Не удалось найти камеру. Ошибка: Неизвестный ответ функции поиска камер "${result.status}"`);
                    break;
            };
        }).catch(error => {
            log.error('Не удалось найти камеру. Ошибка:', error.message || 'Неизвестная ошибка');
        });

    }).catch(error => {
        log.error('Не удалось получить список камер. Ошибка:', error.message || 'Неизвестная ошибка');
    });

    //Задаёт настройки контейнеру видео
    const setVideoContainer = () => {
        let transform = `rotate(${camOptions.rotate}deg)`;

        //Горизонтальное расположение монитора
        if (screen.width > screen.height) {
            //Вертикальное
            if (camOptions.rotate === 0 || camOptions.rotate === 180) {
                video.style.height = 'auto';
                video.style.width = bodyWidth;
                transform += ` ${camOptions.horizontalInvert ? 'scaleX(-1)' : ''}`;
                transform += ` ${camOptions.verticalInvert ? 'scaleY(-1)' : ''}`;
            };
            if (camOptions.rotate === 90 || camOptions.rotate === 270) {
                video.style.height = bodyWidth;
                video.style.width = 'auto';
                transform += ` ${camOptions.horizontalInvert ? 'scaleY(-1)' : ''}`;
                transform += ` ${camOptions.verticalInvert ? 'scaleX(-1)' : ''}`;
            };
        } else {
            //Вертикальное
            if (camOptions.rotate === 0 || camOptions.rotate === 180) {
                video.style.height = bodyHeight;
                video.style.width = 'auto';
                transform += ` ${camOptions.horizontalInvert ? 'scaleX(-1)' : ''}`;
                transform += ` ${camOptions.verticalInvert ? 'scaleY(-1)' : ''}`;
            };
            if (camOptions.rotate === 90 || camOptions.rotate === 270) {
                video.style.height = 'auto';
                video.style.width = bodyHeight;
                transform += ` ${camOptions.horizontalInvert ? 'scaleY(-1)' : ''}`;
                transform += ` ${camOptions.verticalInvert ? 'scaleX(-1)' : ''}`;
            };
        };
        video.style.transform = transform;

        //Нaстраиваем canvas
        const canvas = document.getElementById('canvas');
        if (video.paused) {
            video.onplay = () => {
                if (camOptions.rotate === 0 || camOptions.rotate === 180) {
                    canvas.width = video.offsetWidth;
                    canvas.height = video.offsetHeight;
                };
                if (camOptions.rotate === 90 || camOptions.rotate === 270) {
                    canvas.width = video.offsetHeight;
                    canvas.height = video.offsetWidth;
                };
            };
        } else {
            if (camOptions.rotate === 0 || camOptions.rotate === 180) {
                canvas.width = video.offsetWidth;
                canvas.height = video.offsetHeight;
            };
            if (camOptions.rotate === 90 || camOptions.rotate === 270) {
                canvas.width = video.offsetHeight;
                canvas.height = video.offsetWidth;
            };
        };
    };

    //События по нажатия на кнопки управления
    const rotateLeftButton = document.getElementById('rotate-left-button');
    if (rotateLeftButton) {
        rotateLeftButton.addEventListener('click', () => {
            const calcAngle = camOptions.rotate - 90 * ((camOptions.horizontalInvert ? -1 : 1) * (camOptions.verticalInvert ? -1 : 1));
            camOptions.rotate = calcAngle;
            if (calcAngle < 0) camOptions.rotate = 270;
            if (calcAngle > 270) camOptions.rotate = 0;
            setVideoContainer();
        });
    };
    const rotateRightButton = document.getElementById('rotate-right-button');
    if (rotateRightButton) {
        rotateRightButton.addEventListener('click', () => {
            const calcAngle = camOptions.rotate + 90 * ((camOptions.horizontalInvert ? -1 : 1) * (camOptions.verticalInvert ? -1 : 1));
            camOptions.rotate = calcAngle;
            if (calcAngle < 0) camOptions.rotate = 270;
            if (calcAngle > 270) camOptions.rotate = 0;
            setVideoContainer();
        });
    };
    const horizontalInvertButton = document.getElementById('horizontal-invert-button');    
    if (horizontalInvertButton) {
        horizontalInvertButton.style.filter = camOptions.horizontalInvert ? 'invert(1)' : '';
        horizontalInvertButton.addEventListener('click', () => {
            camOptions.horizontalInvert = !camOptions.horizontalInvert;
            horizontalInvertButton.style.filter = camOptions.horizontalInvert ? 'invert(1)' : '';
            setVideoContainer();
        });
    };
    const verticalInvertButton = document.getElementById('vertical-invert-button');
    if (verticalInvertButton) {
        verticalInvertButton.style.filter = camOptions.verticalInvert ? 'invert(1)' : '';
        verticalInvertButton.addEventListener('click', () => {
            camOptions.verticalInvert = !camOptions.verticalInvert;
            verticalInvertButton.style.filter = camOptions.verticalInvert ? 'invert(1)' : '';
            setVideoContainer();
        });
    };
});