package com.gh.gamecenter.common.utils

import android.animation.Animator
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.ContextWrapper
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Build
import android.text.*
import android.text.Html.TagHandler
import android.text.format.Formatter
import android.text.style.ClickableSpan
import android.text.style.ImageSpan
import android.text.style.URLSpan
import android.util.Log
import android.util.TypedValue
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.webkit.WebView
import android.widget.*
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.*
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Recycler
import androidx.viewbinding.ViewBinding
import androidx.viewpager.widget.ViewPager
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature
import com.airbnb.lottie.LottieAnimationView
import com.facebook.drawee.generic.RoundingParams
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.gamecenter.common.BuildConfig
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.callback.SimpleCallback
import com.gh.gamecenter.common.constant.Config
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.view.CustomLinkMovementMethod
import com.gh.gamecenter.common.view.ExpandAndShrinkTextView
import com.gh.gamecenter.common.view.ExpandTextView
import com.gh.gamecenter.core.HaloApp
import com.gh.gamecenter.core.provider.*
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.*
import com.google.gson.reflect.TypeToken
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
import com.therouter.TheRouter
import io.reactivex.Observable
import io.reactivex.ObservableTransformer
import io.reactivex.SingleTransformer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONArray
import org.json.JSONObject
import java.io.*
import java.lang.reflect.ParameterizedType
import java.net.URI
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
import kotlin.math.abs


/**
 * 创建以 activity 为观察者上下文的 viewModel
 */
inline fun <reified VM : ViewModel> FragmentActivity.viewModelProvider(
    provider: ViewModelProvider.Factory? = null
) =
    ViewModelProviders.of(this, provider).get(VM::class.java)

/**
 * 创建以 activity 为观察者上下文的 viewModel
 * 额外的 key: 用于区分单 activity 多 viewModel 的情况 (如首页tab)
 */
inline fun <reified VM : ViewModel> Fragment.viewModelProviderFromParent(
    provider: ViewModelProvider.Factory? = null,
    key: String = ""
) = if (key.isEmpty()) {
    ViewModelProviders.of(requireActivity(), provider).get(VM::class.java)
} else {
    ViewModelProviders.of(requireActivity(), provider).get(key, VM::class.java)
}

/**
 * 创建以 activity 为观察者上下文的 viewModel
 */
inline fun <reified VM : ViewModel> FragmentActivity.viewModelProviderFromParent(
    provider: ViewModelProvider.Factory? = null
) =
    ViewModelProviders.of(this, provider).get(VM::class.java)

/**
 * 创建以 fragment 为观察者上下文的 viewModel
 */
inline fun <reified VM : ViewModel> Fragment.viewModelProvider(
    provider: ViewModelProvider.Factory? = null
) =
    ViewModelProviders.of(this, provider).get(VM::class.java)

/**
 *
 * ViewPager Extensions
 *
 */
fun ViewPager.doOnPageSelected(action: (position: Int) -> Unit) =
    addOnPageChangeListener(onSelected = action)

fun ViewPager.addOnPageChangeListener(onSelected: ((position: Int) -> Unit)? = null) {
    val listener = object : ViewPager.OnPageChangeListener {
        override fun onPageScrolled(
            position: Int,
            positionOffset: Float,
            positionOffsetPixels: Int
        ) {
            // Do nothing.
        }

        override fun onPageSelected(position: Int) {
            onSelected?.invoke(position)
        }

        override fun onPageScrollStateChanged(state: Int) {
            // Do nothing.
        }
    }
    addOnPageChangeListener(listener)
}

fun ViewPager.doOnScroll(
    onStateChanged: ((state: Int) -> Unit)? = null,
    onPageScrolled: ((position: Int, positionOffset: Float, positionOffsetPixels: Int) -> Unit)? = null,
    onPageSelected: ((position: Int) -> Unit)? = null
) {
    val listener = object : ViewPager.OnPageChangeListener {
        override fun onPageScrolled(
            position: Int,
            positionOffset: Float,
            positionOffsetPixels: Int
        ) {
            onPageScrolled?.invoke(position, positionOffset, positionOffsetPixels)
        }

        override fun onPageSelected(position: Int) {
            onPageSelected?.invoke(position)
        }

        override fun onPageScrollStateChanged(state: Int) {
            onStateChanged?.invoke(state)
        }
    }
    addOnPageChangeListener(listener)
}


/**
 * Fragment related
 */
inline fun <reified T : Fragment> Fragment.fragmentFromActivity() =
    parentFragmentManager.findFragmentByTag(T::class.java.simpleName) as? T
        ?: parentFragmentManager.fragmentFactory.instantiate(
            requireContext().classLoader,
            T::class.java.canonicalName
        ) as T


inline fun <reified T : Fragment> Fragment.fragmentFromParentFragment() =
    childFragmentManager.findFragmentByTag(T::class.java.simpleName) as? T
        ?: childFragmentManager.fragmentFactory.instantiate(
            requireContext().classLoader,
            T::class.java.canonicalName
        ) as T

/**
 * 注册 fragmentDestroy 回调
 */
fun Fragment.doOnFragmentDestroy(onDestroyAction: () -> Unit) {
    if (!isAdded) return

    fragmentManager?.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
        override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) {
            super.onFragmentDestroyed(fm, f)
            if (this@doOnFragmentDestroy == f) {
                this@doOnFragmentDestroy.fragmentManager?.unregisterFragmentLifecycleCallbacks(this)
            }

            onDestroyAction.invoke()
        }
    }, false)
}

/**
 * RecyclerView Extensions
 */

// 监听滚动距离
fun RecyclerView.doOnScrolledSpecificDistance(
    distanceX: Int = 0,
    distanceY: Int = 0,
    singleTimeEvent: Boolean = false,
    action: () -> Unit
) {
    val listener = object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)

            if ((distanceX != 0 && abs(dx) > distanceX) || (distanceY != 0 && abs(dy) > distanceY)) {
                action.invoke()
                if (singleTimeEvent) {
                    removeOnScrollListener(this)
                }
            }
        }
    }

    addOnScrollListener(listener)
}

/**
 * ViewBinding related Extensions
 */
inline fun <reified T : ViewBinding> ViewGroup.toBinding(): T {
    return T::class.java.getMethod(
        "inflate",
        LayoutInflater::class.java,
        ViewGroup::class.java,
        Boolean::class.java
    ).invoke(null, layoutInflater, this, false) as T
}

/**
 * View Extensions
 */
fun View.visibleIf(predicate: Boolean, visibleCallback: (() -> Unit)? = null) {
    visibility = if (predicate) {
        visibleCallback?.invoke()
        View.VISIBLE
    } else {
        View.INVISIBLE
    }
}

fun View.goneIf(predicate: Boolean) {
    goneIf(predicate, null)
}

fun View.goneIf(predicate: Boolean, visibleCallback: (() -> Unit)? = null) {
    visibility = if (predicate) {
        View.GONE
    } else {
        visibleCallback?.invoke()
        View.VISIBLE
    }
}

/**
 * view 在屏幕上的可见比例
 * @param percentage 取值范围 0~0.99
 */
fun View.isVisibleOnScreenLargerThanPercentage(percentage: Float): Boolean {
    val visibleRect = Rect()
    getGlobalVisibleRect(visibleRect)

    return ((visibleRect.height() * visibleRect.width().toFloat()) / (height * width)) > percentage
}

fun View.addSelectableItemBackground() {
    val outValue = TypedValue()
    context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
    setBackgroundResource(outValue.resourceId)
}

fun View.removeSelectableItemBackground() {
    background = null
}

fun View.setRoundedColorBackground(@ColorRes color: Int, radius: Float) {
    val shape = GradientDrawable()
    shape.cornerRadius = radius.dip2px().toFloat()
    shape.setColor(ContextCompat.getColor(context, color))
    background = shape
}

fun View.setRoundedColorBackgroundByColorInt(@ColorInt color: Int, radius: Float) {
    val shape = GradientDrawable()
    shape.cornerRadius = radius.dip2px().toFloat()
    shape.setColor(color)
    background = shape
}

fun View.setOnlyTopRoundedColorBackground(color: Int, radius: Float) {
    val shape = GradientDrawable()
    shape.cornerRadii = floatArrayOf(
        radius.dip2px().toFloat(),
        radius.dip2px().toFloat(),
        radius.dip2px().toFloat(),
        radius.dip2px().toFloat(),
        0F,
        0F,
        0F,
        0F
    )
    shape.setColor(color)
    background = shape
}

fun View.setDebouncedClickListener(action: () -> Unit) {
    setOnClickListener { debounceActionWithInterval(interval = 300L) { action.invoke() } }
}

val View.layoutInflater: LayoutInflater
    get() = LayoutInflater.from(this.context)

fun View.removeFromParent(withDelay: Boolean = false) {
    val removeClosure = {
        try {
            if (parent == null) {
                Utils.log(javaClass.simpleName, "getParent() returning Null")
            } else {
                try {
                    (parent as ViewGroup).removeView(this@removeFromParent)
                } catch (ex: Exception) {
                    Utils.log(javaClass.simpleName, "Cannot remove from parent layout")
                }
            }
        } catch (ex: Exception) {
            Utils.log(javaClass.simpleName, Log.getStackTraceString(ex))
        }
    }

    if (withDelay) {
        postDelayed({ removeClosure.invoke() }, 100)
    } else {
        removeClosure.invoke()
    }
}

/**
 * 记录 View 所在的业务组件名称
 */
fun View.putWidgetBusinessName(businessName: String) {
    setTag(R.string.view_business_name, businessName)
}

/**
 * 获取 View 所在的业务组件名称
 */
fun View.getWidgetBusinessName(): String {
    return getTag(R.string.view_business_name) as? String ?: "其它"
}

/**
 *  View 绑定对象
 */
fun View.putObject(any: Any) {
    setTag(R.string.view_bounded_object, any)
}

/**
 * 获取 View 绑定的对象
 */
fun View.getObject(): Any? {
    return getTag(R.string.view_bounded_object)
}

/**
 * 扩大 View 的点击区域
 */
fun View.enlargeTouchArea(enlargedSizeInPx: Int = 4F.dip2px()) {
    val parent = parent as View

    parent.post {
        val rect = Rect()
        getHitRect(rect)
        rect.top -= enlargedSizeInPx
        rect.left -= enlargedSizeInPx
        rect.bottom += enlargedSizeInPx
        rect.right += enlargedSizeInPx
        parent.touchDelegate = TouchDelegate(rect, this)
    }
}

@Deprecated("Use EnvHelper.isDevEnv instead", ReplaceWith("!EnvHelper.isDevEnv"))
fun isPublishEnv(): Boolean {
    return !EnvHelper.isDevEnv
}

/**
 * LiveData Extensions
 */
fun <T> LiveData<T?>.observeNonNull(owner: LifecycleOwner, callback: (T) -> Unit) {
    observe(owner, Observer { value ->
        if (value != null) {
            callback(value)
        }
    })
}

/**
 * Login related extensions
 */
fun Fragment.ifLogin(entrance: String, action: (() -> Unit)? = null) {
    requireContext().ifLogin(entrance, action)
}

fun Context.ifLogin(entrance: String, action: (() -> Unit)? = null) {
    val checkLoginConfig = TheRouter.get(ICheckLoginProvider::class.java)
    checkLoginConfig?.checkLogin(this, entrance, action)
}

fun Context.getActivity(): Activity? {
    return when (this) {
        is Activity -> this
        is ContextWrapper -> this.baseContext.getActivity()
        else -> null
    }
}

/**
 * Gson related extensions.
 */
inline fun <reified T : Any> String.toObject(): T? {
    return try {
        GsonUtils.gson.fromJson(this, object : TypeToken<T>() {}.type)
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

inline fun <reified T : Any> T.toJson(): String {
    return GsonUtils.toJson(this)
}

inline fun <reified T : Any> T.toJsonIgnoredNull(): String {
    return GsonUtils.toJsonIgnoreNull(this)
}

fun String.insert(index: Int, string: String): String {
    return this.substring(0, index) + string + this.substring(index, this.length)
}

/**
 * TextView 内部处理 ul li ol 得跟 Android 版本走，这里换成专属的标签手动处理
 */
fun String.replaceUnsupportedHtmlTag(): String {
    return this.replace("<ul", "<hul")
        .replace("</ul>", "</hul>")
        .replace("<li", "<hli")
        .replace("</li>", "</hli>")
        .replace("<ol", "<hol")
        .replace("</ol>", "</hol>")
}

fun String.containHtmlTag(): Boolean {
    val pattern = Pattern.compile("<(\"[^\"]*\"|'[^']*'|[^'\">])*>")
    val matcher = pattern.matcher(this)
    return matcher.find()
}

fun String.dropFontColorInDarkMode(context: Context) : String {
    if (!DarkModeUtils.isDarkModeOn(context)) return this

    return this.replace(Regex("<font[^>]*color[^>]*>"), "")
        .replace(Regex("(?<!-)color\\s*:\\s*[^;\"']+;?"), "") // 匹配css属性中的颜色
}

/**
 * 用户行为相关
 */
fun Fragment.showRegulationTestDialogIfNeeded(action: (() -> Unit)) {
    val provider = TheRouter.get(IDirectProvider::class.java)
    val userManagerProvider = TheRouter.get(IUserManagerProvider::class.java)
    val dialogUtils = TheRouter.get(IDialogUtilsProvider::class.java)
    if (userManagerProvider?.userShouldTakeRegulationBaseOnLastRemind() == true) {
        dialogUtils?.showRegulationTestDialog(requireContext(),
            { provider?.directToRegulationTestPage(requireContext()) },
            { action.invoke() })
    } else {
        action()
    }
}

fun Context.showRegulationTestDialogIfNeeded(action: (() -> Unit)) {
    val provider = TheRouter.get(IDirectProvider::class.java)
    val userManagerProvider = TheRouter.get(IUserManagerProvider::class.java)
    val dialogUtils = TheRouter.get(IDialogUtilsProvider::class.java)
    if (userManagerProvider?.userShouldTakeRegulationBaseOnLastRemind() == true) {
        dialogUtils?.showRegulationTestDialog(this,
            { provider?.directToRegulationTestPage(this) },
            { action.invoke() })
    } else {
        action()
    }
}

/**
 * Serializable 相关
 */
fun <T> Serializable.deepClone(): T? {
    Utils.log("深复制对象 $this")
    return try {
        val baos = ByteArrayOutputStream()
        val oos = ObjectOutputStream(baos)
        oos.writeObject(this)
        val bais = ByteArrayInputStream(baos.toByteArray())
        val ois = ObjectInputStream(bais)
        ois.readObject() as T
    } catch (e: IOException) {
        null
    } catch (e: ClassNotFoundException) {
        null
    }
}

/**
 * 在限定 interval 里只触发一次 action
 */
fun debounceActionWithInterval(id: Int, interval: Long = 300, action: (() -> Unit)? = null) {
    if (!ClickUtils.isFastDoubleClick(id, interval)) {
        action?.invoke()
    }
}

fun View.debounceActionWithInterval(interval: Long = 300, action: (() -> Unit)? = null) {
    debounceActionWithInterval(this.id, interval, action)
}

/**
 * 告诉需要返回 true or false 的外层这个事件已经被消费（即返回 true）
 */
inline fun consume(f: () -> Unit): Boolean {
    f()
    return true
}

/**
 * 简化那些不得不写 try catch 的代码块
 */
inline fun tryWithDefaultCatch(action: (() -> Unit)) {
    try {
        action.invoke()
    } catch (e: Throwable) {
        e.printStackTrace()
    }
}

/**
 * 只在正式版本进行 try catch 操作
 *
 * 对于个别偶发的异常尽量不要做暴力的 try catch 处理
 */
inline fun tryCatchInRelease(action: (() -> Unit)) {
    try {
        action.invoke()
    } catch (e: Throwable) {
        if (BuildConfig.DEBUG) throw e
        else e.printStackTrace()
    }
}

/**
 * 在 debug 状态下抛出异常
 */
fun throwExceptionInDebug(message: String = "", predicate: Boolean = true) {
    if (predicate && BuildConfig.DEBUG) {
        throw RuntimeException(message)
    }
}

/**
 * 在自动打包的包里弹 toast
 */
fun toastInInternalRelease(content: String) {
    if (BuildConfig.BUILD_TIME != 0L) {
        Utils.toast(HaloApp.getInstance(), content)
    }
}

/**
 * 主动抛出异常
 */
fun throwException(message: String = "", predicate: Boolean = true) {
    if (predicate) {
        throw RuntimeException(message)
    }
}

/**
 * String related
 */
fun String.fromHtml(): Spanned {
    return HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_LEGACY)
}

fun String.fromHtmlCompat(imageGetter: Html.ImageGetter? = null, tagHandler: TagHandler? = null): Spanned {
    return HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_COMPACT, imageGetter, tagHandler)
}

// 将双引号换成单引号避免 JSON 解析异常 ( escape 后会有其它异常，暂时先替换成单引号了)
fun String.eliminateDoubleQuote(): String {
    return this.replace("\"", "'")
}

// 去掉文章/答案的插入内容
fun String.removeInsertedContent(): String {
    val textRegex = "(?s)<div class=\"gh-internal-content content-right\".*?</div>"
    return this.replace(textRegex.toRegex(), "")
}

// 去除视频相关文本
fun String.removeVideoContent(): String {
    val videoRegex =
        "(?s)<div data-id.*?class=\"placeholder-video-container\".*? class=\"video-poster-btn\".*?</div>"
    return this.replace(videoRegex.toRegex(), "")
}

// 完全地清除所有 Html 格式
fun String.clearHtmlFormatCompletely(): String {
    return Html.fromHtml(this).toString().replace('\n', 32.toChar())
        .replace(160.toChar(), 32.toChar()).replace(65532.toChar(), 32.toChar()).trim { it <= ' ' }
}

// 如果该字符串长度超过固定长度的话，从头开始截取固定长度并返回
fun String.subStringIfPossible(length: Int): String {
    return if (this.length > length) {
        this.substring(0, length)
    } else {
        this
    }
}

fun String.countOccurrences(char: String): Int {
    return StringTokenizer(" $this ", char).countTokens() - 1
}

fun String.getFirstElementDividedByDivider(divider: String): String {
    if (this.contains(divider)) {
        return this.split(divider.toRegex()).toTypedArray()[0]
    }
    return this
}

fun String.copyText() {
    this.copyTextAndToast("")
}

fun String.copyTextAndToast(toastText: String = "复制成功") {
    try {
        val application = HaloApp.getInstance()
        val cmb = application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
        val clip = ClipData.newPlainText(null, this)
        cmb.setPrimaryClip(clip)

        if (!TextUtils.isEmpty(toastText)) {
            ToastUtils.showToast(toastText)
        }
    } catch (e: SecurityException) {
        // 在一些情况下会报以下这样的错误，可能是以浮动窗口显示然后没有焦点？(https://developer.android.com/about/versions/10/privacy/changes#clipboard-data)
        // java.lang.Throwable: java.lang.SecurityException: com.xunmeng.pinduoduo from uid 10317 not allowed to perform READ_CLIPBOARD
        ToastUtils.showToast("复制失败，请重试")
    }
}

fun Map<String, String>.createRequestBody(): RequestBody {
    val json = GsonUtils.toJson(this)
    return json.toRequestBody("application/json".toMediaTypeOrNull())
}

fun Map<String, Any>.createRequestBodyAny(): RequestBody {
    val json = GsonUtils.toJson(this)
    return json.toRequestBody("application/json".toMediaTypeOrNull())
}

fun Any.toRequestBody(): RequestBody {
    val json = GsonUtils.toJson(this)
    return json.toRequestBody("application/json".toMediaTypeOrNull())
}

fun JSONObject.toRequestBody(): RequestBody {
    return this.toString().toRequestBody("application/json".toMediaTypeOrNull())
}

fun JSONArray.toRequestBody(): RequestBody {
    return this.toString().toRequestBody("application/json".toMediaTypeOrNull())
}

// 对在浏览器(WebView)显示的路径进行转码
fun String.decodeURI(): String {
    return URI(null, null, this, null).rawPath
}

/**
 * 根据手机的分辨率从 dip(像素) 的单位 转成为 px
 */
fun Float.dip2px(): Int {
    val scale = HaloApp.getInstance().resources.displayMetrics.density
    return (this * scale + 0.5f).toInt()
}

/**
 * 根据手机的分辨率从 dip(像素) 的单位 转成为 px
 */
fun Float.dip2px(context: Context): Int {
    val scale = context.resources.displayMetrics.density
    return (this * scale + 0.5f).toInt()
}

/**
 * 根据手机的分辨率从 px(像素) 的单位 转成为 dip
 */
fun Float.px2dip(): Int {
    val scale = HaloApp.getInstance().resources.displayMetrics.density
    return (this / scale + 0.5f).toInt()
}

fun Float.sp2px(): Int {
    val scale: Float = HaloApp.getInstance().resources.displayMetrics.scaledDensity
    return (this * scale + 0.5f).toInt()
}

fun Float.roundTo(n: Int): Float {
    return "%.${n}f".format(Locale.CHINA, this).toFloat()
}

/**
 * PopupWindow 自动适配方向
 * 弹出与锚点右对齐
 */
fun PopupWindow.showAutoOrientation(anchorView: View, distanceY: Int = 0, distanceX: Int = 0) {
    val windowPos = IntArray(2)
    val anchorLoc = IntArray(2)
    // 获取锚点View在屏幕上的左上角坐标位置
    anchorView.getLocationOnScreen(anchorLoc)
    val anchorHeight = anchorView.height + distanceY
    // 获取屏幕的高宽
    val screenHeight = anchorView.context.resources.displayMetrics.heightPixels
    // 测量contentView
    contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
    // 计算contentView的高宽
    val windowHeight = contentView.measuredHeight
    val windowWidth = contentView.measuredWidth

    // 判断需要向上弹出还是向下弹出显示
    val isNeedShowUp = screenHeight - anchorLoc[1] - anchorHeight < windowHeight
    windowPos[1] = if (isNeedShowUp) {
        anchorLoc[1] - windowHeight
    } else anchorLoc[1] + anchorHeight
    windowPos[0] = anchorLoc[0] - windowWidth + anchorView.width - distanceX

    animationStyle = R.style.popwindow_option_anim_style
    showAtLocation(anchorView, Gravity.TOP or Gravity.START, windowPos[0], windowPos[1])
}

/**
 * 权限相关
 */
fun Fragment.checkStoragePermissionBeforeAction(
    gameId: String = "",
    gameName: String = "",
    gameType: String = "",
    action: (() -> Unit)
) {
    PermissionHelper.checkStoragePermissionBeforeAction(requireContext(), gameId, gameName, gameType) {
        action.invoke()
    }
}

fun FragmentActivity.checkStoragePermissionBeforeAction(
    gameId: String = "",
    gameName: String = "",
    gameType: String = "",
    gameFormat: String? = "",
    action: (() -> Unit)
) {
    PermissionHelper.checkStoragePermissionBeforeAction(this, gameId, gameName, gameType, gameFormat) {
        action.invoke()
    }
}

/**
 * List related.
 */

// Returns the second element, or `null` if the list is empty.
fun <T> List<T>.secondOrNull(): T? {
    return if (isEmpty() || size == 1) null else this[1]
}

/**
 * TextView related.
 */
@JvmOverloads
fun TextView.setTextWithHighlightedTextWrappedInsideWrapper(
    text: CharSequence,
    wrapper: String = Constants.DEFAULT_TEXT_WRAPPER,
    @ColorRes
    highlightColorId: Int = R.color.text_theme,
    highlightedTextClickListener: SimpleCallback<String>? = null
) {
    TextHelper.highlightTextThatIsWrappedInsideWrapper(
        this,
        text,
        wrapper,
        highlightColorId,
        highlightedTextClickListener
    )
}

fun TextView.setTextChangedListener(action: (s: CharSequence, start: Int, before: Int, count: Int) -> Unit) {
    this.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {

        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            action.invoke(s ?: "", start, before, count)
        }
    })
}

/**
 * List related
 */
fun <T> List<T>.safelyGetInRelease(index: Int): T? {
    return if (index >= size) {
        throwExceptionInDebug("这里触发了数组越界，请检查 (index $index >= size $size)")
        toastInInternalRelease("这个操作可能触发闪退，请确定复现方式并联系开发处理")
        null
    } else {
        try {
            this[index]
        } catch (e: IndexOutOfBoundsException) {
            e.printStackTrace()
            null
        }
    }
}

/**
 * Array related
 */
fun <T> Array<out T>.secondOrNull(): T? {
    return if (isEmpty() || size < 2) null else this[1]
}

/**
 * ArrayList related
 */
fun <T> List<T>.toArrayList(): ArrayList<T> {
    return if (this is ArrayList) this else ArrayList(this)
}

/**
 * 拦截 TextView 中的 Url Span，用应用内页面的形式打开链接
 * @param shrankText 未展开时的文字
 * @param expandedText 展开后的文字
 */
fun ExpandTextView.setTextWithInterceptingInternalUrl(
    shrankText: CharSequence,
    expandedText: CharSequence,
    sourceEntrance: String = "",
    clickedCallback: (url: String) -> Unit,
) {
    var shrankSsb = shrankText.interceptUrlSpanAndRoundImageSpan(clickedCallback, sourceEntrance)
    var expandedSsb = expandedText.interceptUrlSpanAndRoundImageSpan(clickedCallback, sourceEntrance)

    // 去掉旧版本 Android 系统 [Html.FROM_HTML_MODE_LEGACY] 产生的两个换行符 (丑陋的代码)
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        while (shrankSsb.contains("\n\n")) {
            val index = shrankSsb.indexOf("\n\n", 0, true)
            shrankSsb = SpannableStringBuilder(
                shrankSsb.subSequence(
                    0,
                    index
                )
            ).append(shrankSsb.subSequence(index + "\n".length, shrankSsb.length))
        }
        while (expandedSsb.contains("\n\n")) {
            val index = expandedSsb.indexOf("\n\n", 0, true)
            expandedSsb = SpannableStringBuilder(
                expandedSsb.subSequence(
                    0,
                    index
                )
            ).append(expandedSsb.subSequence(index + "\n".length, expandedSsb.length))
        }
    }

    // 去掉多余的 P 标签换行
    if (expandedSsb.endsWith("\n", true)) {
        expandedSsb =
            SpannableStringBuilder((expandedSsb.subSequence(0, expandedSsb.length - "\n".length)))
    }

    movementMethod = CustomLinkMovementMethod.getInstance()

    shrankSsb = TextHelper.updateSpannableStringWithHighlightedSpan(
        context,
        shrankSsb,
        wrapper = Constants.DEFAULT_TEXT_WRAPPER_2,
        highlightedTextClickListener = TextHelper.CopyToClipboardHighlightedTextClick()
    )
    expandedSsb = TextHelper.updateSpannableStringWithHighlightedSpan(
        context,
        expandedSsb,
        wrapper = Constants.DEFAULT_TEXT_WRAPPER_2,
        highlightedTextClickListener = TextHelper.CopyToClipboardHighlightedTextClick()
    )
    setShrankTextAndExpandedText(shrankSsb, expandedSsb)
}

fun TextView.setTextWithInterceptingInternalUrl(text: CharSequence) {
    var ssb = text.interceptUrlSpanAndRoundImageSpan()
    // 去掉旧版本 Android 系统 [Html.FROM_HTML_MODE_LEGACY] 产生的两个换行符 (丑陋的代码)
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        while (ssb.contains("\n\n")) {
            val index = ssb.indexOf("\n\n", 0, true)
            ssb = SpannableStringBuilder(
                ssb.subSequence(
                    0,
                    index
                )
            ).append(ssb.subSequence(index + "\n".length, ssb.length))
        }
    }
    // 去掉多余的 P 标签换行
    if (ssb.endsWith("\n", true)) {
        ssb =
            SpannableStringBuilder((ssb.subSequence(0, ssb.length - "\n".length)))
    }

    movementMethod = CustomLinkMovementMethod.getInstance()

    ssb = TextHelper.updateSpannableStringWithHighlightedSpan(
        context,
        ssb,
        wrapper = Constants.DEFAULT_TEXT_WRAPPER_2,
        highlightedTextClickListener = TextHelper.CopyToClipboardHighlightedTextClick()
    )
    setText(ssb)
}

fun ExpandAndShrinkTextView.setTextWithInterceptingInternalUrl(text: CharSequence) {
    var ssb = text.interceptUrlSpanAndRoundImageSpan()
    // 去掉旧版本 Android 系统 [Html.FROM_HTML_MODE_LEGACY] 产生的两个换行符 (丑陋的代码)
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        while (ssb.contains("\n\n")) {
            val index = ssb.indexOf("\n\n", 0, true)
            ssb = SpannableStringBuilder(
                ssb.subSequence(
                    0,
                    index
                )
            ).append(ssb.subSequence(index + "\n".length, ssb.length))
        }
    }
    // 去掉多余的 P 标签换行
    if (ssb.endsWith("\n", true)) {
        ssb =
            SpannableStringBuilder((ssb.subSequence(0, ssb.length - "\n".length)))
    }

    movementMethod = CustomLinkMovementMethod.getInstance()

    ssb = TextHelper.updateSpannableStringWithHighlightedSpan(
        context,
        ssb,
        wrapper = Constants.DEFAULT_TEXT_WRAPPER_2,
        highlightedTextClickListener = TextHelper.CopyToClipboardHighlightedTextClick()
    )
    setOriginalText(ssb)
}

/**
 * 为 TextView 设置最大宽度，忽略入参为 0 的场景
 */
fun TextView.maxWidthExcludeZero(width: Int) {
    if (width != 0) {
        maxWidth = width
    }
}

fun CharSequence.interceptUrlSpanAndRoundImageSpan(
    clickedCallback: ((url: String) -> Unit)? = null,
    sourceEntrance: String = ""
): SpannableStringBuilder {
    return SpannableStringBuilder.valueOf(this).apply {
        getSpans(0, length, URLSpan::class.java).forEach {
            setSpan(
                object : ClickableSpan() {
                    override fun updateDrawState(ds: TextPaint) {
                        super.updateDrawState(ds)
                        ds.color = ContextCompat.getColor(
                            HaloApp.getInstance(),
                            R.color.text_theme
                        )
                        ds.isUnderlineText = true
                    }

                    override fun onClick(widget: View) {
                        clickedCallback?.invoke(it.url)
                        val webActivityProvider = TheRouter.get(IWebProvider::class.java)
                        val provider = TheRouter.get(IDefaultUrlHandlerProvider::class.java)
                        if (provider != null && !provider.interceptUrl(widget.context, it.url, "", sourceEntrance = sourceEntrance)) {
                            webActivityProvider?.getIntent(widget.context, it.url, true)?.run {
                                widget.context.startActivity(this)
                            }
                        }
                    }
                },
                getSpanStart(it),
                getSpanEnd(it),
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            removeSpan(it)
        }

        getSpans(0, length, ImageSpan::class.java).forEach {
            setSpan(
                CenterImageSpan(it.drawable),
                getSpanStart(it),
                getSpanEnd(it),
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            removeSpan(it)
        }
    }
}

fun Int.toColor(): Int {
    return ContextCompat.getColor(HaloApp.getInstance(), this)
}

/**
 * 只能传Activity的context，如果是Application的context会导致切换深色模式变不了色
 */
fun Int.toColor(context: Context): Int {
    return ContextCompat.getColor(context, this)
}

fun Int.toDrawable(): Drawable? {
    return ContextCompat.getDrawable(HaloApp.getInstance(), this)
}

/**
 * 只能传Activity的context，如果是Application的context会导致切换深色模式变不了色
 */
fun Int.toDrawable(context: Context): Drawable? {
    return ContextCompat.getDrawable(context, this)
}

fun Int.toResString(): String {
    return HaloApp.getInstance().resources.getString(this)
}

fun Int.toPx(): Int {
    return HaloApp.getInstance().resources.getDimension(this).toInt()
}

fun Int.toSimpleCount(): String {
    return NumberUtils.transSimpleCount(this)
}

fun Int.toNewSimpleCount(pattern: String = "#"): String {
    return NumberUtils.transNewSimpleCount(this, pattern)
}

/**
 * Long related
 */
fun Long.toProperReadableSize(): String {
    return Formatter.formatFileSize(HaloApp.getInstance(), this)
}

fun Long.formatTime(pattern: String = "yyyy-MM-dd"): String {
    return TimeUtils.getFormatTime(this, pattern)
}

/**
 * Image related
 */
fun SimpleDraweeView.display(url: String?, goneIfEmpty: Boolean = false) {
    if (goneIfEmpty && TextUtils.isEmpty(url)) {
        visibility = View.GONE
    } else {
        ImageUtils.display(this, url)
        visibility = View.VISIBLE
    }
}

/**
 * DownloadEntity extension
 */
fun DownloadEntity.addMetaExtra(key: String, value: String?) {
    value?.let {
        if (meta == null) {
            meta = hashMapOf()
        }
        meta[key] = value
    }
}

fun DownloadEntity.getMetaExtra(key: String): String {
    return meta?.get(key) ?: ""
}

fun DownloadEntity.isSilentUpdate(): Boolean {
    return Constants.SILENT_UPDATE == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}

fun DownloadEntity.isSimulatorDownload(): Boolean {
    return Constants.SIMULATOR_DOWNLOAD == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}

fun DownloadEntity.isSimulatorGame(): Boolean {
    return getMetaExtra(Constants.SIMULATOR_GAME).isNotEmpty()
}

fun DownloadEntity.asVGame(): Boolean {
    return Constants.VGAME == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
            || Constants.DUAL_DOWNLOAD_VGAME == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}

/**
 * 下载时触发的是双下载模式里的畅玩下载
 */
fun DownloadEntity.isVGameDownloadInDualDownloadMode(): Boolean {
    return Constants.DUAL_DOWNLOAD_VGAME == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}

/**
 * 下载时触发的是双下载模式里的普通下载
 */
fun DownloadEntity.isLocalDownloadInDualDownloadMode(): Boolean {
    return Constants.DUAL_DOWNLOAD_LOCAL == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}

/**
 * 设置触发时的下载模式为双下载模式里的普通下载
 */
fun DownloadEntity.setLocalDownloadModeInDualDownloadMode() {
    addMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE, Constants.DUAL_DOWNLOAD_LOCAL)
}

/**
 * 设置触发时的下载模式为双下载模式里的畅玩下载
 */
fun DownloadEntity.setVGameDownloadModeInDualDownloadMode() {
    addMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE, Constants.DUAL_DOWNLOAD_VGAME)
}

/**
 * 获取游戏类型 e.g.: local, online 等
 */
fun DownloadEntity.getGameCategory(): String {
    return getMetaExtra(Constants.GAME_CATEGORY)
}

val DownloadEntity.categoryChinese: String
    get() = when (getGameCategory()) {
        "online" -> "网络"
        "local" -> "单机"
        "welfare" -> "福利"
        "simulator" -> "模拟器"
        else -> getGameCategory()
    }

/**
 * 添加游戏类型 e.g.: 游戏类型
 */
fun DownloadEntity.putGameCategory(gameCategory: String) {
    addMetaExtra(Constants.GAME_CATEGORY, gameCategory)
}

inline fun doOnMainProcessOnly(f: () -> Unit) {
    val buildConfig = TheRouter.get(IBuildConfigProvider::class.java)
    val processName = ProcessUtil.getCurrentProcessName()
    var applicationId = buildConfig?.getApplicationId()

    // buildConfig 为空的兜底方案，至于你说为什么会为空，我也不知道
    if (applicationId.isNullOrEmpty()) {
        applicationId = HaloApp.getInstance().packageName ?: "com.gh.gamecenter"
    }

    if (processName == null || applicationId == processName) {
        f.invoke()
    } else {
        tryWithDefaultCatch {
            Utils.log("Block one useless sub process method call from ${Thread.currentThread().stackTrace[3].methodName} -> ${Thread.currentThread().stackTrace[2].methodName}.")
        }
    }
}

/**
 * 测试用包裹
 */
inline fun debugOnly(f: () -> Unit) {
    if (BuildConfig.DEBUG) {
        f()
    }
}

inline fun testChannelOnly(f: () -> Unit) {
    val appProvider = TheRouter.get(IAppProvider::class.java)
    if (appProvider?.getChannel() == Config.DEFAULT_CHANNEL) {
        f()
    }
}

/**
 * 倒计时，单位s
 */
inline fun countDownTimer(
    timeInSeconds: Long,
    crossinline block: (finish: Boolean, remainingTime: Long) -> Unit
): Disposable {
    var subscribe: Disposable? = null
    subscribe = Observable.interval(0, 1000, TimeUnit.MILLISECONDS)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe {
            if (it < timeInSeconds) {
                block.invoke(false, timeInSeconds - it)
            } else {
                block.invoke(true, 0)
                if (subscribe != null && !subscribe!!.isDisposed) {
                    subscribe?.dispose()
                }
            }
        }
    return subscribe
}

/**
 * 正计时
 * @start 起始时间
 */
inline fun countUpTimer(
    start: Long,
    period: Long = 1000,
    crossinline block: (millisUntilFinished: Long) -> Unit
): Disposable {
    var startTime = start
    return Observable.interval(0, period, TimeUnit.MILLISECONDS)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe {
            startTime += period
            block.invoke(startTime)
        }
}

/**
 * 计时器(注意不需要的时候要取消订阅)
 */
inline fun rxTimer(interval: Long, crossinline block: (times: Long) -> Unit): Disposable {
    return Observable.interval(0, interval, TimeUnit.MILLISECONDS)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe {
            block.invoke(it)
        }
}

inline fun rxTimerWithIoThread(interval: Long, crossinline block: (times: Long) -> Unit): Disposable {
    return Observable.interval(0, interval, TimeUnit.MILLISECONDS)
        .subscribe {
            block.invoke(it)
        }
}

fun LottieAnimationView.doOnAnimationEnd(action: () -> Unit) {
    this.addAnimatorListener(object : Animator.AnimatorListener {
        override fun onAnimationRepeat(animation: Animator) {

        }

        override fun onAnimationEnd(animation: Animator) {
            action.invoke()
        }

        override fun onAnimationCancel(animation: Animator) {

        }

        override fun onAnimationStart(animation: Animator) {

        }
    })
}

fun String?.getExtension(): String? {
    this ?: return null

    tryCatchInRelease {
        val lastDotIndex = this.lastIndexOf('.')
        return if (lastDotIndex == -1) null else this.substring(lastDotIndex + 1)
    }
    return null
}


/**
 * 检查内容是否一致
 * @return true:相同 false:不同
 */
fun List<String>?.checkSameFromStringArray(check2: List<String>?): Boolean {
    if (this == check2) {
        return true
    }
    if (this == null && check2 == null) {
        return true
    }
    if (this == null || check2 == null) {
        return false
    }
    if (this.size != check2.size) {
        return false
    }
    for (tag in this) {
        if (!check2.contains(tag)) return false
    }
    for (tag in check2) {
        if (!this.contains(tag)) return false
    }
    return true
}

/**
 * EditText弹出软键盘
 */
fun EditText.showKeyBoard() {
    this.postDelayed({
        this.requestFocus()
        val inputMethodManager =
            context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        inputMethodManager.showSoftInput(this, 0)
    }, 300)
}


fun SeekBar.doOnSeekBarChangeListener(
    progressChange: ((progress: Int) -> Unit)? = null,
    onStopTrackingTouch: (() -> Unit)? = null
) {
    this.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
            progressChange?.invoke(progress)
        }

        override fun onStartTrackingTouch(seekBar: SeekBar?) {

        }

        override fun onStopTrackingTouch(seekBar: SeekBar?) {
            onStopTrackingTouch?.invoke()
        }
    })
}

fun <T> observableToMain(): ObservableTransformer<T, T> {
    return ObservableTransformer { upstream ->
        upstream.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
    }
}

fun <T> singleToMain(): SingleTransformer<T, T> {
    return SingleTransformer { upstream ->
        upstream.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
    }
}

fun clickToastByStatus(status: String, action: () -> Unit) {
    when (status) {
        "pending" -> ToastUtils.showToast("内容审核中")
        "fail" -> ToastUtils.showToast("内容审核不通过")
        else -> action.invoke()
    }
}

fun View.getBitmapFromView(): Bitmap? {
    val bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    this.draw(canvas)
    return bitmap
}

fun View.getBitmapFromView(width: Int, height: Int, offsetX: Float, offsetY: Float): Bitmap? {
    val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    canvas.translate(offsetX, offsetY)
    this.draw(canvas)
    return bitmap
}

fun View.setRootBackgroundColor(@ColorRes res: Int) {
    if (this.id == View.NO_ID) {
        this.id = R.id.root_container
    }
    this.setBackgroundColor(res.toColor(this.context))
}

fun View.setRootBackgroundDrawable(@DrawableRes res: Int) {
    if (this.id == View.NO_ID) {
        this.id = R.id.root_container
    }
    this.background = res.toDrawable(this.context)
}

fun String.hexStringToIntColor(defaultColor: Int = Color.WHITE): Int {
    try {
        return when {
            isNullOrEmpty() -> defaultColor
            !isNullOrEmpty() && !startsWith("#") -> "#$this".hexStringToIntColor(defaultColor)
            else -> {
                if (this.length == 7 || this.length == 9) {
                    Color.parseColor(this)
                } else {
                    defaultColor
                }
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
        return defaultColor
    }
}

/**
 * 获取字符串长度，1个emoji表情算作1个字符
 */
fun String.lengthOfEmojiHalf(): Int {
    return this.length - this.emojiCount()
}

/**
 * 获取字符串中emoji表情数量
 */
fun String.emojiCount(): Int {
    val onlyText = EmojiUtils.removeEmoji(this)
    return (this.length - onlyText.length) / 2
}

/**
 * WebView启用强制深色模式
 */
@SuppressLint("RequiresFeature")
fun WebView.enableForceDark(isDarkModeOn: Boolean) {
    val isFeatureSupported = try {
        WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)
    } catch (throwable: Throwable) {
        throwable.printStackTrace()
        false
    }
    if (isFeatureSupported) {
        tryWithDefaultCatch {
            WebSettingsCompat.setForceDark(
                settings,
                if (isDarkModeOn) WebSettingsCompat.FORCE_DARK_ON else WebSettingsCompat.FORCE_DARK_OFF
            )
            DarkModeUtils.isWebViewForceDarkEnabled = isDarkModeOn
        }
    }
}

fun WebView.loadDataCompat(data: String, mimeType: String = "text/html", encoding: String = "utf-8") {
    try {
        this.loadDataWithBaseURL(
            null,
            data,
            mimeType,
            encoding,
            null
        )
    } catch (e: NullPointerException) {
        this.loadDataWithBaseURL(
            "localhost",
            data,
            "text/html",
            "utf-8",
            null
        )
    }
}

/**
 * WebView是否启用强制深色模式
 */
@SuppressLint("RequiresFeature")
fun WebView.isEnableForceDark(): Boolean {
    val isFeatureSupported = try {
        WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)
    } catch (throwable: Throwable) {
        throwable.printStackTrace()
        false
    }
    return if (isFeatureSupported) {
        WebSettingsCompat.getForceDark(settings) == WebSettingsCompat.FORCE_DARK_ON
    } else {
        false
    }
}

/**
 * WebView设置透明背景
 */
fun WebView.setTransparentBackground() {
    setBackgroundColor(0)
    background?.alpha = 0
}

/**
 * 深色模式切换更新状态栏底色
 */
fun Activity.updateStatusBarColor(
    @ColorRes nightColor: Int = R.color.ui_surface,
    @ColorRes dayColor: Int = R.color.ui_surface
) {
    DisplayUtils.setStatusBarColor(
        this,
        if (DarkModeUtils.isDarkModeOn(this)) nightColor else dayColor
    )
}

/**
 * 非 activity 创建 launcher
 */
fun <I, O> ComponentActivity.registerActivityResultLauncher(
    contract: ActivityResultContract<I, O>,
    callback: ActivityResultCallback<O>
): ActivityResultLauncher<I> {
    val key = UUID.randomUUID().toString()
    return activityResultRegistry.register(key, contract, callback)
}

/**
 * 设置开关Lottie动画
 */
fun LottieAnimationView.setSwitchAnimation(turnOff: Boolean) {
    cancelAnimation()
    when {
        turnOff && DarkModeUtils.isDarkModeOn(context) -> setAnimation("lottie/switch_turnoff_dark.json")
        !turnOff && DarkModeUtils.isDarkModeOn(context) -> setAnimation("lottie/switch_turnon_dark.json")
        turnOff && !DarkModeUtils.isDarkModeOn(context) -> setAnimation("lottie/switch_turnoff_light.json")
        !turnOff && !DarkModeUtils.isDarkModeOn(context) -> setAnimation("lottie/switch_turnon_light.json")
    }
    progress = 0F
}

/**
 * ImageView related
 */
fun ImageView.dimOnDarkMode() {
//    val isDarkModeON = DarkModeUtils.isSystemDarkModeOn(context)
//
//    if (isDarkModeON) {
//        setColorFilter(0x33000000, PorterDuff.Mode.SRC_OVER)
//    } else {
//        clearColorFilter()
//    }
}

/**
 * 修复WebView初次加载重置深色模式
 */
fun WebView.fixUiModeIfNeeded() {
    val topContext = if (context is Activity) context else CurrentActivityHolder.getCurrentActivity() ?: return

    val configuration = topContext.resources.configuration
    val configurationNightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
    val appCompatNightMode = AppCompatDelegate.getDefaultNightMode()

    val newUiModeConfiguration = when {
        configurationNightMode == Configuration.UI_MODE_NIGHT_NO && appCompatNightMode == AppCompatDelegate.MODE_NIGHT_YES -> {
            Configuration.UI_MODE_NIGHT_YES or (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
        }

        configurationNightMode == Configuration.UI_MODE_NIGHT_YES && appCompatNightMode == AppCompatDelegate.MODE_NIGHT_NO -> {
            Configuration.UI_MODE_NIGHT_NO or (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
        }

        else -> null
    }

    if (newUiModeConfiguration != null) {
        @Suppress("DEPRECATION")
        topContext.resources.updateConfiguration(
            Configuration().apply { uiMode = newUiModeConfiguration },
            topContext.resources.displayMetrics
        )
    }
}

/**
 * 把View转化成Bitmap
 */
fun View.convertViewToBitmap(): Bitmap? {
    measure(
        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
    )
    layout(0, 0, measuredWidth, measuredHeight)
    return if (measuredWidth > 0 && measuredHeight > 0) {
        val bitmap =
            Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        draw(canvas)
        canvas.save()
        bitmap
    } else null
}

fun TextView.removeDrawable() {
    this.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
}

fun TextView.setDrawableStart(@DrawableRes drawableRes: Int, width: Int? = null, height: Int? = null) {
    val drawable = drawableRes.toDrawable(this.context)
    this.setDrawableStart(drawable, width, height)
}

fun TextView.setDrawableStart(drawable: Drawable?, width: Int? = null, height: Int? = null) {
    drawable?.setBounds(0, 0, width ?: drawable.minimumWidth, height ?: drawable.minimumHeight)
    this.setCompoundDrawables(drawable, null, null, null)
}

fun TextView.setDrawableTop(@DrawableRes drawableRes: Int, width: Int? = null, height: Int? = null) {
    val drawable = drawableRes.toDrawable(this.context)
    this.setDrawableTop(drawable, width, height)
}

fun TextView.setDrawableTop(drawable: Drawable?, width: Int? = null, height: Int? = null) {
    drawable?.setBounds(0, 0, width ?: drawable.minimumWidth, height ?: drawable.minimumHeight)
    this.setCompoundDrawables(null, drawable, null, null)
}

fun TextView.setDrawableEnd(@DrawableRes drawableRes: Int, width: Int? = null, height: Int? = null) {
    val drawable = drawableRes.toDrawable(this.context)
    this.setDrawableEnd(drawable, width, height)
}

fun TextView.setDrawableEnd(drawable: Drawable?, width: Int? = null, height: Int? = null) {
    drawable?.setBounds(0, 0, width ?: drawable.minimumWidth, height ?: drawable.minimumHeight)
    this.setCompoundDrawables(null, null, drawable, null)
}

fun TextView.setDrawableBottom(@DrawableRes drawableRes: Int, width: Int? = null, height: Int? = null) {
    val drawable = drawableRes.toDrawable(this.context)
    this.setDrawableBottom(drawable, width, height)
}

fun TextView.setDrawableBottom(drawable: Drawable?, width: Int? = null, height: Int? = null) {
    drawable?.setBounds(0, 0, width ?: drawable.minimumWidth, height ?: drawable.minimumHeight)
    this.setCompoundDrawables(null, null, null, drawable)
}

/**
 * 设置下载提示Lottie动画
 */
fun LottieAnimationView.setDownloadTipsAnimation(isPlaying: Boolean = false) {
    runOnUiThread {
        cancelAnimation()
        setAnimation(if (DarkModeUtils.isDarkModeOn(context)) "lottie/downloadtips_dark.json" else "lottie/downloadtips_light.json")
        if (isPlaying) {
            // 动态
            playAnimation()
        } else {
            // 静态
            progress = 0.5F
        }
    }
}

/**
 * RGB颜色转HSV颜色
 */
fun List<Int>.colorRGBToHSV(): FloatArray {
    val colorHSV = FloatArray(3)
    if (size == 3) {
        Color.RGBToHSV(this[0], this[1], this[2], colorHSV)
    }
    return colorHSV
}

/**
 * 获取没有 host 的 uri String
 */
fun Uri.dropSchemeAndHost(): String {
    // Get the components of the URI
    val path = path
    val query = query
    val fragment = fragment

    // Build the string without the host
    return StringBuilder().apply {
        path?.let { append(it) }
        query?.let { append("?$it") }
        fragment?.let { append("#$it") }
    }.toString()
}

/**
 * 转HSV颜色
 */
fun Int.colorToHSV(): FloatArray {
    val colorHSV = FloatArray(3)
    Color.colorToHSV(this, colorHSV)
    return colorHSV
}

fun Activity.getUniqueId(): String {
    return System.identityHashCode(this).toString()
}

/**
 * 通过反射,获得定义Class时声明的父类的范型参数的类型.
 */
fun <T> Class<*>.getSuperClassGenericType(): Class<T> {
    return getSuperClassGenericType(0)
}

/**
 * 通过反射,获得定义Class时声明的父类的范型参数的类型.
 * @param index 获取第几个范型参数的类型
 */
@Suppress("UNCHECKED_CAST")
fun <T> Class<*>.getSuperClassGenericType(index: Int): Class<T> {
    var cls: Class<*>? = this
    var genType = cls?.genericSuperclass
    while (genType !is ParameterizedType) {
        cls = cls?.superclass
        requireNotNull(cls)
        genType = cls.genericSuperclass
    }
    val params = genType.actualTypeArguments
    require(!(index >= params.size || index < 0))
    return if (params[index] !is Class<*>) {
        throw IllegalArgumentException()
    } else params[index] as Class<T>
}


/**
 * 滚动到指定位置，并定位到顶部
 */
fun RecyclerView.scrollToPositionWithTop(position: Int) {
    val smoothScroller = object : LinearSmoothScroller(context) {
        override fun getVerticalSnapPreference(): Int {
            return SNAP_TO_START
        }

        override fun getHorizontalSnapPreference(): Int {
            return SNAP_TO_START
        }
    }
    smoothScroller.targetPosition = position
    layoutManager?.startSmoothScroll(smoothScroller)
}

/**
 * 跳转到指定位置，并触发滚动
 */
fun RecyclerView.jumpToPositionForSmoothScroller(position: Int) {
    javaClass.getDeclaredMethod("jumpToPositionForSmoothScroller", Int::class.java)
        .apply { isAccessible = true }
        .invoke(this, position)
}

fun RecyclerView.tryToClearRecycler() {
    try {
        // 移除 Recycler 已预生成，但未添加至 recyclerView 的 ViewHolder
        val mRecyclerField = RecyclerView::class.java.getDeclaredField("mRecycler")
        mRecyclerField.isAccessible = true
        val recycler = mRecyclerField.get(this) as Recycler
        recycler.clear()
    } catch (e: java.lang.Exception) {
        e.printStackTrace()
    }
}

fun ViewStub.inflateOrShow(): View? =
    if (parent != null) {
        inflate()
    } else {
        try {
            visibility = View.VISIBLE
        } catch (e: IllegalStateException) {
            e.printStackTrace()
        }
        null
    }

fun TextView.marqueeOnce() {
    isFocusable = true
    isFocusableInTouchMode = true
    setSingleLine()
    marqueeRepeatLimit = 1
    ellipsize = TextUtils.TruncateAt.MARQUEE
    isSelected = true
}

fun TextView.marqueeAgain() {
    if (!isMarqueeRunning()) {
        if (isSelected) isSelected = false
        isSelected = true
    }
}

fun TextView.isMarqueeRunning(): Boolean = try {
    val marqueeField = TextView::class.java.getDeclaredField("mMarquee")
    marqueeField.isAccessible = true
    val marquee = marqueeField.get(this)

    if (marquee != null) {
        val marqueeClass = marquee.javaClass
        val isRunningMethod = marqueeClass.getDeclaredMethod("isRunning")
        isRunningMethod.isAccessible = true
        isRunningMethod.invoke(marquee) as Boolean
    } else {
        false
    }
} catch (e: Exception) {
    e.printStackTrace()
    false
}

fun SimpleDraweeView.updateRoundingParams(block: RoundingParams.() -> Unit) {
    val roundingParams = hierarchy.roundingParams
    if (roundingParams != null) {
        block(roundingParams)
    }
    hierarchy.roundingParams = roundingParams
}

// 设置默认 position 避免 position 为 0 的 fragment 被先加载显示再跳转至具体页面
fun ViewPager.setRestoredCurItem(defaultPosition: Int) {
    tryCatchInRelease {
        val field = ViewPager::class.java.getDeclaredField("mRestoredCurItem")
        field.isAccessible = true
        field.set(this, defaultPosition)
    }
}

// RGBA转换为ARGB格式颜色字符串
fun String.RGBAToARGB(): String = if (length == 9) {
    substring(0, 1) + substring(7, 9) + substring(1, 7)
} else this

// 换行符替换为Html的<br>换行
fun String.replaceLineBreakWithBr(): String = replace("\n", "<br>")