package io.nexilis.service.core

import android.content.Context
import android.content.SharedPreferences
import androidx.compose.ui.graphics.Color
import io.nexilis.service.namePreference
import java.io.File
import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.security.SecureRandom
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

fun String.decrypt(): String {
    return Secret().decrypt(this)
}

fun String.encrypt(): String {
    return Secret().encrypt(this)
}

fun SharedPreferences.optString(key: String, default: String): String {
    return this.getString(key, default) ?: default
}

fun SharedPreferences.put(key: String, value: String) {
    with(this.edit()) {
        putString(key, value)
        apply()
    }
}

fun Context.createImageFile(): File {
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
    val imageFileName = "IMG_" + timeStamp + "_"
    return File.createTempFile(
        imageFileName,
        ".webp",
        externalCacheDir
    )
}

fun String.toMD5(): String {
    val md = MessageDigest.getInstance("MD5")
    val hashInBytes = md.digest(this.toByteArray(StandardCharsets.UTF_8))

    val sb = StringBuilder()
    for (b in hashInBytes) {
        sb.append(String.format("%02x", b))
    }
    return sb.toString()
}

fun String.toNormalString(): String {
    return URLDecoder.decode(this, "UTF-8").replace("<NL>", "\n").replace("<CR>", "\r")
}

fun String.toStupidString(): String {
    return URLEncoder.encode(this, "UTF-8").replace("\n", "<NL>").replace("\n", "<CR>")
}

fun Context.getSharedPreferences(): SharedPreferences {
    return getSharedPreferences(
        namePreference,
        Context.MODE_PRIVATE
    )
}

val Color.Companion.Purple: Color
    get() = Color(0xFF800080)
val Color.Companion.Orange: Color
    get() = Color(0xFFFFA500)
val Color.Companion.DarkLimeGreen: Color
    get() = Color(0xFF007600)
val Color.Companion.BlueSky: Color
    get() = Color(0xFF0075F1)
val Color.Companion.WhiteTransparent: Color
    get() = Color(0xEFFFFFFF)

class Secret {
    private val iBB = 48 // 0

    private val iBBT1 = 57 // 9

    private val iBAT1 = 65 // A

    private val iBBT2 = 90 // Z

    private val iBAT2 = 97 // a

    private val iBA = 122 // z


    private val icBB = 33 // !

    private val icBBT1 = 47 // /

    private val icBAT1 = 58 // :

    private val icBBT2 = 64 // @

    private val icBAT2 = 91 // [

    private val icBBT3 = 96 // @

    private val icBAT3 = 123 // [

    private val icBA = 126 // `

    private val icIGNORE: HashSet<Int> = object : HashSet<Int>() {
        init {
            add(10) // \r
            add(13) // \n
            add(32) // <space>
        }
    }

    /**
     * Get random number with specific ranges
     *
     * @return a number
     */
    private fun getRandomNumber(): Int {
        val gen = SecureRandom()
        return gen.nextInt(9 + 1 - 1) + 1
    }

    private fun isSpecialChar(ch: Char): Boolean {
        return ch.code >= icBB && ch.code <= icBBT1 || ch.code >= icBAT1 && ch.code <= icBBT2 || ch.code >= icBAT2 && ch.code <= icBBT3 || ch.code >= icBAT3 && ch.code <= icBA
    }

    /**
     * Encrypts a string with random number index as token at first char
     *
     * @param string String to encrypt
     * @return encrypted string
     */
    fun encrypt(string: String): String {
        var str = string
        val arr: CharArray = str.toCharArray()
        val iRandom = getRandomNumber()
        for (i in arr.indices) {
            try {
                if (isSpecialChar(arr[i])) {
                    arr[i] = getNextcChar(arr[i], iRandom)
                } else {
                    arr[i] = getNextChar(arr[i], iRandom)
                }
            } catch (exception: Exception) {
                exception.printStackTrace()
                return "-1"
            }
        }
        str = String(arr)
        str = iRandom.toString() + getPalindrom(str)
        return str
    }

    /**
     * Decrypts a string
     *
     * @param str String to decrypt
     * @return decrypted string
     */
    fun decrypt(str: String): String {
        val arr: CharArray
        var iRandom = 0
        val sDecrypt: String
        try {
            iRandom = str.substring(0, 1).toInt()
        } catch (ignored: NumberFormatException) {
        }
        sDecrypt = getPalindrom(str.substring(1))
        arr = sDecrypt.toCharArray()
        for (i in arr.indices) {
            try {
                if (isSpecialChar(arr[i])) {
                    arr[i] = getBeforecChar(arr[i], iRandom)
                } else {
                    arr[i] = getBeforeChar(arr[i], iRandom)
                }
            } catch (exception: Exception) {
                return "-1"
            }
        }
        return String(arr)
    }

    /**
     * Shift up a char with given number
     *
     * @param ch  Given char to shift
     * @param inc Number to be shift
     * @return shifted char
     * @throws Exception
     */
    @Throws(Exception::class)
    private fun getNextChar(ch: Char, inc: Int): Char {
        var iAscii = ch.code
        val iAsciiBefore = iAscii
        if (icIGNORE.contains(iAscii)) {
            return iAscii.toChar()
        }
        if (iAscii > iBA || iAscii < iBB) {
            throw Exception("Unsupported Character")
        } else {
            iAscii = iAscii + inc
            //jika diantara karakter ignore 1
            if (iBBT1 < iAscii && iAsciiBefore <= iBBT1) {
                iAscii = iBAT1 - 1 + (iAscii - iBBT1)
            }
            //jika diantara karakter ignore 2
            if (iBBT2 < iAscii && iAsciiBefore <= iBBT2) {
                iAscii = iBAT2 - 1 + (iAscii - iBBT2)
            }
            //jika lebih dari batas atas
            if (iAscii > iBA) {
                iAscii = iBB - 1 + (iAscii - iBA)
            }
        }
        return iAscii.toChar()
    }

    /**
     * Shift up a char with given number
     *
     * @param ch  Given char to shift
     * @param inc Number to be shift
     * @return shifted char
     * @throws Exception
     */
    @Throws(Exception::class)
    private fun getNextcChar(ch: Char, inc: Int): Char {
        var iAscii = ch.code
        val iAsciiBefore = iAscii
        if (iAscii > icBA || iAscii < icBB) {
            throw Exception("Unsupported Character")
        } else {
            iAscii = iAscii + inc
            //jika diantara karakter ignore 1
            if (icBBT1 < iAscii && iAsciiBefore <= icBBT1) {
                iAscii = icBAT1 - 1 + (iAscii - icBBT1)
            }
            //jika diantara karakter ignore 2
            if (icBBT2 < iAscii && iAsciiBefore <= icBBT2) {
                iAscii = icBAT2 - 1 + (iAscii - icBBT2)
            }
            //jika diantara karakter ignore 2
            if (icBBT3 < iAscii && iAsciiBefore <= icBBT3) {
                iAscii = icBAT3 - 1 + (iAscii - icBBT3)
            }
            //jika lebih dari batas atas
            if (iAscii > icBA) {
                iAscii = icBB - 1 + (iAscii - icBA)
            }
        }
        return iAscii.toChar()
    }

    /**
     * Shift down a char with given number
     *
     * @param ch  Given char to shift
     * @param inc Number to be shift
     * @return shifted char
     * @throws Exception
     */
    @Throws(Exception::class)
    private fun getBeforeChar(ch: Char, inc: Int): Char {
        var iAscii = ch.code
        val iAsciiBefore = iAscii
        if (icIGNORE.contains(iAscii)) {
            return iAscii.toChar()
        }
        if (iAscii > iBA || iAscii < iBB) {
            throw Exception()
        } else {
            if (!icIGNORE.contains(iAscii)) {
                iAscii = iAscii - inc
                //jika diantara karakter ignore 1
                if (iBAT1 > iAscii && iAsciiBefore >= iBAT1) {
                    iAscii = iBBT1 + 1 - (iBAT1 - iAscii)
                }
                //jika diantara karakter ignore 2
                if (iBAT2 > iAscii && iAsciiBefore >= iBAT2) {
                    iAscii = iBBT2 + 1 - (iBAT2 - iAscii)
                }
                if (iAscii < iBB) {
                    iAscii = iBA + 1 + (iAscii - iBB)
                }
            }
        }
        return iAscii.toChar()
    }

    /**
     * Shift down a char with given number
     *
     * @param ch  Given char to shift
     * @param inc Number to be shift
     * @return shifted char
     * @throws Exception
     */
    @Throws(Exception::class)
    private fun getBeforecChar(ch: Char, inc: Int): Char {
        var iAscii = ch.code
        val iAsciiBefore = iAscii
        if (iAscii > icBA || iAscii < icBB) {
            throw Exception()
        } else {
            iAscii = iAscii - inc
            if (iAscii < icBB) {
                iAscii = icBA + 1 + (iAscii - icBB)
                //jika diantara karakter ignore 3
                if (iAscii < icBAT3 && iAscii > icBBT3) {
                    iAscii = icBBT3 + 1 - (icBAT3 - iAscii)
                }
            }

            //jika diantara karakter ignore 3
            if (icBAT3 > iAscii && iAsciiBefore >= icBAT3) {
                iAscii = icBBT3 + 1 - (icBAT3 - iAscii)
                //jika diantara karakter ignore 2
//                if (icBAT2 > iAscii && iAsciiBefore >= icBAT2) {
//                    iAscii = (icBBT2 + 1) - (icBAT2 - iAscii);
//                }
            }

            //jika diantara karakter ignore 2
            if (icBAT2 > iAscii && iAsciiBefore >= icBAT2) {
                iAscii = icBBT2 + 1 - (icBAT2 - iAscii)
            }
            //jika diantara karakter ignore 1
            if (icBAT1 > iAscii && iAsciiBefore >= icBAT1) {
                iAscii = icBBT1 + 1 - (icBAT1 - iAscii)
            }
        }
        return iAscii.toChar()
    }

    /**
     * Reverses order of a string
     *
     * @param str
     * @return reversed order of string
     */
    private fun getPalindrom(str: String): String {
        val arr = str.toCharArray()
        val arr2 = arr.clone()
        for (i in arr.indices) {
            arr2[i] = arr[arr.size - (i + 1)]
        }
        return String(arr2)
    }
}