package de.softwareapp_hb.privateqrscanner.util

import com.google.mlkit.vision.barcode.common.Barcode
import java.util.Base64

internal data class ReadableBarcodePayload(
    val content: String,
    val isBase64Encoded: Boolean
)

fun Barcode.readablePayload(): String? {
    return readableBarcodePayload()?.content
}

internal fun Barcode.readableBarcodePayload(): ReadableBarcodePayload? {
    return readablePayload(rawValue, displayValue, rawBytes)
}

internal fun readablePayload(
    rawValue: String?,
    displayValue: String?,
    rawBytes: ByteArray?
): ReadableBarcodePayload? {
    val bytes = rawBytes?.takeIf { it.isNotEmpty() }
    rawValue?.trim()?.takeIf { it.isNotBlank() }?.let { value ->
        return if (value.isLikelyHumanReadable()) {
            ReadableBarcodePayload(content = value, isBase64Encoded = false)
        } else {
            value.asBase64Payload(bytes)
        }
    }
    displayValue?.trim()?.takeIf { it.isNotBlank() }?.let { value ->
        return if (value.isLikelyHumanReadable()) {
            ReadableBarcodePayload(content = value, isBase64Encoded = false)
        } else {
            value.asBase64Payload(bytes)
        }
    }

    bytes ?: return null
    val utf8 = bytes.toString(Charsets.UTF_8).trim()
    if (utf8.isLikelyHumanReadable()) {
        return ReadableBarcodePayload(content = utf8, isBase64Encoded = false)
    }

    return bytes.asBase64Payload()
}

private fun String.isLikelyHumanReadable(): Boolean {
    if (isBlank()) return false
    var index = 0
    while (index < length) {
        val codePoint = codePointAt(index)
        val charCount = Character.charCount(codePoint)
        if (
            Character.isISOControl(codePoint) && codePoint != '\n'.code &&
            codePoint != '\r'.code && codePoint != '\t'.code
        ) {
            return false
        }
        if (
            codePoint == '\uFFFD'.code ||
            (this[index].isSurrogate() && charCount == 1) ||
            codePoint.isNonCharacter()
        ) {
            return false
        }
        index += charCount
    }
    return true
}

private fun String.asBase64Payload(rawBytes: ByteArray?): ReadableBarcodePayload {
    return rawBytes?.asBase64Payload() ?: toByteArray(Charsets.UTF_8).asBase64Payload()
}

private fun ByteArray.asBase64Payload(): ReadableBarcodePayload {
    return ReadableBarcodePayload(
        content = Base64.getEncoder().encodeToString(this),
        isBase64Encoded = true
    )
}

private fun Int.isNonCharacter(): Boolean {
    return this in 0xFDD0..0xFDEF || (this and 0xFFFE) == 0xFFFE
}
