
import {
    EventEmitter
} from 'events'
import jssip from 'jssip'

let $baseWebSocketUrl = null
let $subscriber = null
let $socket = null
let $userAgent = null
let $isVideoScreen = false
let $outgoingRtcSession = null
let $incomingRtcSession = null
let $localMediaStream = null
let $remoteMediaStream = null
let $videoTransceiver = null
let $audioTransceiver = null

const TERMINATION_OPTIONS = {
    status_code: 603,
    reason_phrase: 'Decline'
}

const VIDEO_MAX_FRAME_RATE = 30
const VIDEO_IDEAL_WIDTH = 1920
const VIDEO_IDEAL_HEIGHT = 1080

function callGetVideoConstraints () {
    const supported = navigator?.mediaDevices?.getSupportedConstraints()
    const constraints = {}
    if (supported.frameRate) {
        constraints.frameRate = {
            max: VIDEO_MAX_FRAME_RATE
        }
    }
    constraints.width = {
        ideal: VIDEO_IDEAL_WIDTH
    }
    constraints.height = {
        ideal: VIDEO_IDEAL_HEIGHT
    }
    return constraints
}

function callGetCameraConstraints () {
    const supported = navigator?.mediaDevices?.getSupportedConstraints()
    const constraints = callGetVideoConstraints()
    if (supported.facingMode) {
        constraints.facingMode = 'user'
    }
    return constraints
}

function callGetScreenConstraints () {
    return callGetVideoConstraints()
}

export const callEvent = new EventEmitter()

function callTrackMuteHandler () {
    if ($audioTransceiver) {
        $remoteMediaStream = new MediaStream([
            $audioTransceiver.receiver.track
        ])
        callEvent.emit('remoteStream', $remoteMediaStream)
    }
}

function callTrackUnMuteHandler () {
    if ($audioTransceiver && $videoTransceiver) {
        $remoteMediaStream = new MediaStream([
            $audioTransceiver.receiver.track,
            $videoTransceiver.receiver.track
        ])
        callEvent.emit('remoteStream', $remoteMediaStream)
    }
}

function handleRemoteMediaStream ({ transceiver }) {
    if (!$audioTransceiver && transceiver.receiver.track.kind === 'audio') {
        $audioTransceiver = transceiver
    } else if (!$videoTransceiver && transceiver.receiver.track.kind === 'video') {
        $videoTransceiver = transceiver
        $videoTransceiver.receiver.track.onmute = callTrackMuteHandler
        $videoTransceiver.receiver.track.onunmute = callTrackUnMuteHandler
    }
    const tracks = []
    if ($audioTransceiver) {
        tracks.push($audioTransceiver.receiver.track)
    }
    if ($videoTransceiver) {
        tracks.push($videoTransceiver.receiver.track)
    }
    $remoteMediaStream = new MediaStream(tracks)
    callEvent.emit('remoteStream', $remoteMediaStream)
}

function getSubscriberUri () {
    return 'sip:' + $subscriber.username + '@' + $subscriber.domain
}

function callCreateSocket () {
    return new jssip.WebSocketInterface($baseWebSocketUrl + '/' + $subscriber.username)
}

export function callConfigure ({ baseWebSocketUrl }) {
    $baseWebSocketUrl = baseWebSocketUrl
}

export async function callInitialize ({ subscriber, instanceId }) {
    $subscriber = subscriber
    callRegister({ instanceId })
}

export function callRegister ({ instanceId }) {
    if (!$socket) {
        $socket = callCreateSocket()
        const config = {
            sockets: [$socket],
            uri: getSubscriberUri(),
            password: $subscriber.password,
            instance_id: instanceId
        }
        $userAgent = new jssip.UA(config)
        const delegateEvent = (eventName) => {
            $userAgent.on(eventName, (event) => callEvent.emit(eventName, event))
        }
        delegateEvent('connected')
        delegateEvent('disconnected')
        delegateEvent('newRTCSession')
        delegateEvent('newMessage')
        delegateEvent('registered')
        delegateEvent('unregistered')
        delegateEvent('registrationFailed')
        $userAgent.on('newRTCSession', (event) => {
            if (event.originator === 'remote') {
                if ($incomingRtcSession || $outgoingRtcSession) {
                    event.session.terminate({
                        status_code: 486,
                        reason_phrase: 'Busy'
                    })
                } else {
                    $incomingRtcSession = event.session
                    $incomingRtcSession.on('peerconnection', () => {
                        $incomingRtcSession.connection.ontrack = handleRemoteMediaStream
                    })
                    $incomingRtcSession.on('failed', (failedEvent) => {
                        callEvent.emit('incomingFailed', failedEvent)
                    })
                    $incomingRtcSession.on('ended', (failedEvent) => {
                        callEvent.emit('incomingEnded', failedEvent)
                        $incomingRtcSession = null
                    })
                    callEvent.emit('incoming', $incomingRtcSession)
                    $incomingRtcSession.on('hold', (holdEvent) => {
                        callEvent.emit('incomingHold', holdEvent)
                    })
                    $incomingRtcSession.on('unhold', (unholdEvent) => {
                        callEvent.emit('incomingUnHold', unholdEvent)
                    })
                    $incomingRtcSession.on('refer', (event) => {
                        callEvent.emit('incomingRefer', event)
                    })
                }
            }
        })
        $userAgent.start()
    }
}

export function callUnregister () {
    if ($userAgent) {
        $userAgent.unregister({
            all: true
        })
    }
}

export async function callStart ({ number }) {
    try {
        $localMediaStream = await callCreateLocalAudioStream()
        callEvent.emit('localStream', $localMediaStream)
        $outgoingRtcSession = $userAgent.call(number, {
            eventHandlers: {
                progress (event) {
                    if (event.response.status_code === 183) {
                        callEvent.emit('outgoingProgress', event)
                    } else {
                        callEvent.emit('outgoingRinging', event)
                    }
                },
                failed (event) {
                    callEvent.emit('outgoingFailed', event)
                },
                confirmed (event) {
                    callEvent.emit('outgoingConfirmed', event)
                },
                ended (event) {
                    callEvent.emit('outgoingEnded', event)
                    $outgoingRtcSession = null
                },
                hold (event) {
                    if (event.originator === 'local') {
                        callEvent.emit('outgoingHold', event)
                    } else {
                        callEvent.emit('outgoingHolded', event)
                    }
                },
                unhold (event) {
                    if (event.originator === 'local') {
                        callEvent.emit('outgoingUnHold', event)
                    } else {
                        callEvent.emit('outgoingUnHolded', event)
                    }
                },
                refer (event) {
                    callEvent.emit('outgoingRefer', event)
                }

            },
            mediaStream: $localMediaStream
        })
        $outgoingRtcSession.connection.ontrack = handleRemoteMediaStream
        return true
    } catch (e) {
        return false
    }
}

export async function callAccept () {
    $localMediaStream = await callCreateLocalAudioStream()
    callEvent.emit('localStream', $localMediaStream)
    if ($incomingRtcSession) {
        $incomingRtcSession.answer({
            mediaStream: $localMediaStream
        })
    }
}

export async function callCreateLocalAudioStream () {
    return await navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true
    })
}

export function callGetRemoteMediaStream () {
    return $remoteMediaStream
}

export function callGetRemoteMediaStreamId () {
    return $remoteMediaStream?.id
}

export function callGetLocalMediaStream () {
    return $localMediaStream
}

export function callGetLocalMediaStreamId () {
    return $localMediaStream?.id
}

export function callGetRtcSession () {
    if ($outgoingRtcSession) {
        return $outgoingRtcSession
    } else if ($incomingRtcSession) {
        return $incomingRtcSession
    }
}

export function callGetRtcConnection () {
    return callGetRtcSession().connection
}

async function callStopVideo () {
    if ($videoTransceiver && $localMediaStream) {
        $localMediaStream.removeTrack($videoTransceiver.sender.track)
        $videoTransceiver.sender.track.stop()
        $videoTransceiver.direction = 'recvonly'
        await $videoTransceiver.sender.replaceTrack(null)
        await callRenegotiate()
    }
}

export async function callSendVideo (stream, audioMuted) {
    const videoTrack = stream.getVideoTracks()[0]
    if ($videoTransceiver?.sender?.track) {
        $localMediaStream.removeTrack($videoTransceiver.sender.track)
        $videoTransceiver.sender.track.stop()
    }
    $localMediaStream.addTrack(videoTrack)
    callEvent.emit('localStream', $localMediaStream)
    if (!$videoTransceiver) {
        $videoTransceiver = callGetRtcConnection().addTransceiver(videoTrack, { direction: 'sendrecv' })
        $videoTransceiver.receiver.track.onmute = callTrackMuteHandler
        $videoTransceiver.receiver.track.onunmute = callTrackUnMuteHandler
    } else {
        $videoTransceiver.direction = 'sendrecv'
        await $videoTransceiver.sender.replaceTrack(videoTrack)
    }
    await callRenegotiate()
}

export async function callAddCamera () {
    $isVideoScreen = false
    await callSendVideo(await navigator.mediaDevices.getUserMedia({
        video: callGetCameraConstraints(),
        audio: false
    }))
}

export async function callAddScreen () {
    $isVideoScreen = true
    await callSendVideo(await navigator.mediaDevices.getDisplayMedia({
        video: callGetScreenConstraints(),
        audio: false
    }))
}

export async function callRemoveVideo () {
    $isVideoScreen = false
    await callStopVideo()
}

export async function callRenegotiate () {
    callGetRtcSession().renegotiate({
        useUpdate: false
    }, () => {
        callEvent.emit('renegotiationSucceeded')
    })
}

export function callHasRemoteVideo () {
    return $videoTransceiver?.receiver?.track?.enabled
}

export function callHasLocalVideo () {
    return $videoTransceiver?.sender?.track?.enabled
}

export function callHasLocalCamera () {
    return callHasLocalVideo() && !$isVideoScreen
}

export function callHasLocalScreen () {
    return callHasLocalVideo() && $isVideoScreen
}

export function callToggleMicrophone () {
    if ($audioTransceiver?.sender?.track) {
        $audioTransceiver.sender.track.enabled = !$audioTransceiver.sender.track.enabled
    }
}

export function callMute () {
    return callGetRtcSession()?.mute()
}

export function callUnMute () {
    return callGetRtcSession()?.unmute()
}

export function callIsMuted () {
    if ($audioTransceiver?.sender?.track) {
        return !$audioTransceiver.sender.track.enabled
    }
    return false
}

export function callSendDTMF (tone, transport = 'RFC2833') {
    const rtcSession = callGetRtcSession()
    if (rtcSession) {
        rtcSession.sendDTMF(tone, {
            transportType: transport
        })
    }
}

/**
 * Enables or disables the remote audio depending on the current state.
 */
export function callToggleRemoteAudio () {
    if ($audioTransceiver?.receiver?.track) {
        $audioTransceiver.receiver.track.enabled = !$audioTransceiver.receiver.track.enabled
    }
}

export function callMuteRemote () {
    if ($audioTransceiver?.receiver?.track) {
        $audioTransceiver.receiver.track.enabled = false
    }
}

export function callUnMuteRemote () {
    if ($audioTransceiver?.receiver?.track) {
        $audioTransceiver.receiver.track.enabled = true
    }
}

/**
 * Checks whether remote audio is muted or not.
 * @returns {boolean}
 */
export function callIsRemoteMuted () {
    return !$audioTransceiver?.receiver?.track?.enabled
}

/**
 * Terminates the call if not ended and cleans up all related resources.
 */
export function callEnd () {
    const rtcSession = callGetRtcSession()
    if (rtcSession && !rtcSession.isEnded()) {
        rtcSession.terminate(TERMINATION_OPTIONS)
    }
    try {
        if ($localMediaStream) {
            $localMediaStream.getTracks().forEach(track => track.stop())
        }
    } finally {
        $localMediaStream = null
    }
    try {
        if ($remoteMediaStream) {
            $remoteMediaStream.getTracks().forEach(track => track.stop())
        }
    } finally {
        $remoteMediaStream = null
    }
    $outgoingRtcSession = null
    $incomingRtcSession = null
    $audioTransceiver = null
    $videoTransceiver = null
}
/**
 * Hold on.
 */
export function callToggleHold () {
    const rtcSession = callGetRtcSession()
    if (rtcSession) {
        if (rtcSession.isOnHold().local) {
            rtcSession.unhold()
        } else {
            rtcSession.hold()
        }
    }
}
/**
 * Blind Transfer.
 */
export function callBlindTransfer (numberToTransfer) {
    return new Promise((resolve, reject) => {
        const rtcSession = callGetRtcSession()
        const eventHandlers = {
            requestFailed: function (e) {
                console.log('Transfer failed')
                reject(new Error('Transfer failed'))
            },
            requestSucceeded: function (e) {
                console.log('Success', e)
                resolve(true)
            }
        }
        try {
            rtcSession.refer(numberToTransfer, {
                eventHandlers
            })
        } catch (err) {
            reject(err)
        }
    })
}
