package eu.geyskens.pdfscan.scan

import android.graphics.Bitmap
import android.graphics.PointF
import eu.geyskens.pdfscan.data.ScanFilter
import org.opencv.android.Utils
import org.opencv.core.Mat
import org.opencv.core.MatOfPoint2f
import org.opencv.core.Point
import org.opencv.core.Size
import org.opencv.imgproc.Imgproc
import kotlin.math.hypot
import kotlin.math.max

/**
 * Turns a raw capture into a clean, deskewed page: perspective-corrects to the
 * given corners, rotates, then applies the chosen look. Fully on-device.
 */
object ImageProcessor {

    /**
     * @param corners normalized (0..1) TL, TR, BR, BL in [source] space.
     * @param rotationDegrees multiple of 90 applied after warping.
     */
    fun process(
        source: Bitmap,
        corners: List<PointF>,
        rotationDegrees: Int,
        filter: ScanFilter,
    ): Bitmap {
        val src = Mat()
        Utils.bitmapToMat(source, src)
        try {
            val w = src.cols().toDouble()
            val h = src.rows().toDouble()
            val pts = corners.map { Point(it.x * w, it.y * h) }
            val (tl, tr, br, bl) = pts

            val widthTop = hypot(tr.x - tl.x, tr.y - tl.y)
            val widthBottom = hypot(br.x - bl.x, br.y - bl.y)
            val heightLeft = hypot(bl.x - tl.x, bl.y - tl.y)
            val heightRight = hypot(br.x - tr.x, br.y - tr.y)
            val outW = max(widthTop, widthBottom).toInt().coerceAtLeast(1)
            val outH = max(heightLeft, heightRight).toInt().coerceAtLeast(1)

            val srcQuad = MatOfPoint2f(tl, tr, br, bl)
            val dstQuad = MatOfPoint2f(
                Point(0.0, 0.0),
                Point(outW - 1.0, 0.0),
                Point(outW - 1.0, outH - 1.0),
                Point(0.0, outH - 1.0),
            )
            val transform = Imgproc.getPerspectiveTransform(srcQuad, dstQuad)
            val warped = Mat()
            Imgproc.warpPerspective(src, warped, transform, Size(outW.toDouble(), outH.toDouble()))

            applyFilter(warped, filter)
            rotate(warped, rotationDegrees)

            val out = Bitmap.createBitmap(warped.cols(), warped.rows(), Bitmap.Config.ARGB_8888)
            Utils.matToBitmap(warped, out)
            warped.release()
            return out
        } finally {
            src.release()
        }
    }

    private fun applyFilter(mat: Mat, filter: ScanFilter) {
        when (filter) {
            ScanFilter.COLOR -> {
                // Gentle contrast/brightness lift so paper reads as white.
                mat.convertTo(mat, -1, 1.15, 8.0)
            }
            ScanFilter.GRAYSCALE -> {
                val gray = Mat()
                Imgproc.cvtColor(mat, gray, Imgproc.COLOR_RGBA2GRAY)
                Imgproc.cvtColor(gray, mat, Imgproc.COLOR_GRAY2RGBA)
                gray.release()
            }
            ScanFilter.BLACK_WHITE -> {
                val gray = Mat()
                Imgproc.cvtColor(mat, gray, Imgproc.COLOR_RGBA2GRAY)
                val bw = Mat()
                Imgproc.adaptiveThreshold(
                    gray, bw, 255.0,
                    Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY,
                    15, 10.0
                )
                Imgproc.cvtColor(bw, mat, Imgproc.COLOR_GRAY2RGBA)
                gray.release()
                bw.release()
            }
        }
    }

    private fun rotate(mat: Mat, degrees: Int) {
        when (((degrees % 360) + 360) % 360) {
            90 -> org.opencv.core.Core.rotate(mat, mat, org.opencv.core.Core.ROTATE_90_CLOCKWISE)
            180 -> org.opencv.core.Core.rotate(mat, mat, org.opencv.core.Core.ROTATE_180)
            270 -> org.opencv.core.Core.rotate(mat, mat, org.opencv.core.Core.ROTATE_90_COUNTERCLOCKWISE)
            else -> {}
        }
    }
}
