package xyz.nuark.liblinphone_flutter

import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.util.Log
import android.view.TextureView
import androidx.core.content.ContextCompat
import org.linphone.core.Account
import org.linphone.core.Call
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.Factory
import org.linphone.core.MediaDirection
import org.linphone.core.PayloadType
import org.linphone.core.ProxyConfig
import org.linphone.core.RegistrationState
import org.linphone.mediastream.video.capture.CaptureTextureView
import kotlin.properties.Delegates

class LinphoneBridge(
    private val activity: Activity,
    private val remoteViewAcquisitor: () -> TextureView?,
    private val localViewAcquisitor: () -> CaptureTextureView?,
    private val onRegistrationStateChanged: (Int) -> Unit,
    private val onCallStateChanged: (Int) -> Unit
) {
    private lateinit var core: Core
    private var isRegistered = false
    private var currentCall: Call? = null

    private var registrationState: RegistrationState by Delegates.observable(RegistrationState.None) { _, oldValue, newValue ->
        Log.i(
            LiblinphoneFlutterPlugin.TAG,
            "registrationState delegate: oldValue: $oldValue, newValue: $newValue"
        )
        onRegistrationStateChanged(newValue.ordinal)
    }
    private var callState: Call.State by Delegates.observable(Call.State.Idle) { _, oldValue, newValue ->
        Log.i(
            LiblinphoneFlutterPlugin.TAG,
            "callState delegate: oldValue: $oldValue, newValue: $newValue"
        )
        onCallStateChanged(newValue.ordinal)
    }

    private val coreListener = object : CoreListenerStub() {
//        override fun onRegistrationStateChanged(
//            core: Core,
//            proxyConfig: ProxyConfig,
//            state: RegistrationState,
//            message: String
//        ) {
//            registrationState = state
//            when (state) {
//                RegistrationState.Ok -> {
//                    isRegistered = true
//                }
//
//                RegistrationState.Failed -> {
//                    isRegistered = false
//                }
//
//                RegistrationState.None -> {
//                    isRegistered = false
//                }
//
//                RegistrationState.Progress -> {
//                    isRegistered = false
//                }
//
//                else -> {}
//            }
//        }

        override fun onAccountRegistrationStateChanged(
            core: Core,
            account: Account,
            state: RegistrationState?,
            message: String
        ) {
            registrationState = state ?: RegistrationState.None
            when (state) {
                RegistrationState.Ok -> {
                    isRegistered = true
                }

                RegistrationState.Failed -> {
                    isRegistered = false
                }

                RegistrationState.None -> {
                    isRegistered = false
                }

                RegistrationState.Progress -> {
                    isRegistered = false
                }

                else -> {}
            }
        }

        override fun onCallStateChanged(
            core: Core,
            call: Call,
            state: Call.State,
            message: String
        ) {
            callState = state
            when (state) {
                Call.State.IncomingReceived -> {
                    currentCall = call
                    Log.i(
                        LiblinphoneFlutterPlugin.TAG,
                        "onCallStateChanged: INCOMING_RECEIVED $call"
                    )
                }

                Call.State.Connected -> {
                    Log.i(LiblinphoneFlutterPlugin.TAG, "onCallStateChanged: CONNECTED $call")

                    remoteViewAcquisitor()?.let {
                        call.nativeVideoWindowId = it
                    } ?: run {
                        Log.i(
                            LiblinphoneFlutterPlugin.TAG,
                            "onCallStateChanged: CONNECTED remoteViewAcquisitor found nothing"
                        )
                    }
                    localViewAcquisitor()?.let {
                        core.nativePreviewWindowId = it
                    } ?: run {
                        Log.i(
                            LiblinphoneFlutterPlugin.TAG,
                            "onCallStateChanged: CONNECTED localViewAcquisitor found nothing"
                        )
                    }
                }

                Call.State.StreamsRunning -> {
                    Log.i(LiblinphoneFlutterPlugin.TAG, "onCallStateChanged: STREAMS_RUNNING $call")
                    remoteViewAcquisitor()?.let {
                        call.nativeVideoWindowId = it
                    } ?: run {
                        Log.i(
                            LiblinphoneFlutterPlugin.TAG,
                            "onCallStateChanged: STREAMS_RUNNING remoteViewAcquisitor found nothing"
                        )
                    }
                    localViewAcquisitor()?.let {
                        core.nativePreviewWindowId = it
                    } ?: run {
                        Log.i(
                            LiblinphoneFlutterPlugin.TAG,
                            "onCallStateChanged: STREAMS_RUNNING localViewAcquisitor found nothing"
                        )
                    }
                }

                Call.State.End -> {
                    currentCall = null
                }

                Call.State.Error -> {
                    currentCall = null
                }

                Call.State.Released -> {
                    currentCall = null
                }

                else -> {}
            }
        }
    }

    private val neededPermissions = arrayOf(
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.CAMERA,
        Manifest.permission.MODIFY_AUDIO_SETTINGS
    )

    fun checkPermissions(): Boolean {
        val missingPermissions = neededPermissions.filter { permission ->
            ContextCompat.checkSelfPermission(
                activity,
                permission
            ) != PackageManager.PERMISSION_GRANTED
        }.toTypedArray()
        if (missingPermissions.isNotEmpty()) {
            activity.requestPermissions(missingPermissions, 1)
            return false
        }

        return true
    }

    fun initializeLinphone() {
        val factory = Factory.instance()
        core = factory.createCore(null, null, activity.baseContext)
        core.addListener(coreListener)

        // Enable video
        core.isVideoCaptureEnabled = true
        core.isVideoDisplayEnabled = true
        core.videoActivationPolicy.automaticallyInitiate = true
        core.videoActivationPolicy.automaticallyAccept = true

        // https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus1000/sw/4_0/qos/configuration/guide/nexus1000v_qos/qos_6dscp_val.pdf
        core.sipDscp = 26 // AF31
        core.audioDscp = 46 // EF
        core.videoDscp = 36 // AF42

        core.isVideoAdaptiveJittcompEnabled = true
//        core.isEchoCancellationEnabled = true // eh results, bad for audio

        // good enough default
        core.uploadBandwidth = 512
        core.downloadBandwidth = 1500

        val preferredAudio = listOf("opus", "pcmu", "pcma") // in order of preference
        val preferredVideo = listOf("h264", "vp8")

        core.audioPayloadTypes.forEach { pt: PayloadType ->
            val mime = pt.mimeType.lowercase()
            val enable = preferredAudio.contains(mime)
            pt.enable(enable)
        }
        core.videoPayloadTypes.forEach { pt: PayloadType ->
            val mime = pt.mimeType.lowercase()
            val enable = preferredVideo.contains(mime)
            pt.enable(enable)
        }

        core.getPayloadType("opus", -1, 0)?.let { it.normalBitrate = 32 }   // 32 kbps for Opus
        core.getPayloadType("h264", -1, 0)?.let { it.normalBitrate = 600 }  // 600 kbps for H264
        core.getPayloadType("vp8", -1, 0)?.let { it.normalBitrate = 600 }   // 600 kbps for VP8

        val preferredVidDef = factory.createVideoDefinition(720, 1280)
        core.preferredVideoDefinition = preferredVidDef
        core.preferredFramerate = 30f

        core.start()
    }

    fun register(username: String, password: String, serverIp: String, serverPort: Int) {
        val factory = Factory.instance()
        val authInfo = factory.createAuthInfo(username, null, password, null, null, serverIp)
        core.addAuthInfo(authInfo)

        val identity = factory.createAddress("sip:$username@$serverIp")
        val server = factory.createAddress("sip:$serverIp:$serverPort")

        val accountParams = core.createAccountParams().let {
            it.identityAddress = identity
            it.serverAddress = server
            it.isRegisterEnabled = true
            it
        }

        val account = core.createAccount(accountParams)
        core.addAccount(account)
        core.defaultAccount = account
    }

    fun unregister() {
        core.clearAccounts()
        core.clearAllAuthInfo()
    }

    fun makeCall(callTo: String, isVideoEnabled: Boolean) {
        if (!isRegistered) {
            throw IllegalStateException("Not registered")
        }

        val factory = Factory.instance()
        val remoteAddress = factory.createAddress(callTo)
        val callParams = core.createCallParams(null)
        if (remoteAddress == null) {
            throw IllegalStateException("Failed to create remote address")
        }
        if (callParams == null) {
            throw IllegalStateException("Failed to create call params")
        }

        callParams.let {
            it.isVideoEnabled = isVideoEnabled
            it.videoDirection =
                if (isVideoEnabled) MediaDirection.SendRecv else MediaDirection.Inactive
        }

        currentCall = core.inviteAddressWithParams(remoteAddress, callParams)
    }

    fun answerCall(): Boolean {
        val res = currentCall?.let { call ->
            val callParams = core.createCallParams(call)
            callParams?.let {
                it.isVideoEnabled = true
                it.videoDirection = MediaDirection.SendRecv
            }
            call.acceptWithParams(callParams)

            return@let true
        }

        return res ?: false
    }

    fun hangupCall(): Boolean {
        return currentCall?.terminate() == 0
    }

    fun toggleVideo(): Boolean {
        currentCall?.let { call ->
            val params = core.createCallParams(call)
            params?.isVideoEnabled = !call.currentParams.isVideoEnabled
            call.update(params)
        }

        return currentCall?.currentParams?.isVideoEnabled == true
    }

    fun toggleMicrophone(): Boolean {
        core.isMicEnabled = !core.isMicEnabled
        return core.isMicEnabled
    }

    fun stop() {
        core.stop()
    }

    fun inCall(): Boolean {
        return currentCall != null
    }

    enum class CallType {
        Audio,
        Video,
        Unknown
    }

    fun callType(): CallType {
        if (currentCall?.currentParams != null) {
            if (currentCall?.currentParams?.isVideoEnabled == true) {
                return CallType.Video
            } else {
                return CallType.Audio
            }
        }
        if (currentCall?.currentParams?.isVideoEnabled == true) {
            return CallType.Video
        }
        return CallType.Unknown
    }
}