package com.dlc.exam.camera

import android.app.Activity
import android.graphics.*
import android.hardware.Camera
import android.os.Environment
import android.view.TextureView
import com.arcsoft.face.*
import com.arcsoft.face.enums.CompareModel
import com.arcsoft.face.enums.DetectFaceOrientPriority
import com.arcsoft.face.enums.DetectMode
import com.dlc.exam.arcsoft.model.DrawInfo
import com.dlc.exam.arcsoft.util.DrawHelper
import com.dlc.exam.arcsoft.util.camera.CameraHelper
import com.dlc.exam.arcsoft.util.camera.CameraListener
import com.dlc.exam.arcsoft.util.face.RecognizeColor
import com.dlc.exam.arcsoft.widget.FaceRectView
import com.dlc.exam.common.Constants
import com.rc.core.log.RcLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.lang.Exception
import java.lang.ref.WeakReference

/**
 * info
 *
 * @author ReiChin_
 */
class FaceEngineHelper(
    private val activityRef: WeakReference<Activity>,
    private val previewView: TextureView,
    private val faceRectView: FaceRectView? = null,
    private val callback: EngineCallback? = null
) : CameraListener {

    private var faceEngine: FaceEngine? = null
    private var afCode: Int = -1

    private var cameraHelper: CameraHelper? = null
    private var drawHelper: DrawHelper? = null
    private var previewSize: Camera.Size? = null

    fun initEngineAndCamera() {
        initEngine()
        initCamera()
    }

    fun releaseEngineAndCamera() {
        cameraHelper?.let {
            it.release()
            cameraHelper = null
        }

        if (afCode == ErrorInfo.MOK) {
            afCode = faceEngine?.unInit() ?: -1
            RcLog.info("[FaceEngineHelper]unInitEngine: $afCode")
            faceEngine = null
        }
    }

    private fun initEngine() {
        val activity = activityRef.get() ?: return

        faceEngine = FaceEngine()
        /**
         * detectMode：
         *   1) VIDEO模式：处理连续帧的图像数据
         *   2) IMAGE模式：处理单张的图像数据
         * detectFaceOrientPriority：
         *   人脸检测角度，推荐单一角度检测
         * detectFaceScaleVal：
         *   1) 识别的最小人脸比例（图片长边与人脸框长边的比值）
         *   2) VIDEO模式取值范围[2,32]，推荐值为16
         *   3) IMAGE模式取值范围[2,32]，推荐值为32
         * detectFaceMaxNum：
         *   最大需要检测的人脸个数，取值范围[1,50]
         * combinedMask：
         *   需要启用的功能组合，可多选
         */
        val combinedMask = FaceEngine.ASF_FACE_RECOGNITION.or(FaceEngine.ASF_FACE_DETECT)
            .or(FaceEngine.ASF_LIVENESS)
        afCode = faceEngine!!.init(
//            activity, DetectMode.ASF_DETECT_MODE_VIDEO, DetectFaceOrientPriority.ASF_OP_0_ONLY,
//            16, 10, combinedMask
            activity, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY,
            16, 10, combinedMask
        )
        RcLog.info("[FaceEngineHelper]initEngine:  init: $afCode")
        if (afCode != ErrorInfo.MOK) {
            callback?.onError("引擎初始化失败，错误码为 ${ArcSoftErrCode.decode(afCode)}", OperType.INIT_ENGINE)
        }
    }

    private fun initCamera() {
//        DisplayMetrics metrics = new DisplayMetrics();
//        getWindowManager().getDefaultDisplay().getMetrics(metrics);

        val activity = activityRef.get() ?: return

        cameraHelper = CameraHelper.Builder()
            .previewViewSize(
                Point(
                    previewView.measuredWidth,
                    previewView.measuredHeight
                )
            )
            .rotation(activity.windowManager.defaultDisplay.rotation)
            .specificCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT)
            .isMirror(false)
            .previewOn(previewView)
            .cameraListener(this)
            .build().apply {
                init()
                start()
            }
    }

    override fun onCameraOpened(
        camera: Camera?,
        cameraId: Int,
        displayOrientation: Int,
        isMirror: Boolean
    ) {
        RcLog.info("[FaceEngineHelper]onCameraOpened: $cameraId  $displayOrientation  $isMirror")

        previewSize = camera!!.parameters.previewSize
        drawHelper =
            DrawHelper(
                previewSize!!.width,
                previewSize!!.height,
                previewView.width,
                previewView.height,
                displayOrientation,
                cameraId,
                isMirror,
                false,
                false
            )
    }

    private var isDetecting = false
    private var isComparing = false

    private var stopPreview = true

    var isVisibleToUser = false

    fun stopPreview(stop: Boolean) {
        stopPreview = stop
    }

    override fun onPreview(nv21: ByteArray?, camera: Camera?) {
//        RcLog.warn("OKHttp EngineHelper onPreview enter. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}, isVisibleToUser=${isVisibleToUser}")

        if (stopPreview or isDetecting or isComparing or !isVisibleToUser) return

        GlobalScope.launch(Dispatchers.Main) {
            RcLog.warn("OKHttp EngineHelper onPreview start. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}")

            isDetecting = true
            val nv21Clone = nv21!!.clone()
            faceRectView?.clearFaceInfo()
            val width = previewSize!!.width
            val height = previewSize!!.height

            val faceInfoList = ArrayList<FaceInfo>()

            RcLog.warn("OKHttp EngineHelper detectFaces start. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}")
            var code = detectFaces(nv21Clone, width, height, faceInfoList)
            if (null != code && verifyDetect(code, faceInfoList)) {
                RcLog.warn("OKHttp EngineHelper process start. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}")
                code = process(nv21Clone, width, height, faceInfoList)
                if (code == ErrorInfo.MOK) {
                    val faceLivenessInfoList = ArrayList<LivenessInfo>()
                    RcLog.warn("OKHttp EngineHelper getLiveness start. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}")
                    if (getLiveness(faceLivenessInfoList)) {
                        // 获取视频流的特征值
                        val faceFeature = FaceFeature()
                        RcLog.warn("OKHttp EngineHelper extractFaceFeature start. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}")
                        if (extractFaceFeature(
                                nv21,
                                width,
                                height,
                                keepMaxFace(faceInfoList),
                                faceFeature,
                                OperType.DETECT
                            )
                        ) {
                            RcLog.warn("OKHttp EngineHelper callback onFaceFeature start. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}")
                            callback?.onFaceFeature(faceFeature, callback = {
                                isComparing = it
                            })
                            RcLog.warn("OKHttp EngineHelper callback onFaceFeature end. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}")
                        }

//                        generateFaceFile(nv21Clone, width, height)?.let { file ->
//                            callback?.onFaceFile(file, callback = {
//                                isComparing = it
//                            })
//                        }

//                        callback?.onLivenessSuccess(
//                            nv21Clone,
//                            width,
//                            height,
//                            keepMaxFace(faceInfoList),
//                            callback = {
//                                isComparing = it
//                            }
//                        )

                        drawFaceRect(faceInfoList, faceLivenessInfoList)
                    }
                } else {
                    val info = "人脸属性检测失败，错误码：${ArcSoftErrCode.decode(code)}"
                    RcLog.info("[FaceEngineHelper]${info}")
                    callback?.onError(info, OperType.DETECT)
                }
            }
            isDetecting = false
            RcLog.warn("OKHttp EngineHelper onPreview end. stopPreview=${stopPreview}, isDetecting=${isDetecting}, isComparing=${isComparing}")
        }
    }

    private fun keepMaxFace(faceList: ArrayList<FaceInfo>): FaceInfo? {
        if (faceList.isNullOrEmpty()) return null

        var maxFaceInfo = faceList[0]
        faceList.forEach { item ->
            if (item.rect.width() > maxFaceInfo.rect.width()) {
                maxFaceInfo = item
            }
        }
        return maxFaceInfo
    }

    private suspend fun getLiveness(faceLivenessInfoList: ArrayList<LivenessInfo>): Boolean {
        try {
            /**
             * 获取RGB活体信息。
             *   livenessInfoList: 检测到的活体信息
             */
            val livenessCode = withContext(Dispatchers.IO) {
                faceEngine!!.getLiveness(faceLivenessInfoList)
            }

            // mock代码，不进行活体检测，直接返回true
            if (!Constants.Mock.FACE_LIVENESS) return true

            if (livenessCode != ErrorInfo.MOK) {
                val info = "活体检测失败，错误码：${ArcSoftErrCode.decode(livenessCode)}"
                RcLog.info("[FaceEngineHelper]${info}")
                callback?.onError(info, OperType.DETECT)
                return false
            } else {
                if (faceLivenessInfoList.isEmpty()) {
                    val info = "非活体，无返回数据：faceLivenessInfoList is empty."
                    RcLog.info("[FaceEngineHelper]${info}")
                    callback?.onError("非活体", OperType.DETECT)
                    return false
                }

                val liveness = faceLivenessInfoList[0].liveness
                RcLog.info("[FaceEngineHelper]liveness:${liveness}")
                when (liveness) {
                    LivenessInfo.NOT_ALIVE -> {
                        val info = "非活体"
                        RcLog.info("[FaceEngineHelper]${info}")
                        callback?.onError(info, OperType.DETECT)
                        return false
                    }
                    LivenessInfo.ALIVE -> return true
                    LivenessInfo.FACE_NUM_MORE_THAN_ONE -> {
                        val info = "检查到多张人脸"
                        RcLog.info("[FaceEngineHelper]${info}")
                        callback?.onError(info, OperType.DETECT)
                        return false
                    }
                    else -> {
                        val info = "活体检测未知信息 $liveness"
                        RcLog.info("[FaceEngineHelper]${info}")
                        callback?.onError(info, OperType.DETECT)
                        return false
                    }
                }
            }
        } catch (e: Exception) {
            return false
        }
    }

    private fun drawFaceRect(
        faceInfoList: ArrayList<FaceInfo>,
        faceLivenessInfoList: ArrayList<LivenessInfo>
    ) {
        if (null != drawHelper) {
            faceRectView?.let { view ->
                val drawInfoList = faceInfoList.mapIndexed { index, faceInfo ->
                    DrawInfo(
                        drawHelper!!.adjustRect(
                            faceInfo.rect
                        ),
                        GenderInfo.UNKNOWN,
                        AgeInfo.UNKNOWN_AGE,
                        faceLivenessInfoList[index].liveness,
                        RecognizeColor.COLOR_UNKNOWN,
                        null
                    )
                }
                drawHelper!!.draw(view, drawInfoList)
            }
        }
    }

    private fun verifyDetect(code: Int, faceInfoList: ArrayList<FaceInfo>): Boolean {
        if (code == ErrorInfo.MOK) {
            if (faceInfoList.size > 0) {
                return true
            } else {
                val info = "未检测到人脸"
                RcLog.info("[FaceEngineHelper]${info}")
                callback?.onError(info, OperType.DETECT)
            }
        } else {
            val info = "人脸检测失败，错误码：${ArcSoftErrCode.decode(code)}"
            RcLog.info("[FaceEngineHelper]${info}")
            callback?.onError(info, OperType.DETECT)
        }
        return false
    }

    private suspend fun detectFaces(
        nv21: ByteArray,
        width: Int,
        height: Int,
        faceInfoList: ArrayList<FaceInfo>
    ): Int? {
        return withContext(Dispatchers.IO) {
            try {
                /**
                 * 人脸检测(传入分离的图像信息数据)
                 *  data: 图像数据
                 *  width: 图像宽度，为4的倍数
                 *  height: 图像高度，在NV21格式下要求为2的倍数； BGR24/GRAY/DEPTH_U16格式无限制；
                 *  format: 图像的颜色格式
                 *  faceInfoList: 检测到的人脸信息
                 */
                faceEngine!!.detectFaces(
                    nv21,
                    width,
                    height,
                    FaceEngine.CP_PAF_NV21,
                    faceInfoList
                )
            } catch (e: Exception) {
                null
            }
        }
    }

    private suspend fun process(
        nv21: ByteArray,
        width: Int,
        height: Int,
        faceInfoList: ArrayList<FaceInfo>
    ): Int? {
        return withContext(Dispatchers.IO) {
            try {
                /**
                 * 人脸属性检测
                 *   data: 图像数据
                 *   width: 图像宽度，为4的倍数
                 *   height: 图像高度，在NV21格式下要求为2的倍数； BGR24格式无限制；
                 *   format: 支持NV21/BGR24
                 *   faceInfoList: 检测到的人脸信息
                 *   combinedMask: 检测到的人脸信息
                 *     1.检测的属性（ASF_AGE、ASF_GENDER、 ASF_FACE3DANGLE、ASF_LIVENESS），支持多选
                 *     2.检测的属性须在引擎初始化接口的combinedMask参数中启用
                 */
                faceEngine!!.process(
                    nv21,
                    width,
                    height,
                    FaceEngine.CP_PAF_NV21,
                    faceInfoList,
                    FaceEngine.ASF_LIVENESS
                )
            } catch (e: Exception) {
                null
            }
        }
    }

    /**
     * 保存人脸照片
     */
    private suspend fun generateFaceFile(nv21: ByteArray, width: Int, height: Int): File? {
        return withContext(Dispatchers.IO) {
            try {
                val image = YuvImage(nv21, ImageFormat.NV21, width, height, null)
                val outputStream = ByteArrayOutputStream()
                val faceRect = Rect(0, 0, width, height)
                image.compressToJpeg(faceRect, 90, outputStream)
                val jpegData = outputStream.toByteArray()
                val options = BitmapFactory.Options()
                options.inSampleSize = 1
                val nv21Bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size)
                val matrix = Matrix()
                val matriculatedBitmap = Bitmap.createBitmap(
                    nv21Bitmap,
                    0,
                    0,
                    nv21Bitmap.width,
                    nv21Bitmap.height,
                    matrix,
                    true
                )
                val checkedBitmap = ImageUtils.alignBitmapForNv21(matriculatedBitmap);
                saveBitmapToFile(
                    checkedBitmap,
                    Environment.getExternalStorageDirectory().path + "/check_face.jpg"
                )
            } catch (e: Exception) {
                null
            }
        }
    }

    private fun saveBitmapToFile(bitmap: Bitmap?, filePath: String?): File? {
        if (bitmap == null || filePath.isNullOrEmpty()) {
            return null
        }
        try {
            val file = File(filePath)

            if (!file.exists() /*&& file.parentFile?.mkdirs() == false*/ && !file.createNewFile()) {
                return null
            }
            val os = BufferedOutputStream(FileOutputStream(file))
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os)
            os.close()
            return file
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
    }

    /**
     * 人脸特征值比对
     *
     * @param nv21 图像数据
     * @param width 图片宽度，为4的倍数
     * @param height 图片高度，在NV21格式下要求为2的倍数；BGR24/GRAY/DEPTH_U16格式无限制；
     * @param faceInfo 人脸信息（人脸框、人脸角度）
     * @param baseFaceFeature base图片特征信息
     */
    fun compareFaceFeature(
        nv21: ByteArray,
        width: Int,
        height: Int,
        faceInfo: FaceInfo?,
        baseFaceFeature: FaceFeature
    ) {
        // 特征值提取
        GlobalScope.launch(Dispatchers.Main) {
            // 获取视频流的特征值
            val faceFeature = FaceFeature()
            if (extractFaceFeature(nv21, width, height, faceInfo, faceFeature, OperType.COMPARE)) {
                val faceSimilar = FaceSimilar()
                if (compareFaceFeature(
                        baseFaceFeature,
                        faceFeature,
                        faceSimilar,
                        OperType.COMPARE
                    )
                ) {
                    isComparing = false
                    RcLog.info("[FaceEngineHelper]人脸比对结果，相似度: ${faceSimilar.score}")
                    if (faceSimilar.score >= Constants.ArcFace.FACE_SIMILAR_SCORE) {
                        // 人脸比对成功
                        callback?.onCompareSuccess()
                    } else {
                        callback?.onError("不是同一人", OperType.COMPARE)
                    }
                }
            }
            isComparing = false
        }
    }

    private suspend fun compareFaceFeature(
        baseFaceFeature: FaceFeature,
        faceFeature: FaceFeature,
        faceSimilar: FaceSimilar,
        type: OperType
    ): Boolean {
        try {
            val compareCode = withContext(Dispatchers.IO) {
                faceEngine!!.compareFaceFeature(
                    baseFaceFeature,
                    faceFeature,
                    CompareModel.ID_CARD,
                    faceSimilar
                )
            }
            if (compareCode != ErrorInfo.MOK) {
                isComparing = false
                val errorMsg = "人脸特征值比对失败，错误码 ${ArcSoftErrCode.decode(compareCode)}"
                RcLog.info("[FaceEngineHelper]${errorMsg}")
                callback?.onError(errorMsg, type)
                return false
            }
            return true
        } catch (e: Exception) {
            return false
        }
    }

    private suspend fun extractFaceFeature(
        nv21: ByteArray,
        width: Int,
        height: Int,
        faceInfo: FaceInfo?,
        faceFeature: FaceFeature,
        type: OperType
    ): Boolean {
        try {
            val extractFaceFeatureCode = withContext(Dispatchers.IO) {
                faceEngine!!.extractFaceFeature(
                    nv21,
                    width,
                    height,
                    FaceEngine.CP_PAF_NV21,
                    faceInfo,
                    faceFeature
                )
            }
            if (extractFaceFeatureCode != ErrorInfo.MOK) {
                isComparing = false
                val errorMsg = "人脸特征提取失败，错误码 ${ArcSoftErrCode.decode(extractFaceFeatureCode)}"
                RcLog.info("[FaceEngineHelper]${errorMsg}")
                callback?.onError(errorMsg, type)
                return false
            }
            return true
        } catch (e: Exception) {
            return false
        }
    }

    override fun onCameraClosed() {
        RcLog.info("[FaceEngineHelper]onCameraClosed: ")
    }

    override fun onCameraError(e: Exception?) {
        e?.printStackTrace()
        val info = "onCameraError: ${e?.message}"
        RcLog.info("[FaceEngineHelper]${info}")
        callback?.onError(info, OperType.INIT_ENGINE)
    }

    override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) {
        drawHelper?.cameraDisplayOrientation = displayOrientation
        RcLog.info("[FaceEngineHelper]onCameraConfigurationChanged: $cameraID  $displayOrientation")
    }

    interface EngineCallback {
        fun onLivenessSuccess(
            nv21: ByteArray,
            width: Int,
            height: Int,
            faceInfo: FaceInfo?,
            callback: (comparing: Boolean) -> Unit
        )

        fun onFaceFile(
            file: File,
            callback: (comparing: Boolean) -> Unit
        )

        fun onFaceFeature(
            faceFeature: FaceFeature,
            callback: (comparing: Boolean) -> Unit
        )

        fun onCompareSuccess()
        fun onError(errMsg: String, type: OperType)
    }

    enum class OperType {
        INIT_ENGINE, // 初始化引擎
        DETECT,
        COMPARE
    }

}