package com.gh.gamecenter.common.utils

import android.annotation.SuppressLint
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.drawable.Animatable
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Environment
import android.text.TextUtils
import android.view.View
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.facebook.common.executors.CallerThreadExecutor
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSource
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.controller.BaseControllerListener
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.imagepipeline.common.ResizeOptions
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.ImageInfo
import com.facebook.imagepipeline.listener.RequestListener
import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.facebook.imagepipeline.request.Postprocessor
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.callback.BiCallback
import com.gh.gamecenter.core.GHThreadFactory
import com.gh.gamecenter.core.provider.IConfigProvider
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.core.utils.TopCutProcess
import com.lightgame.utils.Utils
import com.squareup.picasso.LruCache
import com.squareup.picasso.Picasso
import com.therouter.TheRouter
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.io.*
import java.util.concurrent.Executors

object ImageUtils {
    private const val TAG = "ImageUtils"

    private const val PIC_MAX_FILE_SIZE: Long = 10 * 1024 * 1024

    private val TINY_ANIMATED_ICON_SIZE by lazy { 30F.dip2px() }
    private val LARGE_ANIMATED_ICON_SIZE by lazy { 80F.dip2px() }
    private val STANDARD_ANIMATED_ICON_SIZE by lazy { 60F.dip2px() }

    val TAG_TARGET_WIDTH by lazy { R.dimen.width_placeholder }
    val TAG_IS_GAME_ICON by lazy { R.dimen.game_icon_placeholder }

    private val TAG_NO_MEMORY_CACHE by lazy { R.string.no_memory_cache }

    // 是否仅显示静态图片 (动图会转码为静态图片)
    private var displayStaticImageOnly = false

    private val imageUrlCacheSet by lazy { FixedSizeLinkedHashSet<String>(maxSize = 200) }
    private val imageDecorationThread by lazy {
        Executors.newSingleThreadExecutor(
            GHThreadFactory("GH_IMAGE_DECORATOR_")
        )
    }

    private val picassoLruCache: LruCache by lazy { LruCache(5 * 1024 * 1024) }

    @JvmStatic
    val picasso: Picasso by lazy {
        // Picasso 的 Lru 缓存改为 5M，默认设定是 1/7 的 heap 大小，太大了
        // https://stackoverflow.com/a/20105828/4812571
        Picasso.Builder(HaloApp.getInstance()).memoryCache(picassoLruCache).build()
    }

    /**
     * 禁用动图，全局设置
     */
    @JvmStatic
    fun disableAnimatedImage() {
        displayStaticImageOnly = true
    }

    fun getUploadFileMaxSize(): Long {
        val configProvider = TheRouter.get(IConfigProvider::class.java)
        val uploadLimitSize = configProvider?.getUploadLimitSize()
        if (uploadLimitSize != null) {
            return uploadLimitSize
        }
        return PIC_MAX_FILE_SIZE
    }

    @JvmStatic
    fun getDefaultGifRule(): String {
        val configProvider = TheRouter.get(IConfigProvider::class.java)
        val gifConfig = configProvider?.getGif()
        if (gifConfig != null) {
            return gifConfig
        }
        return ""
    }

    @JvmStatic
    fun getWatermarkWidthGifRule(width: Int?): String {
        val configProvider = TheRouter.get(IConfigProvider::class.java)
        val gifConfig = configProvider?.getGitThumb()
        val gifWaterMark = configProvider?.getGifWaterMark()
        if (gifConfig != null && gifWaterMark != null) {
            return "$gifConfig,w_${width ?: 0}$gifWaterMark"
        }
        return ""
    }

    @JvmStatic
    fun getLimitWidthRule(width: Int?): String {
        val configProvider = TheRouter.get(IConfigProvider::class.java)
        val jpegConfig = configProvider?.getJpeg()
        if (jpegConfig != null) {
            return "$jpegConfig,w_${width ?: 0}"
        }
        return ""
    }

    /**
     * 根据配置限制图片宽度
     */
    @JvmStatic
    private fun limitWidthWithRuleIfPossible(imageUrl: String?, width: Int?, loadAsAnimatedImage: Boolean = true): String? {
        val configProvider = TheRouter.get(IConfigProvider::class.java)
        var jpegConfig = configProvider?.getJpeg()
        var webpConfig = configProvider?.getWebp() ?: configProvider?.getGif() ?: ""

        // 禁用动图时，各种配置转 png
        if (displayStaticImageOnly || !loadAsAnimatedImage) {
            webpConfig = webpConfig.replace("webp", "png")
            jpegConfig = jpegConfig?.replace("webp", "png")
        }

        if (imageUrl?.contains("s-process") == false && jpegConfig != null) {
            // jpegConfig 或者 width 为空的时候尝试用 webpConfig 加载
            return if (width == 0 || width == null || TextUtils.isEmpty(jpegConfig)) {
                "$imageUrl$webpConfig"
            } else {
                "$imageUrl$jpegConfig,w_${width}"
            }
        }
        return imageUrl
    }

    fun displayWithCallback(
        draweeView: SimpleDraweeView?,
        imageUrl: String?,
        noMemoryCache: Boolean = false,
        controllerListener: BaseControllerListener<ImageInfo>?
    ) {
        if (noMemoryCache) draweeView?.setTag(TAG_NO_MEMORY_CACHE, true)
        displayInternal(
            view = draweeView,
            url = imageUrl,
            loadAsAnimatedImage = true,
            processor = null,
            isIgnoreWidth = false,
            controllerListener = controllerListener
        )
    }

    /**
     * 固定宽，高度自适应来加载显示图片
     */
    @JvmStatic
    fun displayWithAdaptiveHeight(simpleDraweeView: SimpleDraweeView?, url: String?, width: Int) {
        val listener = object : BaseControllerListener<ImageInfo>() {
            override fun onFinalImageSet(
                id: String?,
                imageInfo: ImageInfo?,
                animatable: Animatable?
            ) {
                if (imageInfo == null) {
                    return
                }
                val layoutParams = simpleDraweeView?.layoutParams
                val scale = imageInfo.height.toFloat() / imageInfo.width.toFloat()
                layoutParams?.height = (width * scale).toInt()
                simpleDraweeView?.layoutParams = layoutParams
            }
        }
        simpleDraweeView?.controller = Fresco.newDraweeControllerBuilder()
            .setControllerListener(listener)
            .setUri(getIdealImageUrl(url, width))
            .build()
    }

    // 设置缩放类型，设置按压状态下的叠加图
    @JvmStatic
    fun display(
        resources: Resources?,
        simpleDraweeView: SimpleDraweeView?,
        width: Int,
        scaleType: ScalingUtils.ScaleType?,
        url: String?
    ) {
        if (simpleDraweeView == null) return
        val context = simpleDraweeView.context ?: return
        simpleDraweeView.hierarchy = GenericDraweeHierarchyBuilder(resources)
            .setFadeDuration(500)
            .setPressedStateOverlay(
                ColorDrawable(
                    ContextCompat.getColor(
                        context,
                        R.color.pressed_bg
                    )
                )
            )
            .setPlaceholderImage(R.drawable.occupy2, ScalingUtils.ScaleType.CENTER)
            .setBackground(ColorDrawable(ContextCompat.getColor(context, R.color.placeholder_bg)))
            .setActualImageScaleType(scaleType)
            .build()
        simpleDraweeView.setImageURI(getIdealImageUrl(url, width))
    }

    /**
     * 获取bitmap (使用 fresco 获取 gif 的 bitmap 会为空，https://github.com/facebook/fresco/issues/241)
     * 所以当 url 以 .gif 结尾时换用 picasso 获取
     * 优先使用 fresco 是因为可能已经有了缓存
     */
    @SuppressLint("CheckResult")
    @JvmStatic
    fun getBitmap(url: String, callback: BiCallback<Bitmap, Boolean>) {
        if (url.endsWith(".gif")) {
            getBitmapWithPicasso(url, callback)
        } else {
            getBitmapWithFresco(url, callback)
        }
    }

    private fun getBitmapWithFresco(url: String, callback: BiCallback<Bitmap, Boolean>) {
        val imageRequest = ImageRequestBuilder
            .newBuilderWithSource(Uri.parse(url))
            .build()
        Fresco.getImagePipeline()
            .fetchDecodedImage(imageRequest, HaloApp.getInstance())
            .subscribe(object : BaseBitmapDataSubscriber() {
                override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
                    callback.onSecond(true)
                }

                override fun onNewResultImpl(bitmap: Bitmap?) {
                    if (bitmap != null) {
                        callback.onFirst(bitmap)
                    } else {
                        callback.onSecond(true)
                    }
                }
            }, CallerThreadExecutor.getInstance())
    }

    @SuppressLint("CheckResult")
    private fun getBitmapWithPicasso(url: String, callback: BiCallback<Bitmap, Boolean>) {
        Single.just(url)
            .map {
                Picasso
                    .with(HaloApp.getInstance())
                    .load(url)
                    .priority(Picasso.Priority.HIGH)
                    .get()
            }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                callback.onFirst(it)
            }, {
                callback.onSecond(true)
                it.printStackTrace()
            })
    }

    /**
     * 加载显示图片
     */
    @JvmStatic
    fun display(view: SimpleDraweeView?, url: String?) {
        display(view, url, true)
    }

    /**
     * 加载图片，但图片不放到内存缓存中
     * 适用于仅加载一次，应用启动生命周期内不再复用的场景
     */
    fun displayWithoutMemoryCache(view: SimpleDraweeView?, url: String?) {
        view?.setTag(TAG_NO_MEMORY_CACHE, true)
        display(view, url, true, null, true)
    }

    private fun displayInternal(
        view: SimpleDraweeView?,
        url: String?,
        loadAsAnimatedImage: Boolean = true,
        processor: Postprocessor? = null,
        isIgnoreWidth: Boolean = false,
        controllerListener: BaseControllerListener<ImageInfo>? = null,
    ) {
        if (url.isNullOrEmpty()) return

        imageDecorationThread.execute {
            val targetWidth = view?.getTag(TAG_TARGET_WIDTH) as? Int

            // 部分自适应宽高图片需要一个 TARGET_WIDTH 来避免加载过小图片
            val width = targetWidth ?: view?.width?.coerceAtLeast(view.layoutParams?.width ?: 0)
            val disableMemoryCache = view?.getTag(TAG_NO_MEMORY_CACHE) == true

            if (view != null && width == 0 && !isIgnoreWidth) {
                AsyncImageLoader.getInstance().addAsyncImage(view, url, loadAsAnimatedImage, processor, controllerListener)
                return@execute
            }
            var lowResUrl = ""
            var highResUrl = ""

            // 找同一图片地址已加载过的图片作为低质量预览图(字符串操作耗时不短，所以这里放到了独立的子线程来处理)
            // TODO 根据实际请求大小(w_width)来避免小图用大图作为低质量图片
            for (cachedImageUrl in imageUrlCacheSet) {
                if (url.isNotEmpty() && cachedImageUrl.startsWith(url)) {
                    lowResUrl = cachedImageUrl
                    break
                }
            }

            if (loadAsAnimatedImage && view?.tag == url) return@execute

            if (!loadAsAnimatedImage
                || displayStaticImageOnly
                || (width != null && width > 0)
            ) {
                view ?: return@execute

                highResUrl = getTunedImageUrl(view, url, width ?: 0, loadAsAnimatedImage, processor)
                runOnUiThread {
                    loadImage(view, highResUrl, lowResUrl, processor, disableMemoryCache, controllerListener)
                }
            } else {
                runOnUiThread {
                    view ?: return@runOnUiThread
                    highResUrl = getTunedImageUrl(view, url, width ?: view.width,  true, processor)
                    loadImage(view, highResUrl, lowResUrl, processor, disableMemoryCache, controllerListener)
                }
            }
            view?.tag = url
        }
    }

    /**
     *  显示图片
     *
     *  @param loadAsAnimatedImage 是否支持动图
     */
    fun display(
        view: SimpleDraweeView?,
        url: String?,
        loadAsAnimatedImage: Boolean = true,
        processor: Postprocessor? = null,
        isIgnoreWidth: Boolean = false,
        controllerListener: BaseControllerListener<ImageInfo>? = null
    ) {
        displayInternal(view, url, loadAsAnimatedImage, processor, isIgnoreWidth, controllerListener)
    }

    /**
     * 获取经过优化处理后的图片 url 地址
     */
    private fun getTunedImageUrl(view: SimpleDraweeView,
                                 url: String,
                                 width: Int,
                                 loadAsAnimatedImage: Boolean,
                                 processor: Postprocessor?): String {
        val isGameIcon = view.getTag(TAG_IS_GAME_ICON) == true

        // displayStaticImageOnly 时，走普通 getIdealImageUrl 逻辑
        val modifiedUrl = if (isGameIcon && !displayStaticImageOnly) {
            getIdealGameIconUrl(url, width)
        } else {
            getIdealImageUrl(url, width, loadAsAnimatedImage) ?: ""
        }

        if (processor is TopCutProcess) {
            if (width > 0) {
                val ratioHeight = (width / processor.cutRatio).toInt()
                if (ratioHeight > 0) {
                    val configProvider = TheRouter.get(IConfigProvider::class.java)

                    val cropSuffix = configProvider?.getImageCropSuffix() ?: ""

                    return if (modifiedUrl.contains("s-process")) {
                        "${modifiedUrl}/crop,h_${ratioHeight}"
                    } else if (cropSuffix.isNotEmpty()){
                        "${modifiedUrl}${cropSuffix},h_${ratioHeight}"
                    } else {
                        modifiedUrl
                    }
                }
            }
        }

        return modifiedUrl
    }

    private fun View.findFragment(): Fragment? {
        var view: View? = this
        val addTagViewList = ArrayList<View>()
        while (view != null) {
            val tag = view.getTag(androidx.fragment.R.id.fragment_container_view_tag)
            if (tag is Fragment) {
                addTagViewList.forEach {
                    it.setTag(androidx.fragment.R.id.fragment_container_view_tag, tag)
                }
                return tag
            }
            addTagViewList.add(view)
            view = view.parent as? View
        }
        return null
    }

    private fun loadImage(
        view: SimpleDraweeView?,
        highResUrl: String,
        lowResUrl: String,
        processor: Postprocessor?,
        shouldNotSaveMemoryCache: Boolean,
        controllerListener: BaseControllerListener<ImageInfo>? = null,
    ) {
        val lifecycleObserver = view?.getTag(R.id.lifecycle_observer) as? LifecycleObserver
        val lifecycle = if (lifecycleObserver != null) {
            try {
                view.findFragment()?.viewLifecycleOwner?.lifecycle
            } catch (e: Exception) {
                null
            }
        } else null
        lifecycleObserver?.let {
            lifecycle?.removeObserver(it)
            view.setTag(R.id.lifecycle_observer, null)
        }

        val listener = getFinalControllerListener(view, controllerListener, lifecycle)
        val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(highResUrl))
            .apply {
                if (shouldNotSaveMemoryCache) {
                    disableMemoryCache()
                }
                postprocessor = processor
            }
            .build()
        val controller = Fresco.newDraweeControllerBuilder()
            .setImageRequest(imageRequest)
            .setControllerListener(listener)
            .apply {
                if (lowResUrl.isNotEmpty()
                    && lowResUrl != highResUrl
                    && highResUrl != view?.getTag(R.string.highResImageTag)
                ) {
                    lowResImageRequest =
                        ImageRequestBuilder.newBuilderWithSource(Uri.parse(lowResUrl))
                            .setPostprocessor(processor)
                            .build()
                }
            }
            .build()
        view?.controller = controller

        view?.setTag(R.string.highResImageTag, highResUrl)
    }

    private fun getFinalControllerListener(
        view: SimpleDraweeView?,
        controllerListener: BaseControllerListener<ImageInfo>?,
        lifecycle: Lifecycle?
    ) = object : BaseControllerListener<ImageInfo>() {
        override fun onSubmit(id: String?, callerContext: Any?) {
            super.onSubmit(id, callerContext)
            controllerListener?.onSubmit(id, callerContext)
        }
        override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
            super.onFinalImageSet(id, imageInfo, animatable)
            controllerListener?.onFinalImageSet(id, imageInfo, animatable)
            animatable?.let {
                val viewLifecycle = try {
                    lifecycle ?: view?.findFragment()?.viewLifecycleOwner?.lifecycle
                } catch (e: Exception) {
                    null
                }
                viewLifecycle?.run {
                    val observer = object : DefaultLifecycleObserver {
                        override fun onPause(owner: LifecycleOwner) {
                            super.onPause(owner)
                            it.stop()
                        }

                        override fun onResume(owner: LifecycleOwner) {
                            super.onResume(owner)
                            it.start()
                        }

                        override fun onDestroy(owner: LifecycleOwner) {
                            super.onDestroy(owner)
                            removeObserver(this)
                        }
                    }
                    view?.setTag(R.id.lifecycle_observer, observer)
                    addObserver(observer)
                } ?: it.start()
            }
        }
        override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
            super.onIntermediateImageSet(id, imageInfo)
            controllerListener?.onIntermediateImageSet(id, imageInfo)
        }
        override fun onIntermediateImageFailed(id: String?, throwable: Throwable?) {
            super.onIntermediateImageFailed(id, throwable)
            controllerListener?.onIntermediateImageFailed(id, throwable)
        }
        override fun onFailure(id: String?, throwable: Throwable?) {
            super.onFailure(id, throwable)
            controllerListener?.onFailure(id, throwable)
        }
        override fun onRelease(id: String?) {
            super.onRelease(id)
            controllerListener?.onRelease(id)
        }
    }

    private fun addCachedUrl(url: String) {
        imageDecorationThread.execute {
            if (url.startsWith("http")) {
                imageUrlCacheSet.add(url)
            }
        }
    }

    /**
     * 获取已缓存的图片地址，不区分质量，暂时只供查看大图页面使用
     */
    fun getCachedUrl(url: String): String {
        // 创建一个 Snapshot Set 供主线程遍历用
        tryCatchInRelease {
            val snapshotUrlSet = HashSet<String>(imageUrlCacheSet)
            for (decoratedUrl in snapshotUrlSet) {
                if (decoratedUrl.contains(url)) {
                    return decoratedUrl.also { snapshotUrlSet.clear() }
                }
            }
        }
        return url
    }

    @JvmStatic
    fun bmpToByteArray(bmp: Bitmap, needRecycle: Boolean): ByteArray {
        val output = ByteArrayOutputStream()
        bmp.compress(Bitmap.CompressFormat.PNG, 100, output)
        if (needRecycle) {
            bmp.recycle()
        }

        val result = output.toByteArray()
        try {
            output.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return result
    }

    @JvmStatic
    fun display(draweeView: SimpleDraweeView, @DrawableRes res: Int?) {
        draweeView.setImageURI("res:///" + res)
    }

    fun displayResizeMedia(
        draweeView: SimpleDraweeView,
        uri: Uri,
        resizeWidthDp: Int,
        resizeHeightDp: Int
    ) {
        val request = ImageRequestBuilder.newBuilderWithSource(uri)
            .setResizeOptions(ResizeOptions(resizeWidthDp, resizeHeightDp))
            .build()
        val controller = Fresco.newDraweeControllerBuilder()
            .setImageRequest(request)
            .setOldController(draweeView.controller)
            .setControllerListener(BaseControllerListener())
            .build()
        draweeView.controller = controller
    }

    // 预加载图片
    fun prefetchToDiskCache(url: String, callback: ((Boolean) -> Unit)? = null) {
        val imagePipeline = Fresco.getImagePipeline()
        val imageRequest = ImageRequest.fromUri(url)
        imagePipeline.prefetchToDiskCache(imageRequest, HaloApp.getInstance(), object : RequestListener {
            override fun onProducerStart(requestId: String?, producerName: String?) {
                Utils.log(TAG, "onProducerStart $requestId")
            }

            override fun onProducerEvent(requestId: String?, producerName: String?, eventName: String?) {
                Utils.log(TAG, "onProducerEvent $requestId")
            }

            override fun onProducerFinishWithSuccess(
                requestId: String?,
                producerName: String?,
                extraMap: MutableMap<String, String>?
            ) {
                Utils.log(TAG, "onProducerFinishWithSuccess $requestId")
            }

            override fun onProducerFinishWithFailure(
                requestId: String?,
                producerName: String?,
                t: Throwable?,
                extraMap: MutableMap<String, String>?
            ) {
                Utils.log(TAG, "onProducerFinishWithFailure $requestId")
            }

            override fun onProducerFinishWithCancellation(
                requestId: String?,
                producerName: String?,
                extraMap: MutableMap<String, String>?
            ) {
                Utils.log(TAG, "onProducerFinishWithCancellation $requestId")
            }

            override fun onUltimateProducerReached(requestId: String?, producerName: String?, successful: Boolean) {
                Utils.log(TAG, "onUltimateProducerReached $requestId")
            }

            override fun requiresExtraMap(requestId: String?): Boolean {
                Utils.log(TAG, "requiresExtraMap $requestId")
                return false
            }

            override fun onRequestStart(
                request: ImageRequest?,
                callerContext: Any?,
                requestId: String?,
                isPrefetch: Boolean
            ) {
                Utils.log(TAG, "onRequestStart $requestId")
            }

            override fun onRequestSuccess(request: ImageRequest?, requestId: String?, isPrefetch: Boolean) {
                Utils.log(TAG, "onRequestSuccess $requestId isPrefetch $isPrefetch")
                callback?.invoke(true)
            }

            override fun onRequestFailure(
                request: ImageRequest?,
                requestId: String?,
                throwable: Throwable?,
                isPrefetch: Boolean
            ) {
                Utils.log(TAG, "onRequestFailure $requestId isPrefetch $isPrefetch error ${throwable?.localizedMessage}")
                callback?.invoke(false)
            }

            override fun onRequestCancellation(requestId: String?) {
                Utils.log(TAG, "onRequestCancellation $requestId")
                callback?.invoke(false)
            }
        })
    }

    /**
     * 获取理想的图片地址
     */
    fun getIdealImageUrl(url: String?, width: Int?, loadAsAnimatedImage: Boolean = true): String? {
        if (url?.startsWith("res:") == true) return url

        val idealUrl: String? = if (width != null && width > 0) {
            limitWidthWithRuleIfPossible(url, width, loadAsAnimatedImage)
        } else {
            limitWidthWithRuleIfPossible(url, null, loadAsAnimatedImage)
        }
        addCachedUrl(idealUrl ?: "")
        return idealUrl
    }

    /**
     * 获取理想的游戏 icon 地址
     */
    private fun getIdealGameIconUrl(url: String, width: Int): String {
        if (url.startsWith("res:")) return url

        val idealSize = getIdealIconSize(width)
        val configProvider = TheRouter.get(IConfigProvider::class.java)
        val resizeConfig = configProvider?.getImageResizeSuffix() ?: "?x-oss-process=image/resize"

        return "$url$resizeConfig,h_$idealSize,w_$idealSize".also { addCachedUrl(it) }
    }

    private fun getIdealIconSize(width: Int): String {
        return if (width > LARGE_ANIMATED_ICON_SIZE) {
            "256"
        } else if (width >= STANDARD_ANIMATED_ICON_SIZE) {
            "192"
        } else if (width > TINY_ANIMATED_ICON_SIZE) {
            "128"
        } else {
            "64"
        }
    }

    fun getVideoSnapshot(videoUrl: String, progress: Long): String {
        val configProvider = TheRouter.get(IConfigProvider::class.java)
        return if (!configProvider?.getVideoSnapshotSuffix().isNullOrEmpty()) {
            "$videoUrl${configProvider?.getVideoSnapshotSuffix()},t_$progress,f_jpg,w_0,h_0,ar_auto"
        } else {
            "$videoUrl?x-oss-process=video/snapshot,t_$progress,f_jpg,w_0,h_0,ar_auto"
        }
    }

    /**
     * 将图片保存到外部存储空间
     * @param imageFile 内部存储的文件
     * @param url 图片原地址，用来截取当图片名称用的
     * @param useRandomName 是否使用随机名称
     */
    fun saveImageToFile(imageFile: File, url: String?, useRandomName: Boolean = false, callback: ((Boolean) -> Unit)? = null) {
        var `in`: InputStream? = null
        var out: OutputStream? = null
        try {
            val fileName: String = if (useRandomName) {
                MD5Utils.getContentMD5(System.currentTimeMillis().toString()) + ".png"
            } else {
                Uri.parse(url).lastPathSegment.toString()
            }
            val savePath =
                Environment.getExternalStorageDirectory().absolutePath + "/Pictures/ghzhushou/"
            val file = File(savePath)
            if (!file.exists()) {
                file.mkdirs()
            }
            val dst = File(savePath, fileName)
            if (dst.exists()) {
                dst.delete()
            }
            `in` = FileInputStream(imageFile)
            out = FileOutputStream(dst)
            val buf = ByteArray(1024)
            var len: Int
            while (`in`.read(buf).also { len = it } > 0) {
                out.write(buf, 0, len)
            }

            runOnUiThread {
                ToastUtils.toast("图片已保存到/Pictures/ghzhushou/")
                callback?.invoke(true)
            }
            BitmapUtils.refreshImage(HaloApp.getInstance(), dst)
        } catch (e: Exception) {
            Utils.log("图片保存失败：$e")
            runOnUiThread {
                callback?.invoke(false)
            }
        } finally {
            tryWithDefaultCatch {
                out?.close()
                `in`?.close()
            }
        }
    }

    @JvmStatic
    fun clearCaches() {
        picassoLruCache.clear()
        imageDecorationThread.execute {
            imageUrlCacheSet.clear()
        }
    }

    /**
     * 这里反射获取 sDraweecontrollerbuildersupplier 根据是否有值确定是否被初始化了
     */
    @JvmStatic
    fun isFrescoInitialized(): Boolean {
        val clazz = SimpleDraweeView::class.java
        return try {
            val field =
                clazz.getDeclaredField("sDraweecontrollerbuildersupplier")
            field.isAccessible = true
            val obj = field[SimpleDraweeView::class.java]
            obj != null
        } catch (ignore: java.lang.Exception) {
            false
        }
    }

}
