package com.kdanmobile.kdan_others_library_for_android.utils

import android.graphics.Color
import android.graphics.Typeface
import android.text.SpannableString
import android.text.Spanned
import android.text.TextPaint
import android.text.style.*
import android.view.View
import java.lang.Exception
import java.util.*
import java.util.regex.Pattern

class SpannableStringHelper {
    companion object {
        /**
         * 文字風格，將想使用的風格以逗點隔開設置，例如：
         * 粗體+斜體+紅色背景：b,i,bg#FFFF0000
         * 刪除+底線+藍色文字：s,u,#FF0000FF
         * */
        const val STYLE_FULL_UNDERLINE = "underline"
        const val STYLE_FULL_BOLD = "bold"
        const val STYLE_FULL_ITALIC = "italic"
        const val STYLE_FULL_STRIKE = "strike"
        const val STYLE_SHORT_UNDERLINE = "u"
        const val STYLE_SHORT_BOLD = "b"
        const val STYLE_SHORT_ITALIC = "i"
        const val STYLE_SHORT_STRIKE = "s"
        const val STYLE_TEXT_COLOR = "#"
        const val STYLE_BACKGROUND_COLOR = "bg#"
        /**
         * 使用createSpannableString時，預設的文字顏色和背景顏色
         * */
        const val DEFAULT_TEXT_COLOR = 0xFF0077FD.toInt()
        const val DEFAULT_BACKGROUND_COLOR = 0x00FFFFFF
        /**
         * 快捷設置，可以預先定義多組常用文字風格
         * （保險起見，別使用任何特殊字元）
         * */
        private val QUICK_SETTING = TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER).also {
            it["Term of Use"] = "#FFFF00FF,b"
        }

        /**
         * 控制碼定義
         * [CTRL-START 文字風格:標籤]顯示文字[CTRL-END]
         *      例如：[CTRL-START b,i:tag1]有風格定義+事件標籤的文字[CTRL-END]
         *
         * [CTRL-START <快捷設置>:標籤]顯示文字[CTRL-END]
         *      例如：[CTRL-START <Term of Use>:tag2]採用快捷設置+事件標籤的文字[CTRL-END]
         *
         * [CTRL-START 文字風格]顯示文字[CTRL-END]
         *      例如：[CTRL-START b,i]僅有風格定義的文字[CTRL-END]
         *
         * [CTRL-START <快捷設置>]顯示文字[CTRL-END]
         *      例如：[CTRL-START <Term of Use>]僅採用快捷設置的文字[CTRL-END]
         *
         * [CTRL-START :標籤][顯示文字][CTRL-END]
         *      例如：[CTRL-START :tag3]僅有事件標籤的文字，注意標籤前須加上「:」[CTRL-END]
         * */
        private const val CTRL_CODE_BEGIN_LEFT = "\\[CTRL-START "
        private const val CTRL_CODE_BEGIN_RIGHT = "]"
        private const val CTRL_CODE_END = "\\[CTRL-END]"
        private const val CTRL_QUICK_START = "<"
        private const val CTRL_QUICK_END = ">"
        private const val CTRL_TAG_MARK = ":"
    }

    //  依照輸入文字的控制碼來套用文字風格，並設定文字的點擊事件
    @JvmOverloads
    fun createSpannableString(text: String, actionMap: TreeMap<String, () -> Unit>? = null, ignoreOtherTags: Boolean = false): SpannableString {
        val spannableInfo = extractSpannableInfo(text, actionMap?.keys?.toList(), ignoreOtherTags)
        val spannableString = SpannableString(spannableInfo.displayText)
        for (item in spannableInfo.tagMap) {
            for (spannableStyle in item.value) {
                spannableStyle.action = actionMap?.get(item.key)
                applySpanStyle(spannableString, spannableStyle)
            }
        }
        return spannableString
    }

    //  擷取文字範圍、風格與標籤等資訊
    @JvmOverloads
    fun extractSpannableInfo(text: String, tags: List<String>? = null, ignoreOtherTags: Boolean = false): SpannableInfo {
        val spannableInfo = SpannableInfo()
        tags?.also {
            for (tag in it) {
                spannableInfo.tagMap[tag] = arrayListOf()
            }
        }
        val displayText = StringBuffer(text)
        var currentIndex = 0

        val patternFull = Pattern.compile("$CTRL_CODE_BEGIN_LEFT.*?$CTRL_CODE_BEGIN_RIGHT.*?$CTRL_CODE_END")
        val patternCtrl = Pattern.compile("$CTRL_CODE_BEGIN_LEFT.*?$CTRL_CODE_BEGIN_RIGHT")
        val patternEnd = Pattern.compile(CTRL_CODE_END)
        while (true) {
            val matcher = patternFull.matcher(displayText.toString())
            if (!matcher.find(currentIndex)) break

            val result = matcher.toMatchResult()
            val startIndex = result.start()
            val endIndex = result.end()
            val group = result.group()

            val matcherCtrl = patternCtrl.matcher(group)
            if (!matcherCtrl.find()) break
            val matcherEnd = patternEnd.matcher(group)
            if (!matcherEnd.find()) break

            val ctrlBeginText = matcherCtrl.toMatchResult().group()
            val fullParameter = ctrlBeginText.substring(CTRL_CODE_BEGIN_LEFT.length - 1, ctrlBeginText.length - CTRL_CODE_BEGIN_RIGHT.length).trim()
            val eventTagParameter = getEventTagParameter(fullParameter)
            val skip = ignoreOtherTags && tags?.contains(eventTagParameter) == false

            val textStyleParameter = getTextStyleParameter(fullParameter)

            if (skip) {
                currentIndex = endIndex
            } else {
                val targetBeginIndex = startIndex + matcherCtrl.toMatchResult().end()
                val targetEndIndex = startIndex + matcherEnd.toMatchResult().start()
                val targetTextLength = startIndex + (targetEndIndex - targetBeginIndex)
                val target = displayText.substring(targetBeginIndex, targetEndIndex)
                displayText.delete(targetEndIndex, endIndex)
                displayText.delete(startIndex, targetBeginIndex)

                if (spannableInfo.tagMap[eventTagParameter] == null) {
                    spannableInfo.tagMap[eventTagParameter] = arrayListOf()
                }
                val spannableStyle = SpannableStyle(
                    target,
                    startIndex,
                    targetTextLength
                )
                setDisplayStyle(spannableStyle, textStyleParameter)
                spannableInfo.tagMap[eventTagParameter]?.add(spannableStyle)
            }
        }
        spannableInfo.displayText = displayText.toString()
        return spannableInfo
    }

    //  取得事件標籤參數字串
    private fun getEventTagParameter(fullParameter: String): String {
        val tagIndex = fullParameter.indexOf(CTRL_TAG_MARK)
        return if (tagIndex != -1) fullParameter.substring(fullParameter.lastIndexOf(CTRL_TAG_MARK) + CTRL_TAG_MARK.length).trim() else ""
    }

    //  取得文字風格參數字串
    private fun getTextStyleParameter(fullParameter: String): String {
        val tagIndex = fullParameter.indexOf(CTRL_TAG_MARK)
        var styleParameter = if (tagIndex != -1) {
            fullParameter.substring(0, tagIndex).trim()
        } else {
            fullParameter.trim()
        }

        if (styleParameter.startsWith(CTRL_QUICK_START) && styleParameter.endsWith(CTRL_QUICK_END)) {
            val key = styleParameter.substring(CTRL_QUICK_START.length, styleParameter.length - CTRL_QUICK_END.length)
            QUICK_SETTING[key]?.also {
                styleParameter = it
            }
        }
        return styleParameter
    }

    //  依照輸入字串設定文字風格
    private fun setDisplayStyle(spannableStyle: SpannableStyle, styleParameter: String) {
        val tokens = styleParameter.split(",")
        for (token in tokens) {
            token.trim().also {
                when (it) {
                    STYLE_FULL_UNDERLINE, STYLE_SHORT_UNDERLINE -> spannableStyle.isUnderline = true
                    STYLE_FULL_BOLD, STYLE_SHORT_BOLD -> spannableStyle.isBold = true
                    STYLE_FULL_ITALIC, STYLE_SHORT_ITALIC -> spannableStyle.isItalic = true
                    STYLE_FULL_STRIKE, STYLE_SHORT_STRIKE -> spannableStyle.isStrikeThrough = true
                }
                if (it.startsWith(STYLE_TEXT_COLOR)) {
                    spannableStyle.textColor = try {
                        Color.parseColor("#" + it.substring(STYLE_TEXT_COLOR.length))
                    } catch (e: Exception) {
                        DEFAULT_TEXT_COLOR
                    }
                } else if (it.startsWith(STYLE_BACKGROUND_COLOR)) {
                    spannableStyle.backgroundColor = try {
                        Color.parseColor("#" + it.substring(STYLE_BACKGROUND_COLOR.length))
                    } catch (e: Exception) {
                        DEFAULT_BACKGROUND_COLOR
                    }
                }
            }
        }
    }

    //  套用文字風格
    private fun applySpanStyle(spannableString: SpannableString, spannableStyle: SpannableStyle) {
        spannableStyle.run {
            if (isStrikeThrough) {
                spannableString.setSpan(StrikethroughSpan(), startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
            }

            if (isBold) {
                if (isItalic) {
                    spannableString.setSpan(StyleSpan(Typeface.BOLD_ITALIC), startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
                } else {
                    spannableString.setSpan(StyleSpan(Typeface.BOLD), startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
                }
            } else if (isItalic) {
                spannableString.setSpan(StyleSpan(Typeface.ITALIC), startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
            }

            if (isStrikeThrough) {
                spannableString.setSpan(StrikethroughSpan(), startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
            }

            val clickableSpan = object : ClickableSpan() {
                override fun onClick(textView: View) {
                    spannableStyle.action?.invoke()
                }

                override fun updateDrawState(ds: TextPaint) {
                    super.updateDrawState(ds)
                    ds.isUnderlineText = isUnderline
                    ds.color = textColor
                    ds.bgColor = backgroundColor
                }
            }
            spannableString.setSpan(clickableSpan, startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        }
    }

    data class SpannableStyle(
        //  文字與範圍
        var text: String,
        var startIndex: Int,
        var endIndex: Int,
        //  文字風格
        var isUnderline: Boolean = false,
        var isBold: Boolean = false,
        var isItalic: Boolean = false,
        var isStrikeThrough: Boolean = false,
        var textColor: Int = DEFAULT_TEXT_COLOR,
        var backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
        //  點擊事件
        var action: (() -> Unit)? = null
    )

    data class SpannableInfo(
        var displayText: String = "",
        val tagMap: TreeMap<String, ArrayList<SpannableStyle>> = TreeMap()
    )
}