'use strict';

const Logger = require('./Logger');
const log = new Logger('Peer');

module.exports = class Peer {
    constructor(socket_id, data) {
        const { peer_info } = data;

        const { peer_name, peer_presenter, peer_audio, peer_video, peer_video_privacy, peer_recording, peer_hand } =
            peer_info;

        this.id = socket_id;
        this.peer_info = peer_info;
        this.peer_name = peer_name;
        this.peer_presenter = peer_presenter;
        this.peer_audio = peer_audio;
        this.peer_video = peer_video;
        this.peer_video_privacy = peer_video_privacy;
        this.peer_recording = peer_recording;
        this.peer_hand = peer_hand;

        this.transports = new Map();
        this.consumers = new Map();
        this.producers = new Map();
    }

    // ####################################################
    // UPDATE PEER INFO
    // ####################################################

    updatePeerInfo(data) {
        log.debug('Update peer info', data);
        switch (data.type) {
            case 'audio':
            case 'audioType':
                this.peer_info.peer_audio = data.status;
                this.peer_audio = data.status;
                break;
            case 'video':
            case 'videoType':
                this.peer_info.peer_video = data.status;
                this.peer_video = data.status;
                if (data.status == false) {
                    this.peer_info.peer_video_privacy = data.status;
                    this.peer_video_privacy = data.status;
                }
                break;
            case 'screen':
            case 'screenType':
                this.peer_info.peer_screen = data.status;
                break;
            case 'hand':
                this.peer_info.peer_hand = data.status;
                this.peer_hand = data.status;
                break;
            case 'privacy':
                this.peer_info.peer_video_privacy = data.status;
                this.peer_video_privacy = data.status;
                break;
            case 'presenter':
                this.peer_info.peer_presenter = data.status;
                this.peer_presenter = data.status;
                break;
            case 'recording':
                this.peer_info.peer_recording = data.status;
                this.peer_recording = data.status;
                break;
            default:
                break;
        }
    }

    // ####################################################
    // TRANSPORT
    // ####################################################

    getTransports() {
        return JSON.parse(JSON.stringify([...this.transports]));
    }

    getTransport(transport_id) {
        return this.transports.get(transport_id);
    }

    delTransport(transport_id) {
        this.transports.delete(transport_id);
    }

    addTransport(transport) {
        this.transports.set(transport.id, transport);
    }

    async connectTransport(transport_id, dtlsParameters) {
        if (!this.transports.has(transport_id)) {
            return false;
        }

        await this.transports.get(transport_id).connect({
            dtlsParameters: dtlsParameters,
        });

        return true;
    }

    close() {
        this.transports.forEach((transport, transport_id) => {
            log.debug('Close and delete peer transports', {
                transport_id: transport_id,
                //transportInternal: transport.internal,
            });
            transport.close();
            this.delTransport(transport_id);
        });
    }

    // ####################################################
    // PRODUCER
    // ####################################################

    getProducers() {
        return JSON.parse(JSON.stringify([...this.producers]));
    }

    getProducer(producer_id) {
        return this.producers.get(producer_id);
    }

    delProducer(producer_id) {
        this.producers.delete(producer_id);
    }

    async createProducer(producerTransportId, producer_rtpParameters, producer_kind, producer_type) {
        try {
            if (!producerTransportId) {
                return 'Invalid producer transport ID';
            }

            const producerTransport = this.transports.get(producerTransportId);

            if (!producerTransport) {
                return `Producer transport with ID ${producerTransportId} not found`;
            }

            const producer = await producerTransport.produce({
                kind: producer_kind,
                rtpParameters: producer_rtpParameters,
            });

            if (!producer) {
                return `Producer type: ${producer_type} kind: ${producer_kind} not found`;
            }

            const { id, appData, type, kind, rtpParameters } = producer;

            appData.mediaType = producer_type;

            this.producers.set(id, producer);

            if (['simulcast', 'svc'].includes(type)) {
                const { scalabilityMode } = rtpParameters.encodings[0];
                const spatialLayer = parseInt(scalabilityMode.substring(1, 2)); // 1/2/3
                const temporalLayer = parseInt(scalabilityMode.substring(3, 4)); // 1/2/3
                log.debug(`Producer [${type}-${kind}] ----->`, {
                    scalabilityMode,
                    spatialLayer,
                    temporalLayer,
                });
            } else {
                log.debug('Producer ----->', { type: type, kind: kind });
            }

            producer.on('transportclose', () => {
                log.debug('Producer "transportclose" event');
                this.closeProducer(id);
            });

            return producer;
        } catch (error) {
            log.error('Error creating producer', error.message);
            return error.message;
        }
    }

    closeProducer(producer_id) {
        if (!this.producers.has(producer_id)) return;

        const producer = this.getProducer(producer_id);
        const { id, kind, type, appData } = producer;

        try {
            producer.close();
        } catch (error) {
            log.warn('Close Producer', error.message);
        }

        this.delProducer(producer_id);

        log.debug('Producer closed and deleted', {
            peer_name: this.peer_name,
            kind: kind,
            type: type,
            appData: appData,
            producer_id: id,
        });
    }

    // ####################################################
    // CONSUMER
    // ####################################################

    getConsumers() {
        return JSON.parse(JSON.stringify([...this.consumers]));
    }

    getConsumer(consumer_id) {
        return this.consumers.get(consumer_id);
    }

    delConsumer(consumer_id) {
        this.consumers.delete(consumer_id);
    }

    async createConsumer(consumer_transport_id, producer_id, rtpCapabilities) {
        try {
            if (!consumer_transport_id) {
                return 'Invalid consumer transport ID';
            }

            const consumerTransport = this.transports.get(consumer_transport_id);

            if (!consumerTransport) {
                return `Consumer transport with id ${consumer_transport_id} not found`;
            }

            const consumer = await consumerTransport.consume({
                producerId: producer_id,
                rtpCapabilities,
                enableRtx: true, // Enable NACK for OPUS.
                paused: false,
            });

            if (!consumer) {
                return `Consumer for producer ID ${producer_id} not found`;
            }

            const { id, type, kind, rtpParameters, producerPaused } = consumer;

            if (['simulcast', 'svc'].includes(type)) {
                // simulcast - L1T3/L2T3/L3T3 | svc - L3T3
                const { scalabilityMode } = rtpParameters.encodings[0];
                const spatialLayer = parseInt(scalabilityMode.substring(1, 2)); // 1/2/3
                const temporalLayer = parseInt(scalabilityMode.substring(3, 4)); // 1/2/3
                try {
                    await consumer.setPreferredLayers({
                        spatialLayer: spatialLayer,
                        temporalLayer: temporalLayer,
                    });
                    log.debug(`Consumer [${type}-${kind}] ----->`, {
                        scalabilityMode,
                        spatialLayer,
                        temporalLayer,
                    });
                } catch (error) {
                    return `Error to set Consumer preferred layers: ${error.message}`;
                }
            } else {
                log.debug('Consumer ----->', { type: type, kind: kind });
            }

            this.consumers.set(id, consumer);

            consumer.on('transportclose', () => {
                log.debug('Consumer "transportclose" event');
                this.removeConsumer(id);
            });

            return {
                consumer: consumer,
                params: {
                    producerId: producer_id,
                    id: id,
                    kind: kind,
                    rtpParameters: rtpParameters,
                    type: type,
                    producerPaused: producerPaused,
                },
            };
        } catch (error) {
            log.error('Error creating consumer', error.message);
            return error.message;
        }
    }

    removeConsumer(consumer_id) {
        if (!this.consumers.has(consumer_id)) return;

        const consumer = this.getConsumer(consumer_id);
        const { id, kind, type } = consumer;

        try {
            consumer.close();
        } catch (error) {
            log.warn('Close Consumer', error.message);
        }

        this.delConsumer(consumer_id);

        log.debug('Consumer closed and deleted', {
            peer_name: this.peer_name,
            kind: kind,
            type: type,
            consumer_id: id,
        });
    }
};
