import { EventEmitter } from 'ee-ts';
// @ts-ignore
import { debug, version, UA, WebSocketInterface } from 'jssip/lib-es5/JsSIP';
import { Log } from '../../log';
import { Session } from './session';
export class SipPhone extends EventEmitter {
    constructor(instanceId, d) {
        super();
        this.instanceId = instanceId;
        this.type = 'webrtc';
        this.sessionCache = new Map();
        this.log = new Log();
        if (d) {
            this.log.info(`JsSip version: ${version}`);
            debug.enable('*');
        }
        else {
            debug.disable();
        }
        this.on('unregistered', () => {
            this.sessionCache.clear();
        });
    }
    async callOption(req = {}) {
        const mediaStream = await this.getUserCallStream(req);
        return {
            sessionTimersExpires: 120,
            pcConfig: req.disableStun
                ? undefined
                : {
                    iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }],
                },
            mediaStream,
        };
    }
    async answer(id, req) {
        if (this.sessionCache.has(id)) {
            const session = this.sessionCache.get(id);
            const params = await this.callOption(req);
            return session.answer(params);
        }
        return false;
    }
    async call(req) {
        const params = {};
        const display = {
            extraHeaders: new Array(),
        };
        if (req.params) {
            params.audio = req.params.audio;
            params.video = req.params.video || false;
            params.screen = req.params.screen || false;
            params.disableStun = req.params.disableStun || false;
        }
        if (typeof req.destination !== 'string') {
            throw new Error('bad destination type');
        }
        const invite = await this.callOption(params);
        if (req.params) {
            if (req.params.display) {
                display.extraHeaders.push(`X-Webitel-Display: ${req.params.display}`);
            }
            if (req.params.contactId) {
                display.extraHeaders.push(`X-Webitel-Contact-Id: ${req.params.contactId}`);
            }
        }
        const forceHighQuality = async (sender) => {
            try {
                const parameters = sender.getParameters();
                if (!parameters.encodings || parameters.encodings.length === 0) {
                    parameters.encodings = [{}];
                }
                // parameters.encodings[0].scaleResolutionDownBy = 1.0;
                parameters.encodings[0].priority = 'high';
                parameters.encodings[0].networkPriority = 'high';
                parameters.encodings[0].maxBitrate = 4000 * 100 * 1000; // 4000kbps
                // parameters.encodings[0].scalabilityMode = 'L1T3';
                parameters.degradationPreference = 'maintain-resolution';
                await sender.setParameters(parameters);
            }
            catch (e) {
                this.log.error(`SipPhone Error: ${e}`);
            }
        };
        await this.ua.call(req.destination, {
            ...invite,
            ...display,
            eventHandlers: {
                peerconnection: async (data) => {
                    const pc = data.peerconnection;
                    pc.addEventListener('track', async (event) => {
                        if (event.track.kind === 'video') {
                            const sender = pc
                                .getSenders()
                                // @ts-ignore
                                .find((s) => s.track && s.track.kind === 'video');
                            if (sender) {
                                await forceHighQuality(sender);
                            }
                        }
                    });
                },
            },
        });
    }
    async register(sipConf) {
        const socket = new WebSocketInterface(sipConf.server);
        const configuration = {
            realm: sipConf.realm,
            uri: sipConf.uri,
            authorization_user: sipConf.authorization_user,
            ha1: sipConf.ha1,
            user_agent: SipPhone.userAgent,
            sockets: [socket],
            session_timers: true,
            // use_preloaded_route: true,
            display_name: sipConf.display_name,
            register_expires: sipConf.register_expires || 90,
            connection_recovery_min_interval: 2,
            connection_recovery_max_interval: 30,
            // instance_id: '8f1fa16a-1165-4a96-8341-785b1ef24f13',
        };
        const ua = (this.ua = new UA(configuration));
        // ua.on('connected', (e: object) => {})
        ua.on('newRTCSession', (e) => {
            const session = e.session;
            const id = session.id;
            const callSession = new Session(session, e.request);
            this.storeSession(id, callSession);
            session.on('ended', () => {
                // this handler will be called for incoming calls too
                this.removeSession(callSession);
            });
            session.on('failed', () => {
                // this handler will be called for incoming calls too
                this.removeSession(callSession);
            });
            session.on('accepted', (a) => {
                // the call has answered
                this.setupMedia(callSession, session.connection);
            });
            session.on('progress', () => {
                // the call has pre answer
                this.setupMedia(callSession, session.connection);
            });
            session.on('confirmed', () => {
                this.setupMedia(callSession, session.connection);
            });
            session.on('newInfo', (ev) => {
                if (ev.originator !== 'remote') {
                    return;
                }
                let body = null;
                try {
                    body = JSON.parse(ev.request.body);
                }
                catch (e) {
                    return;
                }
                if (body) {
                    callSession.remoteVideoMuted = !!body.videoMuted;
                    callSession.remoteAudioMuted = !!body.audioMuted;
                    callSession.remoteHold = !!body.hold;
                    this.emit('info', callSession);
                }
            });
            this.emit('newSession', callSession);
        });
        ua.on('disconnected', (e) => {
            this.emit('unregistered');
        });
        // ua.on('registered', (e: object) => {})
        ua.on('unregistered', (e) => {
            this.emit('unregistered');
        });
        ua.on('registrationFailed', (e) => {
            this.log.error('registrationFailed', e);
        });
        ua.on('registered', (e) => {
            this.emit('registered');
        });
        // fixes WTEL-4236
        ua.on('connected', (e) => {
            this.emit('connected');
        });
        ua.start();
    }
    async unregister() {
        if (this.ua) {
            this.ua.terminateSessions();
            this.ua.removeAllListeners();
            await this.ua.stop();
            delete this.ua;
        }
    }
    isRegistered() {
        if (this.ua) {
            return this.ua.isRegistered();
        }
        return false;
    }
    sipSessionByCallId(id) {
        for (const sess of this.allSession) {
            if (sess.callId === id) {
                return sess;
            }
        }
        return null;
    }
    sipSessionBySipId(id) {
        for (const sess of this.allSession) {
            if (sess.id.startsWith(id)) {
                return sess;
            }
        }
        return null;
    }
    removeSession(s) {
        if (this.sessionCache.has(s.id)) {
            this.sessionCache.delete(s.id);
            s.getLocalMedia().forEach((m) => {
                m.getTracks().forEach((track) => track.stop());
            });
            s.getPeerMedia().forEach((m) => {
                m.getTracks().forEach((track) => track.stop());
            });
            return true;
        }
        return false;
    }
    storeSession(id, session) {
        if (this.sessionCache.has(id)) {
            throw new Error('Session already store');
        }
        this.sessionCache.set(id, session);
    }
    get allSession() {
        return Array.from(this.sessionCache.values());
    }
    getMediaConstraints(req) {
        if (req.screen) {
            return {
                video: false,
                audio: false,
                screen: true,
            };
        }
        return {
            video: req.video || false,
            audio: true,
        };
    }
    getSession(id) {
        if (this.sessionCache.has(id)) {
            return this.sessionCache.get(id);
        }
        return null;
    }
    hasSession(id) {
        return this.sessionCache.has(id);
    }
    setupMedia(sess, connection) {
        if (!connection) {
            return;
        }
        if (!sess.peerStream && connection.getRemoteStreams) {
            const peerMedia = connection.getRemoteStreams();
            sess.peerStream = peerMedia[0];
            if (peerMedia.length > 1) {
                this.log.warn('more than 1 peer stream');
            }
            this.emit('peerStreams', sess, sess.getPeerMedia());
        }
        if (!sess.localStream && connection.getLocalStreams) {
            const localMedia = connection.getLocalStreams();
            sess.localStream = localMedia[0];
            if (localMedia.length > 1) {
                this.log.warn('more than 1 local stream');
            }
            this.emit('localStreams', sess, sess.getLocalMedia());
        }
    }
    async getUserScreen() {
        return new Promise(async (resolve, reject) => {
            try {
                const stream = (await navigator.mediaDevices.getDisplayMedia());
                resolve(stream);
            }
            catch (e) {
                reject(e);
            }
        });
    }
    async getUserCallStream(req) {
        let stream;
        if (req.screen === true) {
            stream = await this.getUserScreen();
        }
        if (req.audio !== false || req.video) {
            const camera = await this.getUserMedia(req.audio, req.video);
            if (stream) {
                camera.getTracks().forEach((track) => {
                    stream.addTrack(track);
                });
            }
            else {
                stream = camera;
            }
        }
        if (!stream) {
            throw new Error('bad stream request');
        }
        return stream;
    }
    async getUserMedia(audio, video) {
        return new Promise(async (resolve, reject) => {
            const mediaConstraints = {
                audio: undefined,
                video: undefined,
            };
            if (!(audio === false)) {
                // TODO
                // mediaConstraints.audio = {
                //   autoGainControl: false,
                // }
                mediaConstraints.audio = true;
            }
            if (video) {
                mediaConstraints.video = {
                    width: { ideal: 1280 },
                    height: { ideal: 720 },
                };
            }
            return getMediaStream(mediaConstraints)
                .then(resolve)
                .catch(reject);
        });
    }
}
SipPhone.userAgent = `Webitel-Phone/${version}`;
SipPhone.sipVersion = version;
async function getMediaStream(constraints) {
    return new Promise((resolve, reject) => {
        if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
            navigator.mediaDevices
                .getUserMedia(constraints)
                .then((stream) => resolve(stream))
                .catch((err) => reject(err));
        }
        else {
            return reject(new Error('getUserMedia is not supported in this browser'));
        }
    });
}
//# sourceMappingURL=index.js.map