package myhr.facescan.myhr_facescan

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.util.Log
import androidx.annotation.NonNull

import io.flutter.embedding.engine.plugins.FlutterPlugin
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import java.io.ByteArrayOutputStream
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import myhr.facescan.myhr_facescan.models.FaceTemplate

/** MyhrFacescanPlugin */
class MyhrFacescanPlugin : FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel
    private var matchJob: Job? = null
    private var detectJob: Job? = null
    private var attributeJob: Job? = null

    override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "myhr_facescan")
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(call: MethodCall, result: Result) {
        if (call.method == "activateLuxand") {
            result.success(activateLuxand(call.arguments))
        } else if (call.method == "detectFace") {
            detectFace(call.arguments, result)
        } else if (call.method == "getFaceTemplate") {
            val templates = getFaceTemplate(call.arguments)
            val data: Map<String, ByteArray> = mapOf(
                "eye" to templates[0],
                "face" to templates[1]
            )

            result.success(data)
        } else if (call.method == "getFaceAttribute") {
            getFaceAttribute(call.arguments, result)
        } else if (call.method == "detectFaceForMatch") {
            detectFaceForMatch(call.arguments, result)
        } else if (call.method == "setAvailableTemplate") {
            val key = call.arguments as Map<String, String>
            val ids = key["ids"] as List<String>
            val eyes = key["eyes"] as List<ByteArray>
            val faces = key["faces"] as List<ByteArray>
            val data =
                ids.mapIndexed { index, s -> FaceTemplate(s, eyes.get(index), faces.get(index)) }

            LuxandSdk.SetAvailableTempalte(data)
            result.success(true)
        } else if (call.method == "freeResource") {
            LuxandSdk.FreeTracker()
            result.success(true)
        } else {
            result.notImplemented()
        }
    }

    private fun activateLuxand(data: Any): Int {
        val key = data as Map<String, String>
        val licenseKey = key["licenseKey"] as String
        val libCode = LuxandSdk.Initialize(licenseKey);

        return libCode;
    }

    private fun getImageBytes(data: Any): ByteArray {
        val key = data as Map<String, String>
        val bytesList = key["platforms"] as List<ByteArray>;
        val strides = key["strides"] as IntArray;
        val width = key["width"] as Int;
        val height = key["height"] as Int;
        val imageBytes = YuvConverter.NV21toJPEG(
            YuvConverter.YUVtoNV21(
                bytesList, strides, width, height
            ), width, height, 80
        )

        return imageBytes
    }

    private fun detectFace(data: Any, result: Result) {
        if (detectJob?.isActive == false || detectJob == null) {
            detectJob = CoroutineScope(Dispatchers.Main).launch {
                val bytes = withContext(Dispatchers.Default) {
                    val imageBytes = getImageBytes(data)
                    val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
                    bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100)
                }

                withContext(Dispatchers.Default) {
                    val faces = LuxandSdk.TrackFace(bytes)

                    result.success(faces)
                }
            }
        }
    }

    private fun detectFaceForMatch(data: Any, result: Result) {
        if (matchJob?.isActive == false || matchJob == null) {
            matchJob = CoroutineScope(Dispatchers.Main).launch {
                val bytes = withContext(Dispatchers.Default) {
                    val imageBytes = getImageBytes(data)
                    val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
                    bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100)
                }

                val key = data as Map<String, String>
                val ids = key["ids"] as List<String>
                val eyes = key["eyes"] as List<ByteArray>
                val faces = key["faces"] as List<ByteArray>
                val models = mutableListOf<FaceTemplate>()

                for (index in ids.indices) {
                    models.add(FaceTemplate(ids[index], eyes.get(index), faces.get(index)))
                }

                withContext(Dispatchers.Default) {
                    val matches = LuxandSdk.TrackFaceForMatchTemplate(bytes, models)
                    val matchesString = mutableListOf<String>()

                    for (m in matches) {
                        matchesString.add(
                            m.FaceId + "(,)" + m.SimilarityEye + "(,)" + m.SimilarityFace + "(,)" +
                                    m.Position.CenterX + "(,)" + m.Position.CenterY + "(,)" + m.Position.Width
                        )
                    }

                    result.success(matchesString)
                }
            }
        }
    }

    private fun getFaceTemplate(data: Any): Array<ByteArray> {
        val imageBytes = getImageBytes(data)
        val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
        val result = bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100)
        val templates = LuxandSdk.GetTemplate(result)

        return templates
    }

    private fun getFaceAttribute(data: Any, result: Result) {
        if (attributeJob?.isActive == false || attributeJob == null) {
            attributeJob = CoroutineScope(Dispatchers.Main).launch {
                val bytes = withContext(Dispatchers.Default) {
                    val imageBytes = getImageBytes(data)
                    val rotateBitmap = rotateImage(byteArrayToBitmap(imageBytes), 270f)
                    bitmapToByteArray(rotateBitmap, Bitmap.CompressFormat.JPEG, 100)
                }

                val key = data as Map<String, String>
                val isExpression = key["isExpression"] as Boolean;
                val attr = LuxandSdk.GetFaceAttribute(bytes, isExpression)

                result.success(attr)
            }
        }
    }

    fun bitmapToByteArray(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int): ByteArray? {
        val stream = ByteArrayOutputStream()
        if (!bitmap.compress(format, quality, stream)) {
            return null
        }
        return stream.toByteArray()
    }

    fun byteArrayToBitmap(byteArray: ByteArray): Bitmap {
        return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
    }

    fun rotateImage(bitmap: Bitmap, degrees: Float): Bitmap {
        val matrix = Matrix()
        matrix.postRotate(degrees)
        matrix.postScale(-1f, 1f, bitmap.width / 2f, bitmap.height / 2f);

        val rotatedBitmap = Bitmap.createBitmap(
            bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
        )

//        val aspectRatio = rotatedBitmap.width.toDouble() / rotatedBitmap.height
//        val width = 480
//        val height = Math.round(width / aspectRatio).toInt()
//
//        val final = Bitmap.createScaledBitmap(rotatedBitmap, width, height, false)

        return rotatedBitmap
    }

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