package xyz.nuark.liblinphone_flutter

import android.app.Activity
import android.util.Log
import android.util.SparseArray
import android.view.TextureView
import android.view.View
import androidx.core.util.isNotEmpty
import androidx.core.util.size
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import org.linphone.mediastream.video.capture.CaptureTextureView
import xyz.nuark.liblinphone_flutter.views.LocalViewFactory
import xyz.nuark.liblinphone_flutter.views.RemoteViewFactory

/** LiblinphoneFlutterPlugin */
class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler {
    private lateinit var channel: MethodChannel

    private var registrationEventsChannel: EventChannel.EventSink? = null
    private var callEventsChannel: EventChannel.EventSink? = null

    private lateinit var activity: Activity

    private var remoteViewCache = SparseArray<View>(1);
    private var localViewCache = SparseArray<View>(1);

    private lateinit var linphoneBridge: LinphoneBridge

    override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        flutterPluginBinding
            .platformViewRegistry
            .registerViewFactory("liblinphone_flutter.nuark.xyz/remote_view", RemoteViewFactory {
                Log.i(TAG, "onAttachedToEngine: caching RemoteView ${it.id}")
                remoteViewCache.put(0, it)
            })
        flutterPluginBinding
            .platformViewRegistry
            .registerViewFactory("liblinphone_flutter.nuark.xyz/local_view", LocalViewFactory {
                Log.i(TAG, "onAttachedToEngine: caching LocalView ${it.id}")
                localViewCache.put(0, it)
            })

        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "liblinphone_flutter")
        channel.setMethodCallHandler(this)

        EventChannel(
            flutterPluginBinding.binaryMessenger,
            "liblinphone_flutter.nuark.xyz/registration_events"
        ).setStreamHandler(object : EventChannel.StreamHandler {
            override fun onListen(
                arguments: Any?,
                events: EventChannel.EventSink?
            ) {
                registrationEventsChannel = events
            }

            override fun onCancel(arguments: Any?) {
                registrationEventsChannel = null
            }

        })

        EventChannel(
            flutterPluginBinding.binaryMessenger,
            "liblinphone_flutter.nuark.xyz/call_events"
        ).setStreamHandler(object : EventChannel.StreamHandler {
            override fun onListen(
                arguments: Any?,
                events: EventChannel.EventSink?
            ) {
                callEventsChannel = events
            }

            override fun onCancel(arguments: Any?) {
                callEventsChannel = null
            }
        })
    }

    override fun onMethodCall(
        call: MethodCall,
        result: Result
    ) {
        when (call.method) {
            "checkPermissions" -> {
                result.success(linphoneBridge.checkPermissions())
            }

            "initialize" -> {
                try {
                    linphoneBridge = LinphoneBridge(
                        activity,
                        this::acquireRemoteView,
                        this::acquireLocalView,
                        { registrationState ->
                            registrationEventsChannel?.success(registrationState)
                        },
                        { callState ->
                            callEventsChannel?.success(callState)
                        }
                    )
                    linphoneBridge.initializeLinphone()
                    result.success(true)
                } catch (e: Exception) {
                    Log.e(TAG, "initialize: ${e.message}")
                    result.error("error", e.message, e)
                }
            }

            "register" -> {
                try {
                    val username = call.argument<String>("username")!!
                    val password = call.argument<String>("password")!!
                    val serverIp = call.argument<String>("serverIp")!!
                    val serverPort = call.argument<Int>("serverPort")!!
                    linphoneBridge.register(username, password, serverIp, serverPort)
                    result.success(true)
                } catch (e: Exception) {
                    Log.e(TAG, "register: ${e.message}")
                    result.error("error", e.message, e)
                }
            }

            "unregister" -> {
                linphoneBridge.unregister()
                result.success(true)
            }

            "makeCall" -> {
                try {
                    val callTo = call.argument<String>("callTo")!!
                    val isVideoEnabled = call.argument<Boolean>("isVideoEnabled")!!
                    linphoneBridge.makeCall(callTo, isVideoEnabled)
                    result.success(true)
                } catch (e: Exception) {
                    Log.e(TAG, "makeCall: ${e.message}")
                    result.error("error", e.message, e)
                }
            }

            "answerCall" -> {
                val res = linphoneBridge.answerCall()
                result.success(res)
            }

            "hangupCall" -> {
                val res = linphoneBridge.hangupCall()
                result.success(res)
            }

            "inCall" -> {
                val inCall = linphoneBridge.inCall()
                result.success(inCall)
            }

            "callType" -> {
                val callType = linphoneBridge.callType()
                result.success(callType.ordinal)
            }

            "toggleVideo" -> {
                val enabled = linphoneBridge.toggleVideo()
                result.success(enabled)
            }

            "toggleMicrophone" -> {
                val enabled = linphoneBridge.toggleMicrophone()
                result.success(enabled)
            }

            "stop" -> {
                linphoneBridge.stop()
                result.success(true)
            }

            else -> {
                result.notImplemented()
            }
        }
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {
        activity = binding.activity
    }

    override fun onDetachedFromActivityForConfigChanges() {
    }

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
        activity = binding.activity
    }

    override fun onDetachedFromActivity() {
    }

    internal fun acquireLocalView(): CaptureTextureView? {
        Log.i(TAG, "acquireLocalView: ${localViewCache.size}")
        if (localViewCache.isNotEmpty()) {
            val widget = localViewCache.get(0)
            return widget as CaptureTextureView
        }
        return null
    }

    internal fun acquireRemoteView(): TextureView? {
        Log.i(TAG, "acquireRemoteView: ${remoteViewCache.size}")
        if (remoteViewCache.isNotEmpty()) {
            val widget = remoteViewCache.get(0)
            return widget as TextureView
        }
        return null
    }

    companion object {
        const val TAG = "LiblinphoneFlutterPlugin"
    }
}
