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.net.Uri
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import com.bumptech.glide.request.FutureTarget
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream

class ImageIO(private val context: Context) {

    companion object{
        const val LIVE_PREVIEW_FILENAME = "live_preview.png"
        const val LIVE_THUMBNAIL_SOURCE_FILENAME = "live_thumbnail_source.png"
    }

    var futureTarget: FutureTarget<Bitmap>? = null

    fun getAndResize(uri: Uri, width: Int, onComplete: (bitmap: Bitmap) -> Unit){
        GlobalScope.launch {
            val bounds = BitmapFactory.Options()
            bounds.inJustDecodeBounds = true
            val inputStream = context.contentResolver.openInputStream(uri)
            BitmapFactory.decodeStream(inputStream, null, bounds)

            val sourceWidth = bounds.outWidth
            val sourceHeight = bounds.outHeight

            var previewWidth = sourceWidth
            var previewHeight = sourceHeight

            if (previewWidth > width) {
                val ratio = previewWidth / width
                previewWidth = width
                previewHeight /= ratio
            }

            val sourceTarget: FutureTarget<Bitmap> = Glide.with(context)
                .asBitmap()
                .load(uri)
                .submit(previewWidth, previewHeight)

            onComplete(sourceTarget.get())
        }
    }

    fun get(uri: Uri, onLoaded: (bitmap: Bitmap?) -> Unit){
        GlobalScope.launch {
            futureTarget = Glide.with(context)
                .asBitmap()
                .load(uri)
                .submit()

            @Suppress("BlockingMethodInNonBlockingContext")//We're in a coroutine
            val bitmap = futureTarget?.get()
            onLoaded.invoke(bitmap)
        }
    }

    fun recycleLast(){
        if(futureTarget != null) Glide.with(context).clear(futureTarget)
    }

    /**
     *
     * Just saves bitmap to internal cache directory
     * @param bitmap - not recycled by this method
     * @param filename
     * @param onComplete
     *
     */
    fun put(bitmap: Bitmap?, filename: String, onComplete: (uri: Uri) -> Unit){
        val cacheFile = File(context.dataDir,filename)
        if(cacheFile.exists()) cacheFile.delete()

        FileOutputStream(cacheFile).also {
            bitmap?.compress(Bitmap.CompressFormat.PNG, 90, it)
            it.close()
            onComplete(cacheFile.toUri())
        }
    }

    /**
     *
     * Convenience overload
     * @see calculate
     *
     */
    fun buildAndStoreLivePreview(uri: Uri, width: Int, onPreview: (uri: Uri) -> Unit){
        calculate(uri, width, LIVE_PREVIEW_FILENAME, onPreview)
    }

    /**
     *
     * Convenience overload
     * @see calculate
     *
     */
    fun buildAndStoreLiveThumbnail(uri: Uri, width: Int, onPreview: (uri: Uri) -> Unit){
        calculate(uri, width, LIVE_THUMBNAIL_SOURCE_FILENAME, onPreview)
    }

    /**
     *
     * Convenience overload
     * @see calculate
     *
     */
    fun build(uri: Uri, width: Int, filename: String, onComplete: (uri: Uri) -> Unit){
        calculate(uri, width, filename, onComplete)
    }

    /**
     *
     * Generates a smaller (if needed) image from the source image file.
     * @param uri Storage Access Framework content: uri - NOT a file: uri
     * @param width Max width of the preview image, likely the screen width
     * @param onComplete Lambda callback with resulting preview image Uri
     */
    private fun calculate(uri: Uri, width: Int, filename: String, onComplete: (uri: Uri) -> Unit){
        GlobalScope.launch {
            val bounds = BitmapFactory.Options()
            bounds.inJustDecodeBounds = true
            val inputStream = context.contentResolver.openInputStream(uri)
            BitmapFactory.decodeStream(inputStream, null, bounds)

            val sourceWidth = bounds.outWidth
            val sourceHeight = bounds.outHeight

            var previewWidth = sourceWidth
            var previewHeight = sourceHeight

            if (previewWidth > width) {
                val ratio = previewWidth / width
                previewWidth = width
                previewHeight /= ratio

                //Arbitrary value, only reduce image quality for (probably/possibly) full screen width images
                /*
                    if(width > 800) {
                        previewWidth = (previewWidth / 1.5).toInt()
                        previewHeight = (previewHeight / 1.5).toInt()
                    }
                */
            }

            generate(uri,previewWidth, previewHeight, filename){ outputUri ->
                onComplete(outputUri)
            }
        }
    }

    private fun generate(source: Uri, targetWidth: Int, targetHeight: Int, targetFilename: String, onComplete: (uri: Uri) -> Unit){
        val sourceTarget: FutureTarget<Bitmap> = Glide.with(context)
            .asBitmap()
            .load(source)
            .submit(targetWidth, targetHeight)

        val sourceBitmap = sourceTarget.get()

        val outputFile = File(context.cacheDir, targetFilename)
        if (outputFile.exists()) outputFile.delete()
        FileOutputStream(outputFile).also {
            sourceBitmap.compress(Bitmap.CompressFormat.PNG, 90, it)
            it.close()
        }

        Glide.with(context).clear(sourceTarget)//Recycle

        onComplete(outputFile.toUri())
    }

    /**
     *
     * Save bitmap using Storage Access Framework Uri
     * @param bitmap
     * @param uri - must be a SAF Uri
     * @param onComplete
     */
    fun publicExport(bitmap: Bitmap?, uri: Uri, onComplete: (uri: Uri) -> Unit) {
        context.contentResolver.openFileDescriptor(uri, "w")?.use {
            FileOutputStream(it.fileDescriptor).use { outputStream ->
                bitmap?.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
            }
            bitmap?.recycle()
            onComplete(uri)
        }
    }
}