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

const fs = require('fs');
const path = require('path');

let incomingVideo = null; //видеоконтейнер для входящего видеострима
let selfVideo = null; //видеоконтейнер для видео с фронтальной камеры
let incomingAudio;

let bodyHeight = '0px';
let bodyWidth = '0px';

//Выводит сообщение на страницу
const showMessage = (type, ...args) => {
    args = ['[neuro-phone]', ...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 messageContainer = document.getElementById('message-container');
    if (messageElem && messageContainer) {
        messageContainer.classList.add('show');
        messageElem.innerText += '\n\n' + message;
        messageElem.classList = type;
        messageContainer.scrollTo(0, messageContainer.scrollHeight);
    }
};

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

const getUserPhoto = async (width, height) => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = width;
    canvas.height = height;
    ctx.fillRect(0, 0, width, height);
    ctx.drawImage(selfVideo, 0, 0, width, height);
    const userPhoto = await canvas.toDataURL('image/png');
    canvas.remove();
    return userPhoto;
};

//Устанавливаем прослушку на события модуля neuroPhone
neuroPhone.on('info', (info) => {
    log.info(info);
});
neuroPhone.on('warn', (warn) => {
    log.warn(warn);
});
neuroPhone.on('error', (error) => {
    log.error(error);
});
neuroPhone.on('inited', (data) => {
    log.info('Модуль NeuroPhone успешно инициализирован. Настройки: ', data);
});
neuroPhone.on('busy', (data) => {
    log.info(`${data}`);
});
neuroPhone.on('call-decline', () => {
    log.info(`Оператор сбросил вызов`);
    if (incomingVideo) incomingVideo.srcObject = null;
    if (incomingAudio) incomingAudio.srcObject = null;
});
neuroPhone.on('busy-operator-found', (number) => {
    log.info(`Подобран занятый оператор ${number ? '. Номер в очереди: ' + number : ''}`);
});
neuroPhone.on('update-queue', (number) => {
    log.info(`Номер в очереди изменён ${number ? '. Теперь номер: ' + number : ''}`);
});
neuroPhone.on('wait-number', () =>
    log.info('Подождите в очереди, пока не появится доступный номер для звонка'),
);

//изменение громкости микрофона во время разговора
neuroPhone.on('change-volume', (micVolume) => {
    incomingVideo.volume = micVolume;
    log.info('громкость микрофона: ', micVolume);
});

neuroPhone.on('mute', () => {
    if (incomingAudio) {
        incomingAudio.muted = !incomingAudio.muted;
    }
    if (incomingVideo) {
        incomingVideo.muted = !incomingVideo.muted;
        if (incomingVideo.muted) {
            log.info('Оператор отключил микрофон');
        } else {
            log.info('Оператор включил микрофон');
        }
    }
});

neuroPhone.on('hide-video', () => {
    const hideVideo = document.getElementById('hide-video');
    if (hideVideo) {
        hideVideo.classList.toggle('show');
        if (hideVideo.classList.contains('show')) {
            log.info('Оператор отключил видео');
        } else {
            log.info('Оператор включил видео');
        }
    }
});
neuroPhone.on('call-not-possible', (data) => {
    log.info('Невозможно совершить звонок. Причина:', data);
});
neuroPhone.on('incoming-stream', (stream) => {
    const callButton = document.getElementById('call-button');
    const callButtonIncomingVideo = document.getElementById('call-button__incoming-video');
    const selfVideoContainer = document.getElementById('self-video-container');

    document.getElementById('incoming-video-container').classList.add('move-center-image');
    document.getElementById('self-video-container').classList.add('move-center-image');
    callButton.style.display = 'none';
    callButtonIncomingVideo.style.display = 'block';

    //убираем селф видео в сторону
    if (selfVideoContainer.classList.contains('move-center-image')) {
        selfVideoContainer.style.height = selfVideoContainer.offsetWidth / 2.8 + 'px';
    }

    if (incomingVideo) {
        incomingVideo.srcObject = stream;
        incomingVideo.muted = false;
        if (incomingVideo.srcObject)
            incomingVideo.play().catch((error) => {
                log.error('Не удалось запустить входящий видеострим. Ошибка:', error);
            });
    } else {
        log.error(
            'Не удалось запустить входящий видеострим. Ошибка: Не найден видеоконтейнер для входящего видеострима',
        );
    }
});
neuroPhone.on('incoming-audio', (audioStream) => {
    console.log('audioStream: ', audioStream);
    incomingAudio.srcObject = audioStream;
});

neuroPhone.on('call-ended', () => {
    const incomingVideoContainer = document.getElementById('incoming-video-container');
    const selfVideoContainer = document.getElementById('self-video-container');
    selfVideoContainer.style.height = incomingVideoContainer.style.height;

    document.getElementById('self-video-container').classList.remove('move-center-image');
    const hideVideo = document.getElementById('hide-video');
    hideVideo.classList.remove('show');
    log.info('Вызов завершён');
    incomingVideo.srcObject = null;
    incomingAudio.srcObject = null;
    const callButton = document.getElementById('call-button');
    callButton.style.display = 'block';

    if (callButton) {
        callButton.classList.remove('call');
    }
});

//Подключаемся к neuroPhone
const deviceConfig = require('./neuro-home-device-config');
neuroPhone.init({
    serverIp: '87.242.78.60',
    serverPort: '8088',
    platformId: deviceConfig.device.place,
    platformName: 'Тестовая платформа',
    clientType: 'receiver', //receiver - клиент, provider - оператор
    deviceName: deviceConfig.device.device_name,
    camRotate: 'none',
    camInvertY: true,

    floor: 1,
    positionX: 1590,
    positionY: 1925,
    orientationX: -1,
    orientationY: 0,
});

//Подписываемся на события сообщений
cameras.on('info', (info) => {
    log.info(info);
});
cameras.on('error', (error) => {
    log.error(error);
});
//Подключение камер
document.addEventListener('DOMContentLoaded', () => {
    //Следим за изменением размера body
    bodyHeight = document.body.offsetHeight + 'px';
    bodyWidth = document.body.offsetWidth + 'px';
    const resizeObserver = new ResizeObserver(() => {
        bodyHeight = document.body.offsetHeight + 'px';
        bodyWidth = document.body.offsetWidth + 'px';
        setVideoContainer();
    });
    resizeObserver.observe(document.body);

    incomingVideo = document.getElementById('incoming-video');
    incomingAudio = document.getElementById('incoming-audio');
    const selfVideoContainer = document.getElementById('self-video-container');
    const incomingVideoContainer = document.getElementById('incoming-video-container');
    selfVideo = document.getElementById('self-video');
    const camSelect = document.getElementById('camera-name');
    const callButton = document.getElementById('call-button');
    const callButtonIncomingVideo = document.getElementById('call-button__incoming-video');
    const callTypeSelect = document.querySelector('#call-type-select input');

    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) => {
                                callButton.classList.remove('show');
                                log.error(error);
                            });
                            camera.on('stop', (camera) => {
                                neuroPhone.setSelfStream();
                                callButton.classList.remove('show');
                                log.info(
                                    `Стрим с камеры "${camera.name}" (${camera.id}) успешно остановлен`,
                                );
                            });
                            camera.on('disconnect', (camera) => {
                                neuroPhone.setSelfStream();
                                callButton.classList.remove('show');
                                log.error(
                                    `Связь с камерой "${camera.name}" (${camera.id}) потеряна`,
                                );
                            });

                            //Подписываемся на событие подключения к камере
                            camera.on('stream', (stream) => {
                                callButton.classList.add('show');
                                if (selfVideo) {
                                    //Поворачиваем видео элемент по заданным настройкам
                                    setVideoContainer();
                                    selfVideo.onplay = () => {
                                        neuroPhone.setSelfStream(stream);
                                    };
                                    //Вещаем стрим в видеоэлемент видео с фронтальной камеры
                                    selfVideo.srcObject = stream;
                                    selfVideo.muted = true;
                                } else {
                                    log.error('HTML элемент видео "camera" не найден');
                                }
                            });

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

                            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 || 'Неизвестная ошибка',
            );
        });

    //сброс звонка в момент разговора
    callButtonIncomingVideo.addEventListener('click', () => {
        neuroPhone.declineCall();
        callButton.style.display = 'block';
        callButtonIncomingVideo.style.display = 'none';
    });

    // вызов оператора и сброс в момент вызова
    callButton.addEventListener('click', async () => {
        if (callButton.classList.contains('call')) {
            callButton.classList.remove('call');
            neuroPhone.declineCall();
        } else {
            callButton.classList.add('call');
            if (callTypeSelect.checked) {
                neuroPhone.call();
            } else {
                neuroPhone.call(false, await getUserPhoto(780, 780));
            }
        }
    });

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

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

        // selfVideo.style.transform = transform;
        // selfVideo.style.height = 'auto';

        // incomingVideo.style.transform = transform;
        // incomingVideo.style.height = 'auto';

        // setTimeout(() => {
        //     selfVideo.style.height = '100%';
        //     incomingVideo.style.height = '100%';
        // }, 100);
    };

    //События по нажатия на кнопки управления
    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();
        });
    }

    // //Перетаскивание круга с видео фронтальной камеры
    function handleMoveElemStart(e) {
        let firstTouchY = e.touches[0].clientY;
        let firstTouchX = e.touches[0].clientX;

        this.dataset.firstTouchY = firstTouchY;
        this.dataset.firstTouchX = firstTouchX;
        this.touchStartTime = new Date();
    }
    function handleMoveElem(e) {
        this.style.zIndex = 20;
        this.style.transitionDelay = '0s';
        this.style.transitionDuration = '0s';

        const elemY = this.getBoundingClientRect().y;
        const elemX = this.getBoundingClientRect().x;

        const offset = this.offsetHeight / 2;

        let style = window.getComputedStyle(this);
        let matrix = new WebKitCSSMatrix(style.webkitTransform);
        let translateX = matrix.m41;
        let translateY = matrix.m42;

        let newposX = e.touches[0].clientY - elemY - offset + translateY;
        let newposY = e.touches[0].clientX - elemX - offset + translateX;

        this.style.transform = `translate(${newposY}px, ${newposX}px)`;

        this.dataset.lastTouchY = e.touches[0].clientY;
        this.dataset.lastTouchX = e.touches[0].clientX;
    }
    function handleMoveElemEnd(e) {
        let firstTouchY = +this.dataset.firstTouchY;
        let firstTouchX = +this.dataset.firstTouchX;

        let lastTouchY = +this.dataset.lastTouchY;
        let lastTouchX = +this.dataset.lastTouchX;

        let touchDuration = (new Date().getTime() - this.touchStartTime.getTime()) / 1000; //секунды

        let touchLengthX = lastTouchX - firstTouchX;
        let touchSpeedX = Math.abs(touchLengthX / touchDuration);
        let touchLengthY = lastTouchY - firstTouchY;
        let touchSpeedY = Math.abs(touchLengthY / touchDuration);

        if (touchLengthX > 0 && touchSpeedX > 100 && touchDuration < 0.25 && touchDuration > 0.1) {
            const elemX = this.getBoundingClientRect().x;
            let style = window.getComputedStyle(this);
            let matrix = new WebKitCSSMatrix(style.webkitTransform);
            let translateX = matrix.m41;
            let translateY = matrix.m42;

            let deltaX = window.innerWidth - elemX + this.offsetWidth;
            let deltaY = touchLengthY / touchDuration;
            let flyAwayDurationX = Math.abs(deltaX / touchSpeedX);
            let flyAwayDurationY = Math.abs(deltaY / touchSpeedY);
            this.style.transform = `translate(${translateX + deltaX}px, ${translateY + deltaY}px)`;

            this.style.transitionDuration = `${(flyAwayDurationX + flyAwayDurationY) / 2}s`;
        }
        this.dataset.firstTouchY = '';
        this.dataset.firstTouchX = '';
        this.dataset.lastTouchY = '';
        this.dataset.lastTouchX = '';
    }

    //Ставим прослушку на события перетаскивания селф камеры
    selfVideoContainer.addEventListener('touchstart', handleMoveElemStart);
    selfVideoContainer.addEventListener('touchmove', handleMoveElem);
    selfVideoContainer.addEventListener('touchend', handleMoveElemEnd);
});
