package oppen.filters.io

/*

    Lätt - fast image filtering app for Android
    Copyright (C) 2020  Öppenlab

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

 */

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.renderscript.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

/**
 *
 * See: https://android.googlesource.com/platform/frameworks/rs/+/6939f7bf32ab2765dcedb8a8d6fd352e7d08540f/java/tests/ImageProcessing/src/com/android/rs/image
 * https://github.com/dragon-tc/android_frameworks_rs/tree/master/java/tests/ImageProcessing/src/com/android/rs/image
 */
class PhotoProcessor(private val context: Context) {

    companion object{
        const val LUT_ONLY = true
        const val ALL_FILTERS = false
    }

    private var renderScript: RenderScript = RenderScript.create(context)
    private var rscriptLut: ScriptIntrinsic3DLUT
    private var rscriptBrightness: ScriptC_brightness
    private var rscriptSaturation: ScriptC_saturation
    private var rscriptExposure: ScriptC_exposure
    private var rscriptShadows: ScriptC_shadows
    private var rscriptGrain: ScriptC_grain
    private var rscriptContrast: ScriptC_contrast
    private var rscriptVignette: ScriptC_vignette

    init {
        rscriptLut = ScriptIntrinsic3DLUT.create(renderScript, Element.U8_4(renderScript))
        rscriptBrightness = ScriptC_brightness(renderScript)
        rscriptSaturation = ScriptC_saturation(renderScript)
        rscriptExposure = ScriptC_exposure(renderScript)
        rscriptShadows = ScriptC_shadows(renderScript)
        rscriptGrain = ScriptC_grain(renderScript)
        rscriptContrast = ScriptC_contrast(renderScript)
        rscriptVignette = ScriptC_vignette(renderScript)
    }

    /**
     *
     * @param sourceBitmap - the image we want to filter, leave to calling code to recycle
     * @param lutImageRes - the resource Id of the LUT png file
     * @param lutOnly - bypass level adjustments
     * @param onReady - callback with a filtered bitmap ready for handling elsewhere
     */
    fun filter(sourceBitmap: Bitmap, lutImageRes: Int?, lutOnly: Boolean, onReady: (bitmap: Bitmap?) -> Unit){
        GlobalScope.launch {
            val outputBitmap = Bitmap.createBitmap(sourceBitmap.width, sourceBitmap.height, sourceBitmap.config)
            val allocIn = Allocation.createFromBitmap(renderScript, sourceBitmap)
            val allocOut = Allocation.createFromBitmap(renderScript, outputBitmap)

            val doLut = (lutImageRes != null && 0 != lutImageRes)

            if(doLut) {
                val lutBitmap = BitmapFactory.decodeResource(context.resources, lutImageRes!!)
                val width: Int = lutBitmap?.width ?: 0
                val height: Int = lutBitmap?.height ?: 0
                val sideLength = width / height

                val pixels = IntArray(width * height)
                val lut = IntArray(width * height)

                lutBitmap?.getPixels(pixels, 0, width, 0, 0, width, height)
                lutBitmap?.recycle()//Done with Lut bitmap

                var i = 0

                for (red in 0 until sideLength) {
                    for (green in 0 until sideLength) {
                        val p = red + green * width
                        for (blue in 0 until sideLength) {
                            lut[i++] = pixels[p + blue * height]
                        }
                    }
                }

                val type = Type.Builder(renderScript, Element.U8_4(renderScript))
                    .setX(sideLength)
                    .setY(sideLength)
                    .setZ(sideLength)
                    .create()

                val allocCube = Allocation.createTyped(renderScript, type)
                allocCube?.copyFromUnchecked(lut)

                rscriptLut.setLUT(allocCube)
                rscriptLut.forEach(allocIn, allocOut)
            }else{
                allocOut.copyFrom(allocIn)
            }

            if (!lutOnly) {

                //Brightness
                if(Levels.applyBrightness) {
                    rscriptBrightness._brightness = Levels.brightness
                    rscriptBrightness.forEach_root(allocOut, allocOut)
                }

                //Exposure
                if(Levels.applyExposure) {
                    rscriptExposure.invoke_setBright(Levels.exposure)
                    rscriptExposure.forEach_exposure(allocOut, allocOut)
                }

                //Saturation
                if(Levels.applySaturation) {
                    rscriptSaturation._saturationValue = Levels.saturation
                    rscriptSaturation.forEach_saturation(allocOut, allocOut)
                }

                //Shadows
                if(Levels.applyShadows) {
                    rscriptShadows.invoke_prepareShadows(Levels.shadows)
                    rscriptShadows.forEach_shadowsKernel(allocOut, allocOut)
                }

                //Contrast
                if(Levels.applyContrast){
                    rscriptContrast.invoke_setBright(Levels.contrast)
                    rscriptContrast.forEach_contrast(allocOut, allocOut)
                }

                //Vignette
                if(Levels.applyVignette){
                    val centerX = 0.5f
                    val centerY = 0.5f
                    val scale = 0.75f
                    val shade = Levels.vignette
                    val slope = 5.9f
                    rscriptVignette.invoke_init_vignette(
                        sourceBitmap.width.toLong(),
                        sourceBitmap.height.toLong(),
                        centerX,
                        centerY,
                        scale,
                        shade,
                        slope)
                    rscriptVignette.forEach_root(allocOut, allocOut)
                }

                //Grain
                if (Levels.applyGrain && Levels.grain > 0.1f) {
                    val w: Int = allocIn.type.x
                    val h: Int = allocIn.type.y
                    var noiseW: Int = findHighBit(w)
                    var noiseH: Int = findHighBit(h)
                    if (noiseW > 9) {
                        noiseW = 9
                    }
                    if (noiseH > 9) {
                        noiseH = 9
                    }
                    noiseW = 1 shl noiseW
                    noiseH = 1 shl noiseH

                    val tb: Type.Builder = Type.Builder(renderScript, Element.U8(renderScript))
                    tb.setX(noiseW)
                    tb.setY(noiseH)
                    val mNoise = Allocation.createTyped(renderScript, tb.create())
                    val mNoise2 = Allocation.createTyped(renderScript, tb.create())

                    rscriptGrain._gWMask = noiseW - 1
                    rscriptGrain._gHMask = noiseH - 1
                    rscriptGrain._gBlendSource = mNoise
                    rscriptGrain._gNoise = mNoise2

                    rscriptGrain._gNoiseStrength = Levels.grain
                    rscriptGrain.forEach_genRand(mNoise)
                    rscriptGrain.forEach_blend9(mNoise2)
                    rscriptGrain.forEach_root(allocOut, allocOut)
                }
            }

            allocOut?.copyTo(outputBitmap)

            onReady.invoke(outputBitmap)

        }
    }

    private fun findHighBit(_v: Int): Int {
        var v = _v
        var bit = 0
        while (v > 1) {
            bit++
            v = v shr 1
        }
        return bit
    }
}