/*
 * Lunar Launcher
 * Copyright (C) 2022 Md Rasel Hossain
 *
 * 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 <http://www.gnu.org/licenses/>.
 */

package rasel.lunar.launcher.apps

import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import androidx.collection.LruCache
import androidx.core.content.res.ResourcesCompat
import com.squareup.picasso.Picasso
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
import rasel.lunar.launcher.helpers.Constants.Companion.DEFAULT_ICON_PACK
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_ICON_PACK
import rasel.lunar.launcher.helpers.Constants.Companion.PREFS_SETTINGS
import rasel.lunar.launcher.utils.BLog
import java.io.IOException
import java.util.Locale


internal class IconPackManager {

    @SuppressLint("DiscouragedApi")
    companion object {

        private val settingsPrefs = lActivity!!.getSharedPreferences(PREFS_SETTINGS, 0)
        private val packageName = settingsPrefs.getString(KEY_ICON_PACK, DEFAULT_ICON_PACK)
        private var loaded = false
        private val packagesDrawables = HashMap<String?, String?>()
        private val packagesConponentNames = HashMap<String?, String?>()
        private val backImages: MutableList<Bitmap> = ArrayList()
        private var maskImage: Bitmap? = null
        private var frontImage: Bitmap? = null
        private var factor = 1.0f
        private var totalIcons = 0
        private var iconPackRes: Resources? = null
        private var appPackageIconDrawables : HashMap<String, Drawable> = hashMapOf()

        private fun load() {
            /* load appfilter.xml from the icon pack package */
            try {
                var xpp: XmlPullParser? = null
                iconPackRes = lActivity!!.packageManager.getResourcesForApplication(packageName!!)
                val appFilterId = iconPackRes!!.getIdentifier("appfilter", "xml", packageName)
                if (appFilterId > 0) {
                    xpp = iconPackRes!!.getXml(appFilterId)
                    BLog.LOGE("packageName >>> ${packageName}")
                } else {
                    try {
                        xpp = XmlPullParserFactory.newInstance().apply { isNamespaceAware = true }
                            .newPullParser().apply {
                                BLog.LOGE("packageName >>> ${packageName}")
                                setInput(iconPackRes!!.assets.open("appfilter.xml"), "utf-8")
                            }
                    } catch (e: IOException) {
                        e.printStackTrace()
                        BLog.w("", "Couldn't find the appfilter.xml file")
                    }
                }
                if (xpp != null) {
                    var eventType = xpp.eventType
                    while (eventType != XmlPullParser.END_DOCUMENT) {
                        if (eventType == XmlPullParser.START_TAG) {
                            when (xpp.name) {
                                "iconback" -> {
                                    for (i in 0 until xpp.attributeCount) { if (xpp.getAttributeName(i).startsWith("img")) { loadBitmap(xpp.getAttributeValue(i))?.let { backImages.add(it) }}}
                                }
                                "iconmask" -> {
                                    if (xpp.attributeCount > 0 && xpp.getAttributeName(0) == "img1") { maskImage = loadBitmap(xpp.getAttributeValue(0)) }
                                }
                                "iconupon" -> {
                                    if (xpp.attributeCount > 0 && xpp.getAttributeName(0) == "img1") { frontImage = loadBitmap(xpp.getAttributeValue(0)) }
                                }
                                "scale" -> {
                                    if (xpp.attributeCount > 0 && xpp.getAttributeName(0) == "factor") { factor = java.lang.Float.valueOf(xpp.getAttributeValue(0)) }
                                }
                                "item" -> {
                                    var componentName: String? = null
                                    var drawableName: String? = null
                                    for (i in 0 until xpp.attributeCount) { when (xpp.getAttributeName(i)) {
                                        "component" -> componentName = xpp.getAttributeValue(i)
                                        "drawable" -> drawableName = xpp.getAttributeValue(i)
                                    } }
                                    if (!packagesDrawables.containsKey(componentName)) {
                                        packagesDrawables[componentName] = drawableName
                                        totalIcons += 1
                                    }
                                }
                            }
                        }
                        eventType = xpp.next()
                    }
                }
                loaded = true
            } catch (e: PackageManager.NameNotFoundException) {
                BLog.w("", "Failed to load the icon pack")
            } catch (e: XmlPullParserException) {
                BLog.w("", "Failed to parse the appfilter.xml file")
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
        val cacheSize = maxMemory / 8
        val bitmapCache = object : LruCache<String, Bitmap>(cacheSize) {
            fun sizeOf(key: String?, value: Bitmap?): Int {
                return if(value?.byteCount?.toInt() ?: 0 > 1024) {
                    value?.byteCount!!.div(1024)
                } else { 0 }
            }
        }

        private fun loadBitmap(drawableName: String): Bitmap? {
            if (packageName != null && packageName.length > 0) {
                var bm = bitmapCache.get(packageName)
                if (bm != null) return bm
            }
            iconPackRes!!.getIdentifier(drawableName, "drawable", packageName).let { id ->
                if (id > 0) {
                    ResourcesCompat.getDrawable(iconPackRes!!, id, null).let {
                        if (it is BitmapDrawable) {
                            if (packageName != null && packageName.length > 0) {
                                bitmapCache.put(packageName, it.bitmap)
                            }
                            return it.bitmap
                        }
                    }
                }
            }
            return null
        }

        private fun loadDrawable(drawableName: String): Drawable? {
            iconPackRes!!.getIdentifier(drawableName, "drawable", packageName).let {
                return if (it > 0) ResourcesCompat.getDrawable(iconPackRes!!, it, null)
                else null
            }
        }

        fun putAfterReturn(packages: String, drawable : Drawable?) : Drawable? {
            if (drawable != null) {
                appPackageIconDrawables.put(packages, drawable)
            }
            return drawable
        }

        fun getDrawableIconForPackage(appPackageName: String?, defaultDrawable: Drawable?, onComplete : (Drawable?)->Unit) {
            var ddd = if (appPackageIconDrawables.containsKey(appPackageName)) appPackageIconDrawables.get(appPackageName) else null
            if (ddd != null) {
                onComplete(ddd)
            } else {
                when (packageName) {
                    DEFAULT_ICON_PACK -> onComplete.invoke(defaultDrawable)
                    else -> {
                        if (!loaded) load()
                        var componentName: String? = null
                        componentName = packagesConponentNames.get(appPackageName!!)
                        if (componentName == null || componentName.length ?: 0 <= 0 ) {
                            BLog.LOGE("it's compo ${appPackageName}")
                            var pkgIntent =
                                lActivity!!.packageManager.getLaunchIntentForPackage(
                                    appPackageName!!
                                )
                            if (pkgIntent != null) {
                                componentName = pkgIntent!!.component.toString()
                            }
                        }
                        var drawable = packagesDrawables[componentName]
                        if (!drawable.isNullOrEmpty()) onComplete.invoke(
                            putAfterReturn(
                                appPackageName,
                                loadDrawable(drawable)
                            )
                        )
                        else {
                            if (!componentName.isNullOrEmpty()) {
                                val start = componentName.indexOf("{") + 1
                                val end = componentName.indexOf("}", start)
                                if (end > start) {
                                    drawable = componentName.substring(start, end)
                                        .lowercase(Locale.getDefault()).replace(".", "_")
                                        .replace("/", "_")
                                    try {
                                        if (iconPackRes!!.getIdentifier(
                                                drawable,
                                                "drawable",
                                                packageName
                                            ) > 0
                                        ) onComplete.invoke(putAfterReturn(appPackageName, loadDrawable(drawable)))
                                    } catch (e: NullPointerException) {
                                        settingsPrefs.edit()
                                            .putString(KEY_ICON_PACK, DEFAULT_ICON_PACK).apply()
                                    }
                                }
                            } else {
                                onComplete.invoke(defaultDrawable)
                            }
                        }
                    }
                }
            }

        }

    }
}
