package io.legado.app.utils

import android.graphics.*
import android.text.TextPaint
import android.text.TextUtils
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import com.google.zxing.*
import com.google.zxing.common.GlobalHistogramBinarizer
import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.king.zxing.DecodeFormatManager
import com.king.zxing.util.LogUtils
import java.util.*
import kotlin.math.max


@Suppress("MemberVisibilityCanBePrivate", "unused")
object QRCodeUtils {

    const val DEFAULT_REQ_WIDTH = 480
    const val DEFAULT_REQ_HEIGHT = 640

    /**
     * 生成二维码
     * @param content 二维码的内容
     * @param heightPix 二维码的高
     * @param logo 二维码中间的logo
     * @param ratio  logo所占比例 因为二维码的最大容错率为30%，所以建议ratio的范围小于0.3
     * @param errorCorrectionLevel
     */
    fun createQRCode(
        content: String,
        heightPix: Int = DEFAULT_REQ_HEIGHT,
        logo: Bitmap? = null,
        @FloatRange(from = 0.0, to = 1.0) ratio: Float = 0.2f,
        errorCorrectionLevel: ErrorCorrectionLevel = ErrorCorrectionLevel.H
    ): Bitmap? {
        //配置参数
        val hints: MutableMap<EncodeHintType, Any> = EnumMap(EncodeHintType::class.java)
        hints[EncodeHintType.CHARACTER_SET] = "utf-8"
        //容错级别
        hints[EncodeHintType.ERROR_CORRECTION] = errorCorrectionLevel
        //设置空白边距的宽度
        hints[EncodeHintType.MARGIN] = 1 //default is 4
        return createQRCode(content, heightPix, logo, ratio, hints)
    }

    /**
     * 生成二维码
     * @param content 二维码的内容
     * @param heightPix 二维码的高
     * @param logo 二维码中间的logo
     * @param ratio  logo所占比例 因为二维码的最大容错率为30%，所以建议ratio的范围小于0.3
     * @param hints
     * @param codeColor 二维码的颜色
     * @return
     */
    fun createQRCode(
        content: String?,
        heightPix: Int,
        logo: Bitmap?,
        @FloatRange(from = 0.0, to = 1.0) ratio: Float = 0.2f,
        hints: Map<EncodeHintType, *>,
        codeColor: Int = Color.BLACK
    ): Bitmap? {
        try {
            // 图像数据转换，使用了矩阵转换
            val bitMatrix =
                QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, heightPix, heightPix, hints)
            val pixels = IntArray(heightPix * heightPix)
            // 下面这里按照二维码的算法，逐个生成二维码的图片，
            // 两个for循环是图片横列扫描的结果
            for (y in 0 until heightPix) {
                for (x in 0 until heightPix) {
                    if (bitMatrix[x, y]) {
                        pixels[y * heightPix + x] = codeColor
                    } else {
                        pixels[y * heightPix + x] = Color.WHITE
                    }
                }
            }

            // 生成二维码图片的格式
            var bitmap = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888)
            bitmap!!.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix)
            if (logo != null) {
                bitmap = addLogo(bitmap, logo, ratio)
            }
            return bitmap
        } catch (e: WriterException) {
            LogUtils.w(e.message)
        }
        return null
    }

    /**
     * 在二维码中间添加Logo图案
     * @param src
     * @param logo
     * @param ratio  logo所占比例 因为二维码的最大容错率为30%，所以建议ratio的范围小于0.3
     * @return
     */
    private fun addLogo(
        src: Bitmap?,
        logo: Bitmap?,
        @FloatRange(from = 0.0, to = 1.0) ratio: Float
    ): Bitmap? {
        if (src == null) {
            return null
        }
        if (logo == null) {
            return src
        }

        //获取图片的宽高
        val srcWidth = src.width
        val srcHeight = src.height
        val logoWidth = logo.width
        val logoHeight = logo.height
        if (srcWidth == 0 || srcHeight == 0) {
            return null
        }
        if (logoWidth == 0 || logoHeight == 0) {
            return src
        }

        //logo大小为二维码整体大小
        val scaleFactor = srcWidth * ratio / logoWidth
        var bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888)
        try {
            val canvas = Canvas(bitmap!!)
            canvas.drawBitmap(src, 0f, 0f, null)
            canvas.scale(
                scaleFactor,
                scaleFactor,
                (srcWidth / 2).toFloat(),
                (srcHeight / 2).toFloat()
            )
            canvas.drawBitmap(
                logo,
                ((srcWidth - logoWidth) / 2).toFloat(),
                ((srcHeight - logoHeight) / 2).toFloat(),
                null
            )
            canvas.save()
            canvas.restore()
        } catch (e: Exception) {
            bitmap = null
            LogUtils.w(e.message)
        }
        return bitmap
    }

    /**
     * 解析一维码/二维码图片
     * @param bitmap 解析的图片
     * @param hints 解析编码类型
     * @return
     */
    fun parseCode(
        bitmap: Bitmap,
        reqWidth: Int = DEFAULT_REQ_WIDTH,
        reqHeight: Int = DEFAULT_REQ_HEIGHT,
        hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS
    ): String? {
        val result = parseCodeResult(bitmap, reqWidth, reqHeight, hints)
        return result?.text
    }

    /**
     * 解析一维码/二维码图片
     * @param bitmap 解析的图片
     * @param hints 解析编码类型
     * @return
     */
    fun parseCodeResult(
        bitmap: Bitmap,
        reqWidth: Int = DEFAULT_REQ_WIDTH,
        reqHeight: Int = DEFAULT_REQ_HEIGHT,
        hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS
    ): Result? {
        if (bitmap.width > reqWidth || bitmap.height > reqHeight) {
            val bm = bitmap.changeSize(reqWidth, reqHeight)
            return parseCodeResult(getRGBLuminanceSource(bm), hints)
        }
        return parseCodeResult(getRGBLuminanceSource(bitmap), hints)
    }

    /**
     * 解析一维码/二维码图片
     * @param source
     * @param hints
     * @return
     */
    fun parseCodeResult(source: LuminanceSource?, hints: Map<DecodeHintType?, Any?>?): Result? {
        var result: Result? = null
        val reader = MultiFormatReader()
        try {
            reader.setHints(hints)
            if (source != null) {
                result = decodeInternal(reader, source)
                if (result == null) {
                    result = decodeInternal(reader, source.invert())
                }
                if (result == null && source.isRotateSupported) {
                    result = decodeInternal(reader, source.rotateCounterClockwise())
                }
            }
        } catch (e: java.lang.Exception) {
            LogUtils.w(e.message)
        } finally {
            reader.reset()
        }
        return result
    }

    /**
     * 解析二维码图片
     * @param bitmapPath 需要解析的图片路径
     * @return
     */
    fun parseQRCode(bitmapPath: String?): String? {
        val result = parseQRCodeResult(bitmapPath)
        return result?.text
    }

    /**
     * 解析二维码图片
     * @param bitmapPath 需要解析的图片路径
     * @param reqWidth 请求目标宽度，如果实际图片宽度大于此值，会自动进行压缩处理，当 reqWidth 和 reqHeight都小于或等于0时，则不进行压缩处理
     * @param reqHeight 请求目标高度，如果实际图片高度大于此值，会自动进行压缩处理，当 reqWidth 和 reqHeight都小于或等于0时，则不进行压缩处理
     * @return
     */
    fun parseQRCodeResult(
        bitmapPath: String?,
        reqWidth: Int = DEFAULT_REQ_WIDTH,
        reqHeight: Int = DEFAULT_REQ_HEIGHT
    ): Result? {
        return parseCodeResult(bitmapPath, reqWidth, reqHeight, DecodeFormatManager.QR_CODE_HINTS)
    }

    /**
     * 解析一维码/二维码图片
     * @param bitmapPath 需要解析的图片路径
     * @return
     */
    fun parseCode(
        bitmapPath: String?,
        reqWidth: Int = DEFAULT_REQ_WIDTH,
        reqHeight: Int = DEFAULT_REQ_HEIGHT,
        hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS
    ): String? {
        return parseCodeResult(bitmapPath, reqWidth, reqHeight, hints)?.text
    }

    /**
     * 解析一维码/二维码图片
     * @param bitmapPath 需要解析的图片路径
     * @param reqWidth 请求目标宽度，如果实际图片宽度大于此值，会自动进行压缩处理，当 reqWidth 和 reqHeight都小于或等于0时，则不进行压缩处理
     * @param reqHeight 请求目标高度，如果实际图片高度大于此值，会自动进行压缩处理，当 reqWidth 和 reqHeight都小于或等于0时，则不进行压缩处理
     * @param hints 解析编码类型
     * @return
     */
    fun parseCodeResult(
        bitmapPath: String?,
        reqWidth: Int = DEFAULT_REQ_WIDTH,
        reqHeight: Int = DEFAULT_REQ_HEIGHT,
        hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS
    ): Result? {
        var result: Result? = null
        val reader = MultiFormatReader()
        try {
            reader.setHints(hints)
            val source = getRGBLuminanceSource(compressBitmap(bitmapPath, reqWidth, reqHeight))
            result = decodeInternal(reader, source)
            if (result == null) {
                result = decodeInternal(reader, source.invert())
            }
            if (result == null && source.isRotateSupported) {
                result = decodeInternal(reader, source.rotateCounterClockwise())
            }
        } catch (e: Exception) {
            LogUtils.w(e.message)
        } finally {
            reader.reset()
        }
        return result
    }

    private fun decodeInternal(reader: MultiFormatReader, source: LuminanceSource): Result? {
        var result: Result? = null
        try {
            try {
                //采用HybridBinarizer解析
                result = reader.decodeWithState(BinaryBitmap(HybridBinarizer(source)))
            } catch (e: Exception) {
            }
            if (result == null) {
                //如果没有解析成功，再采用GlobalHistogramBinarizer解析一次
                result = reader.decodeWithState(BinaryBitmap(GlobalHistogramBinarizer(source)))
            }
        } catch (e: Exception) {
        }
        return result
    }


    /**
     * 压缩图片
     * @param path
     * @return
     */
    private fun compressBitmap(path: String?, reqWidth: Int, reqHeight: Int): Bitmap {
        if (reqWidth > 0 && reqHeight > 0) { //都大于进行判断是否压缩
            val newOpts = BitmapFactory.Options()
            // 开始读入图片，此时把options.inJustDecodeBounds 设回true了
            newOpts.inJustDecodeBounds = true //获取原始图片大小
            BitmapFactory.decodeFile(path, newOpts) // 此时返回bm为空
            val width = newOpts.outWidth.toFloat()
            val height = newOpts.outHeight.toFloat()
            // 缩放比，由于是固定比例缩放，只用高或者宽其中一个数据进行计算即可
            var wSize = 1 // wSize=1表示不缩放
            if (width > reqWidth) { // 如果宽度大的话根据宽度固定大小缩放
                wSize = (width / reqWidth).toInt()
            }
            var hSize = 1 // wSize=1表示不缩放
            if (height > reqHeight) { // 如果高度高的话根据宽度固定大小缩放
                hSize = (height / reqHeight).toInt()
            }
            var size = max(wSize, hSize)
            if (size <= 0) size = 1
            newOpts.inSampleSize = size // 设置缩放比例
            // 重新读入图片，注意此时已经把options.inJustDecodeBounds 设回false了
            newOpts.inJustDecodeBounds = false
            return BitmapFactory.decodeFile(path, newOpts)
        }
        return BitmapFactory.decodeFile(path)
    }


    /**
     * 获取RGBLuminanceSource
     * @param bitmap
     * @return
     */
    private fun getRGBLuminanceSource(bitmap: Bitmap): RGBLuminanceSource {
        val width = bitmap.width
        val height = bitmap.height
        val pixels = IntArray(width * height)
        bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
        return RGBLuminanceSource(width, height, pixels)
    }

    /**
     * 生成条形码
     * @param content
     * @param format
     * @param desiredWidth
     * @param desiredHeight
     * @param hints
     * @param isShowText
     * @param textSize
     * @param codeColor
     * @return
     */
    fun createBarCode(
        content: String?,
        desiredWidth: Int,
        desiredHeight: Int,
        format: BarcodeFormat = BarcodeFormat.CODE_128,
        hints: Map<EncodeHintType?, *>? = null,
        isShowText: Boolean = true,
        textSize: Int = 40,
        @ColorInt codeColor: Int = Color.BLACK
    ): Bitmap? {
        if (TextUtils.isEmpty(content)) {
            return null
        }
        val writer = MultiFormatWriter()
        try {
            val result = writer.encode(
                content, format, desiredWidth,
                desiredHeight, hints
            )
            val width = result.width
            val height = result.height
            val pixels = IntArray(width * height)
            // All are 0, or black, by default
            for (y in 0 until height) {
                val offset = y * width
                for (x in 0 until width) {
                    pixels[offset + x] = if (result[x, y]) codeColor else Color.WHITE
                }
            }
            val bitmap = Bitmap.createBitmap(
                width, height,
                Bitmap.Config.ARGB_8888
            )
            bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
            return if (isShowText) {
                addCode(bitmap, content, textSize, codeColor, textSize / 2)
            } else bitmap
        } catch (e: WriterException) {
            LogUtils.w(e.message)
        }
        return null
    }

    /**
     * 条形码下面添加文本信息
     * @param src
     * @param code
     * @param textSize
     * @param textColor
     * @return
     */
    private fun addCode(
        src: Bitmap?,
        code: String?,
        textSize: Int,
        @ColorInt textColor: Int,
        offset: Int
    ): Bitmap? {
        if (src == null) {
            return null
        }
        if (TextUtils.isEmpty(code)) {
            return src
        }

        //获取图片的宽高
        val srcWidth = src.width
        val srcHeight = src.height
        if (srcWidth <= 0 || srcHeight <= 0) {
            return null
        }
        var bitmap = Bitmap.createBitmap(
            srcWidth,
            srcHeight + textSize + offset * 2,
            Bitmap.Config.ARGB_8888
        )
        try {
            val canvas = Canvas(bitmap!!)
            canvas.drawBitmap(src, 0f, 0f, null)
            val paint = TextPaint()
            paint.textSize = textSize.toFloat()
            paint.color = textColor
            paint.textAlign = Paint.Align.CENTER
            canvas.drawText(
                code!!,
                (srcWidth / 2).toFloat(),
                (srcHeight + textSize / 2 + offset).toFloat(),
                paint
            )
            canvas.save()
            canvas.restore()
        } catch (e: Exception) {
            bitmap = null
            LogUtils.w(e.message)
        }
        return bitmap
    }


}