package com.example.modifier.utils

import android.util.Log
import java.math.BigDecimal
import java.math.RoundingMode

object SimEncoder {
    fun String.stripF(): String {
        return this.replace(Regex("^[fF]+"), "").replace(Regex("[fF]+$"), "")
    }

    fun String.swapNibbles(): String {
        val sb = StringBuilder()
        var i = 0
        while (i < length) {
            sb.append(this[i + 1]).append(this[i])
            i += 2
        }
        return sb.toString()
    }

    fun asciiToHex(ascii: String): String {
        return ascii.toByteArray().joinToString("") { "%02x".format(it) }
    }

    fun decIMSI(ef: String): String? {
        if (ef.length < 4) {
            return null
        }
        var l = ef.substring(0, 2).toInt(16) * 2
        l -= 1
        val swapped: String = ef.substring(2).swapNibbles().stripF()
        if (swapped.isEmpty()) {
            return null
        }
        val oe = (swapped[0].toString().toInt() shr 3) and 1
        if (oe == 0) {
            l -= 1
        }
        if (l != swapped.length - 1) {
            return null
        }
        val imsi = swapped.substring(1)
        return imsi
    }

    fun encIMSI(imsi: String): String {
        val l = BigDecimal(((imsi.length + 1) / 2f).toDouble()).setScale(0, RoundingMode.HALF_UP)
            .toInt()
        val oe = imsi.length and 1
        val ei =
            String.format("%02x", l) +
                    (String.format("%01x", (oe shl 3) or 1) + imsi.padEnd(
                        15,
                        'f'
                    )).swapNibbles()
        return ei
    }

    fun decICCID(ef: String): String {
        return ef.swapNibbles().stripF()
    }

    fun encICCID(iccid: String): String {
        return iccid.padEnd(20, 'f').swapNibbles()
    }

    /*
    * NPI (Numbering Plan Identification): This indicates the numbering plan used for the phone number. Common values include:
        0x01: ISDN/telephony numbering plan (E.164/E.163)
        0x02: Data numbering plan (X.121)
        0x03: Telex numbering plan
      ToN (Type of Number): This indicates the type of the phone number. Common values include:
        0x00: Unknown
        0x01: International number
        0x02: National number
        0x03: Network-specific number
        0x04: Subscriber number
        0x05: Alphanumeric (coded according to the GSM 7-bit default alphabet)
        0x06: Abbreviated number
    * */
    fun decMSISDN(efMsisdn: String): String? {
        println("ef_msisdn: $efMsisdn")
        // Convert from String to ByteArray
        val efMsisdnBytes = efMsisdn.chunked(2).map { it.toInt(16).toByte() }.toByteArray()

        // Make sure mandatory fields are present
        if (efMsisdnBytes.size < 14) {
            Log.e("DecMSISDN", "EF.MSISDN is too short")
            return null
        }

        // Skip optional Alpha Identifier
        val xlen = efMsisdnBytes.size - 14
        val msisdnLhv = efMsisdnBytes.copyOfRange(xlen, efMsisdnBytes.size)

        // Parse the length (in bytes) of the BCD encoded number
        var bcdLen = msisdnLhv[0].toInt()
        // BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
        if (bcdLen == 0xff) {
            return null
        } else if (bcdLen > 11 || bcdLen < 1) {
            Log.e("DecMSISDN", "Length of MSISDN ($bcdLen bytes) is out of range")
            return null
        }

        // Parse ToN / NPI
        val ton = (msisdnLhv[1].toInt() shr 4) and 0x07
        val npi = msisdnLhv[1].toInt() and 0x0f
        bcdLen -= 1

        // No MSISDN?
        if (bcdLen == 0) {
            return null
        }

        val msisdn =
            msisdnLhv.copyOfRange(2, 2 + bcdLen).joinToString("") { "%02x".format(it) }
                .swapNibbles()
                .stripF()
        // International number 10.5.118/3GPP TS 24.008
        val formattedMsisdn = if (ton == 0x01) "+$msisdn" else msisdn
        Log.i("DecMSISDN", "EF: $efMsisdn, MSISDN: $formattedMsisdn, NPI: $npi, ToN: $ton")
        return formattedMsisdn
    }

    fun encMSISDN(msisdn: String, npi: Int = 0x01, ton: Int = 0x03): String {
        // If no MSISDN is supplied then encode the file contents as all "ff"
        if (msisdn.isEmpty() || msisdn == "+") {
            return "ff".repeat(14)
        }

        var msisdnVar = msisdn
        var tonVar = ton

        // Leading '+' indicates International Number
        if (msisdnVar[0] == '+') {
            msisdnVar = msisdnVar.substring(1)
            tonVar = 0x01
        }

        // An MSISDN must not exceed 20 digits
        if (msisdnVar.length > 20) {
            throw IllegalArgumentException("msisdn must not be longer than 20 digits")
        }

        // Append 'f' padding if number of digits is odd
        if (msisdnVar.length % 2 > 0) {
            msisdnVar += 'f'
        }

        // BCD length also includes NPI/ToN header
        val bcdLen = msisdnVar.length / 2 + 1
        val npiTon = (npi and 0x0f) or ((tonVar and 0x07) shl 4) or 0x80
        val bcd = msisdnVar.swapNibbles().padEnd(10 * 2, 'f')  // pad to 10 octets

        return String.format("%02x", bcdLen) + String.format("%02x", npiTon) + bcd + "ff".repeat(2)
    }

    fun decPLMN(efPlmn: String): String? {
        if (efPlmn.length < 6) {
            return null
        }
        return efPlmn.stripF().chunked(6).joinToString(",") {
            val mcc = decMcc(it)
            val mnc = decMnc(it)
            "$mcc$mnc"
        }
    }

    fun decPLMNwAcT(efPlmn: String): String? {
        if (efPlmn.length < 10) {
            return null
        }
        return efPlmn.chunked(10)
            .filter { !it.substring(0, 6).matches(Regex("^f{3,}", RegexOption.IGNORE_CASE)) }
            .joinToString(",") {
                val mcc = decMcc(it)
                val mnc = decMnc(it)
                val act = it.substring(6)
                "$mcc$mnc:$act"
            }
    }

    fun encPLMN(plmn: String): String {
        return plmn.split(",", "，").map { it.trim() }
            .filter { it.matches(Regex("\\d{5,6}")) }
            .joinToString("") { encMccMnc(it) }
    }

    fun encPLMNwAcT(plmn: String): String {
        return plmn.split(",", "，").map { it.trim() }
            .filter { it.matches(Regex("\\d{5,6}:\\d{4}")) }
            .joinToString("") {
                it.split(":")
                    .let { (plmn, act) ->
                        "${encMccMnc(plmn)}${act}"
                    }
            }
    }

    fun decMcc(plmn: String): String {
        val digit1 = plmn[1]  // 1st byte, LSB
        val digit2 = plmn[0]  // 1st byte, MSB
        val digit3 = plmn[3]  // 2nd byte, LSB
        val res = "$digit1$digit2$digit3"
        return res.stripF()
    }

    fun decMnc(plmn: String): String {
        val digit1 = plmn[5]  // 3rd byte, LSB
        val digit2 = plmn[4]  // 3rd byte, MSB
        val digit3 = plmn[2]  // 2nd byte, MSB
        val res = "$digit1$digit2$digit3"
        return res.stripF()
    }

    fun encMccMnc(mccmnc: String): String {
        if (!mccmnc.matches(Regex("^\\d{5,6}$"))) {
            throw IllegalArgumentException("mccmnc must be 5 or 6 digits")
        }
        // Make sure there are no excess whitespaces in the input parameters
        var mccVar = mccmnc.substring(0, 3)
        var mncVar = mccmnc.substring(3)

        // Make sure that MCC/MNC are correctly padded with leading zeros or 'F', depending on the length
        mncVar = when (mncVar.length) {
            0 -> "FFF"
            1 -> "0$mncVar" + "F"
            2 -> mncVar + "F"
            else -> mncVar
        }

        mccVar = when (mccVar.length) {
            0 -> "FFF"
            1 -> "00$mccVar"
            2 -> "0$mccVar"
            else -> mccVar
        }

        return "${mccVar[1]}${mccVar[0]}${mncVar[2]}${mccVar[2]}${mncVar[1]}${mncVar[0]}"
    }
}

