package com.performancecoachlab.posedetection.recording

import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.ImageBitmap
import com.performancecoachlab.posedetection.camera.FrameProcessor
import com.performancecoachlab.posedetection.camera.drawAnalysisResults
import com.performancecoachlab.posedetection.camera.drawSkeleton
import com.performancecoachlab.posedetection.camera.toImageBitmap
import com.performancecoachlab.posedetection.custom.ObjectModel
import com.performancecoachlab.posedetection.skeleton.Skeleton
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.suspendCancellableCoroutine
import platform.CoreGraphics.CGImageCreateWithImageInRect
import platform.CoreGraphics.CGImageGetHeight
import platform.CoreGraphics.CGImageGetWidth
import platform.CoreGraphics.CGImageRef
import platform.CoreGraphics.CGRectMake
import platform.CoreML.MLModel
import platform.Foundation.NSBundle
import platform.Foundation.NSURL
import platform.UIKit.UIImage
import platform.Vision.VNCoreMLModel
import kotlin.coroutines.resume
import kotlin.native.runtime.NativeRuntimeApi
import kotlin.text.toDouble
import kotlin.text.toFloat

actual class InputFrame @OptIn(ExperimentalForeignApi::class) constructor(
    val cgImage: CGImageRef, actual val timestamp: Long
) {
    @OptIn(ExperimentalForeignApi::class)
    actual fun toImageBitmap(): ImageBitmap {
        return UIImage.imageWithCGImage(cgImage = cgImage).toImageBitmap()
    }

    actual fun drawSkeleton(skel: Skeleton): ImageBitmap {
        return toImageBitmap().drawSkeleton(skel)
    }

    actual fun drawAnalysisResults(analysisResults: AnalysisResult): ImageBitmap {
        return toImageBitmap().drawAnalysisResults(analysisResults)
    }

    @OptIn(ExperimentalForeignApi::class, NativeRuntimeApi::class)
    actual fun crop(rect: Rect): InputFrame {
        val widthPx = CGImageGetWidth(cgImage).toFloat()
        val heightPx = CGImageGetHeight(cgImage).toFloat()
        val sanitised = Rect(
            left = rect.left.coerceIn(0f, widthPx),
            top = rect.top.coerceIn(0f, heightPx),
            right = rect.right.coerceIn(0f, widthPx),
            bottom = rect.bottom.coerceIn(0f, heightPx)
        )

        val cropX = sanitised.left.toDouble()
        // invert y for CoreGraphics coordinate system
        val cropY = (sanitised.top).toDouble()
        val cropW = (sanitised.right - sanitised.left).toDouble()
        val cropH = (sanitised.bottom - sanitised.top).toDouble()

        val cgRect = CGRectMake(cropX, cropY, cropW, cropH)
        kotlin.native.runtime.GC.collect()
        return InputFrame(CGImageCreateWithImageInRect(cgImage, cgRect)?:cgImage, timestamp)
    }
}

actual class FrameAnalyser actual constructor(val model: ObjectModel?) {
    private val modelObj = model?.getModel()
    private val frameProcessor = FrameProcessor(modelObj)

    @OptIn(ExperimentalForeignApi::class)
    actual suspend fun analyseFrame(inputFrame: InputFrame,focusArea: Rect?): AnalysisResult {
        frameProcessor.setFocusArea(focusArea)
        val img = inputFrame.cgImage
        return suspendCancellableCoroutine { continuation ->
            var poseResult: Skeleton? = null
            var objectResults: List<AnalysisObject> = emptyList()
            frameProcessor.analyseFrameForAll(img, inputFrame.timestamp, onSkeletonProcessed = {
                poseResult = it
            }, onObjectsProcessed = {
                objectResults = it
            })
            continuation.resume(AnalysisResult(poseResult, objectResults))
        }
    }
}
