package com.example.myimagepicker.luban

import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.AsyncTask
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.text.TextUtils
import android.util.Log
import com.example.gengmei_flutter_plugin.ImagePlugin.repository.luban.Checker
import java.io.*
import java.util.ArrayList

/**
 * Created by lsy
 * on 2019/3/29
 */
class Luban private constructor(builder: Builder) : Handler.Callback {

    private var mTargetDir: String? = null
    private val focusAlpha: Boolean = false
    private val mLeastCompressSize: Int
    private val mRenameListener: OnRenameListener?
    private val mCompressListener: OnCompressListener?
    private val mCompressionPredicate: CompressionPredicate?
    private val mStreamProviders: MutableList<InputStreamProvider>?
    private var quality: Int = 0
    private var mmTargetName: String? = null;

    private val mHandler: Handler

    init {
        this.mTargetDir = builder.mTargetDir
        this.mRenameListener = builder.mRenameListener
        this.mStreamProviders = builder.mStreamProviders
        this.mCompressListener = builder.mCompressListener
        this.mLeastCompressSize = builder.mLeastCompressSize
        this.mCompressionPredicate = builder.mCompressionPredicate
        this.quality = builder.quality
        this.mmTargetName = builder.mTargetName;
        mHandler = Handler(Looper.getMainLooper(), this)
    }

    /**
     * Returns a file with a cache image name in the private cache directory.
     *
     * @param context A context.
     */
    private fun getImageCacheFile(context: Context, suffix: String): File {
        if (TextUtils.isEmpty(mTargetDir)) {
            mTargetDir = getImageCacheDir(context)!!.absolutePath
        }
        val cacheBuilder = mTargetDir + "/" +
                if (!TextUtils.isEmpty(mmTargetName)) {
                    mmTargetName+if (TextUtils.isEmpty(suffix)) ".jpg" else suffix
                } else {
                    System.currentTimeMillis().toString() +
                            (Math.random() * 1000).toInt() +
                            if (TextUtils.isEmpty(suffix)) ".jpg" else suffix
                }
        return File(cacheBuilder)
    }

    private fun getImageCustomFile(context: Context, filename: String): File {
        if (TextUtils.isEmpty(mTargetDir)) {
            mTargetDir = getImageCacheDir(context)!!.absolutePath
        }

        val cacheBuilder = "$mTargetDir/$filename"

        return File(cacheBuilder)
    }

    /**
     * Returns a directory with a default name in the private cache directory of the application to
     * use to store retrieved audio.
     *
     * @param context A context.
     * @see .getImageCacheDir
     */
    private fun getImageCacheDir(context: Context): File? {
        return getImageCacheDir(context, DEFAULT_DISK_CACHE_DIR)
    }

    /**
     * start asynchronous compress thread
     */
    private fun launch(context: Context) {
        if (mStreamProviders == null || mStreamProviders.size == 0 && mCompressListener != null) {
            mCompressListener!!.onError(NullPointerException("image file cannot be null"))
        }

        val iterator = mStreamProviders!!.iterator()

        while (iterator.hasNext()) {
            val path = iterator.next()

            AsyncTask.SERIAL_EXECUTOR.execute {
                try {
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_START))

                    val result = compress(context, path)

                    mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_SUCCESS, result))
                } catch (e: IOException) {
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_ERROR, e))
                }
            }

            iterator.remove()
        }
    }

    /**
     * start compress and return the file
     */
    @Throws(IOException::class)
    private operator fun get(input: InputStreamProvider, context: Context): File {
        try {
            return Engine(input, getImageCacheFile(context, Checker.SINGLE.extSuffix(input)).absolutePath, focusAlpha, quality).compress()
        } finally {
            input.close()
        }
    }

    @Throws(IOException::class)
    private operator fun get(context: Context): List<File> {
        val results = ArrayList<File>()
        val iterator = mStreamProviders!!.iterator()

        while (iterator.hasNext()) {
            results.add(compress(context, iterator.next()))
            iterator.remove()
        }

        return results
    }

    @Throws(IOException::class)
    private fun compress(context: Context, path: InputStreamProvider): File {
        try {
            return compressReal(context, path)
        } finally {
            path.close()
        }
    }

    @Throws(IOException::class)
    private fun compressReal(context: Context, path: InputStreamProvider): File {
        val result: File

        var outFile = getImageCacheFile(context, Checker.SINGLE.extSuffix(path))

        if (mRenameListener != null) {
            val filename = mRenameListener.rename(path.path!!)
            outFile = getImageCustomFile(context, filename)
        }

//        if (mCompressionPredicate != null) {
//            if (mCompressionPredicate.apply(path.path!!)
//                    && Checker.SINGLE.needCompress(mLeastCompressSize, path.path)
//            ) {
//                result = Engine(path, outFile.absolutePath, focusAlpha).compress()
//            } else {
//                result = File(path.path)
//            }
//        } else {
        result =
//                if (Checker.SINGLE.needCompress(mLeastCompressSize, path.path)) {
                Engine(path, outFile.absolutePath, focusAlpha, quality).compress()
//                } else {
//                    val options = BitmapFactory.Options()
//                    options.inJustDecodeBounds = true
//                    options.inSampleSize = 1
//
//                    BitmapFactory.decodeStream(path.open(), null, options)
//                    var srcWidth = options.outWidth
//                    var srcHeight = options.outHeight
//                    val split = path.path!!.split(".")
//                    var before = ""
//                    var after = ""
//                    after=split[split.size-1]
//                    before=path.path!!.replace("."+after,"")
//                    val file = File(before + "_w" + srcWidth + "_h" + srcHeight + "." + after)
//                    val fos = FileOutputStream(file)
//                    var stream=ByteArrayOutputStream()
//                    fos.write(stream.toByteArray())
//                    fos.flush()
//                    fos.close()
//                    stream.close()
//                    file
//                }
//        }
        return result
    }

    override fun handleMessage(msg: Message): Boolean {
        if (mCompressListener == null) return false

        when (msg.what) {
            MSG_COMPRESS_START -> mCompressListener.onStart()
            MSG_COMPRESS_SUCCESS -> mCompressListener.onSuccess(msg.obj as File)
            MSG_COMPRESS_ERROR -> mCompressListener.onError(msg.obj as Throwable)
        }
        return false
    }

    class Builder internal constructor(private val context: Context) {
        var mTargetDir: String? = null
        var focusAlpha: Boolean = false
        var mLeastCompressSize = 100
        var mRenameListener: OnRenameListener? = null
        var mCompressListener: OnCompressListener? = null
        var mCompressionPredicate: CompressionPredicate? = null
        val mStreamProviders: MutableList<InputStreamProvider>
        var quality: Int = 0
        var mTargetName: String? = null;

        init {
            this.mStreamProviders = ArrayList()
        }

        private fun build(): Luban {
            return Luban(this)
        }

        fun quality(q: Int): Builder {
            quality = q
            return this
        }

        fun setName(name: String): Builder {
            this.mTargetName = name;
            return this;
        }

        fun load(inputStreamProvider: InputStreamProvider): Builder {
            mStreamProviders.add(inputStreamProvider)
            return this
        }

        fun load(file: File): Builder {
            mStreamProviders.add(object : InputStreamAdapter() {

                override val path: String
                    get() = file.absolutePath

                @Throws(IOException::class)
                override fun openInternal(): InputStream {
                    return FileInputStream(file)
                }
            })
            return this
        }

        fun load(string: String): Builder {
            mStreamProviders.add(object : InputStreamAdapter() {

                override val path: String
                    get() = string

                @Throws(IOException::class)
                override fun openInternal(): InputStream {
                    return FileInputStream(string)
                }
            })
            return this
        }

        fun <T> load(list: List<T>): Builder {
            for (src in list) {
                if (src is String) {
                    load(src as String)
                } else if (src is File) {
                    load(src as File)
                } else if (src is Uri) {
                    load(src as Uri)
                } else {
                    throw IllegalArgumentException("Incoming data type exception, it must be String, File, Uri or Bitmap")
                }
            }
            return this
        }

        fun load(uri: Uri): Builder {
            mStreamProviders.add(object : InputStreamAdapter() {

                override val path: String?
                    get() = uri.path

                @Throws(IOException::class)
                override fun openInternal(): InputStream? {
                    return context.contentResolver.openInputStream(uri)
                }
            })
            return this
        }

        fun putGear(gear: Int): Builder {
            return this
        }

        fun setRenameListener(listener: OnRenameListener): Builder {
            this.mRenameListener = listener
            return this
        }

        fun setCompressListener(listener: OnCompressListener): Builder {
            this.mCompressListener = listener
            return this
        }

        fun setTargetDir(targetDir: String): Builder {
            this.mTargetDir = targetDir
            return this
        }

        /**
         * Do I need to keep the image's alpha channel
         *
         * @param focusAlpha
         *
         * true - to keep alpha channel, the compress speed will be slow.
         *
         *  false - don't keep alpha channel, it might have a black background.
         */
        fun setFocusAlpha(focusAlpha: Boolean): Builder {
            this.focusAlpha = focusAlpha
            return this
        }

        /**
         * do not compress when the origin image file size less than one value
         *
         * @param size the value of file size, unit KB, default 100K
         */
        fun ignoreBy(size: Int): Builder {
            this.mLeastCompressSize = size
            return this
        }

        /**
         * do compress image when return value was true, otherwise, do not compress the image file
         *
         * @param compressionPredicate A predicate callback that returns true or false for the given input path should be compressed.
         */
        fun filter(compressionPredicate: CompressionPredicate): Builder {
            this.mCompressionPredicate = compressionPredicate
            return this
        }


        /**
         * begin compress image with asynchronous
         */
        fun launch() {
            build().launch(context)
        }

        @Throws(IOException::class)
        operator fun get(path: String): File {
            return build()[object : InputStreamAdapter() {

                override val path: String
                    get() = path

                @Throws(IOException::class)
                override fun openInternal(): InputStream {
                    return FileInputStream(path)
                }
            }, context]
        }

        /**
         * begin compress image with synchronize
         *
         * @return the thumb image file list
         */
        @Throws(IOException::class)
        fun get(): List<File> {
            return build()[context]
        }
    }

    companion object {
        val TAG = "Luban"
        public val DEFAULT_DISK_CACHE_DIR = ".luban_disk_cache"

        private val MSG_COMPRESS_SUCCESS = 0
        private val MSG_COMPRESS_START = 1
        private val MSG_COMPRESS_ERROR = 2

        fun with(context: Context): Builder {
            return Builder(context)
        }

        /**
         * Returns a directory with the given name in the private cache directory of the application to
         * use to store retrieved media and thumbnails.
         *
         * @param context   A context.
         * @param cacheName The name of the subdirectory in which to store the cache.
         * @see .getImageCacheDir
         */
        private fun getImageCacheDir(context: Context, cacheName: String): File? {
            val cacheDir = context.externalCacheDir
            if (cacheDir != null) {
                val result = File(cacheDir, cacheName)
                return if (!result.mkdirs() && (!result.exists() || !result.isDirectory)) {
                    // File wasn't able to create a directory, or the result exists but not a directory
                    null
                } else result
            }
            if (Log.isLoggable(TAG, Log.ERROR)) {
                Log.e(TAG, "default disk cache dir is null")
            }
            return null
        }
    }
}