package com.github.nullptroma.wallenc.ui.elements

import androidx.camera.core.ImageProxy
import com.google.zxing.BarcodeFormat
import com.google.zxing.BinaryBitmap
import com.google.zxing.ChecksumException
import com.google.zxing.DecodeHintType
import com.google.zxing.FormatException
import com.google.zxing.NotFoundException
import com.google.zxing.PlanarYUVLuminanceSource
import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeReader

internal object QrImageDecoder {
    private val hints = mapOf(
        DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE),
        DecodeHintType.CHARACTER_SET to "UTF-8",
    )
    private val reader = QRCodeReader()

    fun decode(imageProxy: ImageProxy): String? {
        val yPlane = imageProxy.planes[0]
        val yBuffer = yPlane.buffer
        yBuffer.rewind()
        val yRowStride = yPlane.rowStride
        val yPixelStride = yPlane.pixelStride
        val width = imageProxy.width
        val height = imageProxy.height
        val yBytes = ByteArray(width * height)
        var outputOffset = 0
        if (yPixelStride == 1 && yRowStride == width) {
            yBuffer.get(yBytes, 0, yBytes.size.coerceAtMost(yBuffer.remaining()))
        } else {
            for (row in 0 until height) {
                var inputOffset = row * yRowStride
                for (col in 0 until width) {
                    yBytes[outputOffset++] = yBuffer.get(inputOffset)
                    inputOffset += yPixelStride
                }
            }
        }
        val (luminance, decodeWidth, decodeHeight) = orientLuminance(
            yBytes,
            width,
            height,
            imageProxy.imageInfo.rotationDegrees,
        )
        val source = PlanarYUVLuminanceSource(
            luminance,
            decodeWidth,
            decodeHeight,
            0,
            0,
            decodeWidth,
            decodeHeight,
            false,
        )
        return try {
            reader.decode(BinaryBitmap(HybridBinarizer(source)), hints).text
        } catch (_: NotFoundException) {
            null
        } catch (_: ChecksumException) {
            null
        } catch (_: FormatException) {
            null
        }
    }

    private fun orientLuminance(
        yBytes: ByteArray,
        width: Int,
        height: Int,
        rotationDegrees: Int,
    ): Triple<ByteArray, Int, Int> = when (rotationDegrees) {
        90 -> Triple(rotateY90(yBytes, width, height, clockwise = true), height, width)
        180 -> Triple(rotateY180(yBytes, width, height), width, height)
        270 -> Triple(rotateY90(yBytes, width, height, clockwise = false), height, width)
        else -> Triple(yBytes, width, height)
    }

    private fun rotateY90(data: ByteArray, width: Int, height: Int, clockwise: Boolean): ByteArray {
        val output = ByteArray(data.size)
        for (y in 0 until height) {
            for (x in 0 until width) {
                val srcIndex = y * width + x
                val dstIndex = if (clockwise) {
                    x * height + (height - 1 - y)
                } else {
                    (width - 1 - x) * height + y
                }
                output[dstIndex] = data[srcIndex]
            }
        }
        return output
    }

    private fun rotateY180(data: ByteArray, width: Int, height: Int): ByteArray {
        val output = ByteArray(data.size)
        for (y in 0 until height) {
            for (x in 0 until width) {
                output[(height - 1 - y) * width + (width - 1 - x)] = data[y * width + x]
            }
        }
        return output
    }
}
