package com.dumpster.cleaner.ui.similar

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.media.ThumbnailUtils
import com.dumpster.cleaner.bean.SimilarBean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.math.pow

object SimilarUtils {

    private fun loadBitmapFromFile(path: String): Bitmap? {
        val bitmap = BitmapFactory.decodeFile(path)

        if (bitmap.width <= 0 || bitmap.height <= 0) {
            return null
        }

        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeFile(path, options)
        if (options.outHeight == -1 || options.outWidth == -1) {
            return null
        }

        var inSampleSize =
            (0.5f + options.outHeight.toFloat() / bitmap.height.toFloat()).coerceAtLeast(0.5f + options.outWidth.toFloat() / bitmap.width.toFloat())
                .toInt()
        inSampleSize += 1
        options.inSampleSize = inSampleSize.coerceAtLeast(1)
        options.inJustDecodeBounds = false
        return BitmapFactory.decodeFile(path, options)
    }

    suspend fun createImage(path: String): SimilarBean? {
        return withContext(Dispatchers.IO) {
            try {
                val width = 8
                val height = 8
                val source = loadBitmapFromFile(path)
                val thumb = ThumbnailUtils.extractThumbnail(source, width, height)
                val pixels = IntArray(width * height)
                for (i in 0 until width) {
                    for (j in 0 until height) {
                        pixels[i * height + j] = rgbToGray(thumb.getPixel(i, j))
                    }
                }

                val avgPixel = average(pixels)
                val comps = IntArray(width * height)
                for (i in comps.indices) {
                    if (pixels[i] >= avgPixel) {
                        comps[i] = 1
                    } else {
                        comps[i] = 0
                    }
                }

                val hashCode = StringBuffer()
                var i = 0
                while (i < comps.size) {
                    val result = comps[i] * 2.0.pow(3.0).toInt() + (comps[i + 1] * 2.0.pow(2.0)
                        .toInt()) + (comps[i + 2] * 2.0.pow(1.0).toInt()) + comps[i + 3]
                    hashCode.append(binaryToHex(result))
                    i += 4
                }
                recycleBitmap(thumb)
                recycleBitmap(source)
                return@withContext SimilarBean(
                    path,
                    hashCode.toString(),
                    avgPixel,
                    File(path).length(),
                    false
                )
            } catch (_: Exception) {
                return@withContext null
            }
        }
    }

    private fun rgbToGray(pixels: Int): Int {
        val red = Color.red(pixels)
        val green = Color.green(pixels)
        val blue = Color.blue(pixels)
        return (0.3 * red + 0.59 * green + 0.11 * blue).toInt()
    }

    private fun average(pixels: IntArray): Int {
        return (pixels.sumOf { it }.toFloat() / pixels.size).toInt()
    }

    private fun recycleBitmap(thumb: Bitmap?) {
        if (thumb?.isRecycled == false) {
            thumb.recycle()
        }
    }

    fun similarCondition(first: SimilarBean, second: SimilarBean): Boolean {
        return hammingDistance(
            first.hashCode,
            second.hashCode
        ) <= 6 && (first.avgPixel.toFloat() / second.avgPixel) in 0.8..1.0
    }

    private fun hammingDistance(sourceHashCode: String, hashCode: String): Int {
        var difference = 0
        for (i in sourceHashCode.indices) {
            if (sourceHashCode[i] != hashCode[i]) {
                difference++
            }
        }
        return difference
    }

    private fun binaryToHex(binary: Int) = when (binary) {
        0 -> '0'
        1 -> '1'
        2 -> '2'
        3 -> '3'
        4 -> '4'
        5 -> '5'
        6 -> '6'
        7 -> '7'
        8 -> '8'
        9 -> '9'
        10 -> 'a'
        11 -> 'b'
        12 -> 'c'
        13 -> 'd'
        14 -> 'e'
        15 -> 'f'
        else -> ' '
    }
}