package com.example.modifier.utils

import android.telephony.IccOpenLogicalChannelResponse
import android.telephony.TelephonyManager
import android.util.Log
import com.example.modifier.constants.SIMView


class ApduChannel(private val telephonyManager: TelephonyManager, aid: String) {
    companion object {
        const val TAG = "APDU"
        const val INS_SEL = 0xA4
        const val INS_RB = 0xB0
        const val INS_WB = 0xD6
        const val INS_RR = 0xB2
        const val INS_WR = 0xDC
    }

    private val channelId: Int
    private var closed: Boolean = false

    class ApduResponse(rawStr: String) {
        val data: String = rawStr.substring(0, rawStr.length - 4)
        val sw: String = rawStr.substring(rawStr.length - 4)
        val sw1: String = sw.substring(0, 2)
        val sw2: String = sw.substring(2)
    }

    init {
        val res = telephonyManager.iccOpenLogicalChannel(aid, 0x04)
        if (res.status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
            throw Exception("Failed to open logical channel")
        }
        channelId = res.channel
    }

    fun execute(cla: Int, ins: Int, p1: Int, p2: Int, lc: Int, data: String): ApduResponse {
        if (closed) {
            throw Exception("Channel closed")
        }

        Log.d(TAG, ">>> ${String.format("%02X%02X%02X%02X%02X %s", cla, ins, p1, p2, lc, data)}")
        val res = telephonyManager.iccTransmitApduLogicalChannel(
            channelId, cla, ins, p1, p2, lc, data
        )
        Log.d(TAG, "<<< ${String.format("%S", res)}")
        return ApduResponse(res)
    }

    fun close() {
        if (!closed) {
            telephonyManager.iccCloseLogicalChannel(channelId)
        }
        closed = true
    }

    fun readICCID(): String {
        val res = execute(0x00, 0xE1, 0xA0, 0x00, 0x00, "")
        if (res.sw != "9000") {
            throw Exception("Failed to read ICCID")
        }
        return SimEncoder.decICCID(res.data)
    }

    fun writeICCID(iccid: String) {
//        val res = execute(0x00, 0xE2, 0xA0, 0x00, 0x0A, encICCID(iccid))
//        if (res.sw != "9000") {
//            throw Exception("Failed to write ICCID")
//        }
        select(SIMView.FID_MF)
        select(SIMView.FID_EF_ICCID)
        writeBinary(SimEncoder.encICCID(iccid))
    }

    fun readIMSI(): String {
        val res = execute(0x00, 0xE1, 0xA2, 0x00, 0x00, "")
        if (res.sw != "9000") {
            throw Exception("Failed to read IMSI")
        }
        return SimEncoder.decIMSI(res.data)!!
    }

    fun writeIMSI(imsi: String) {
        val res = execute(0x00, 0xE2, 0xA2, 0x00, 0x09, SimEncoder.encIMSI(imsi))
        if (res.sw != "9000") {
            throw Exception("Failed to write IMSI")
        }
    }

    fun select(fid: String) {
        if (execute(0x80, INS_SEL, 0x00, 0x00, fid.length / 2, fid).sw != "9000") {
            throw Exception("Failed to select file")
        }
    }

    fun readBinary(length: Int): String {
        val res = execute(0x00, INS_RB, 0x00, 0x00, length, "")
        if (res.sw != "9000") {
            throw Exception("Failed to read binary")
        }
        return res.data
    }

    fun writeBinary(data: String) {
        val res = execute(0x00, INS_WB, 0x00, 0x00, data.length / 2, data)
        if (res.sw != "9000") {
            throw Exception("Failed to write binary")
        }
    }

    fun readRecord(record: Int, length: Int): String {
        val res = execute(0x00, INS_RR, record, 0x04, length, "")
        if (res.sw != "9000") {
            throw Exception("Failed to read record")
        }
        return res.data
    }

    fun writeRecord(record: Int, data: String) {
        val res = execute(0x00, INS_WR, record, 0x04, data.length / 2, data)
        if (res.sw != "9000") {
            throw Exception("Failed to write record")
        }
    }

    fun writeMSISDN_USIM(msisdn: String) {
        execute(0x00, INS_SEL, 0x00, 0x04, 0x02, SIMView.FID_EF_MSISDN)
        execute(
            0x00,
            INS_WR,
            0x01,
            0x04,
            0x1C,
            SimEncoder.encMSISDN(msisdn).padStart(56, 'F')
        )
    }

}