package internal.sim.sample_applet;

import javacard.framework.*;
import javacardx.framework.math.BCDUtil;
import uicc.access.*;
import uicc.toolkit.*;
import uicc.usim.access.USIMConstants;

public class SampleApplet extends Applet implements ToolkitInterface, ToolkitConstants {
    //SIMカード上で稼働中のUSIM ApplicationのAID値
    private static final byte[] usimAID = {
        (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x87, (byte) 0x10, (byte) 0x02, (byte) 0xFF,
        (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x89, (byte) 0x03, (byte) 0x02, (byte) 0x00, (byte) 0x00};
    private FileView uiccFileView;
    private FileView usimAppFileView;

    private ToolkitRegistry toolkitRegistry;
    private byte menuItem1;
    private byte menuItem2;

    static final byte[] nvramText = {'N', 'V', 'R', 'A', 'M', ':', ' '};
    static final byte[] ramText = {'R', 'A', 'M', ':', ' '};
    static final byte[] menuItem1Text = {'D', 'e', 'b', 'u', 'g'};
    static final byte[] menuItem2Text = {'T', 'e', 's', 't'};

    private byte[] debugBuffer;
    private short[] debugMemBuffer;
    private DiagUtil diag;

    private byte[] bcdBuffer;
    private byte[] tmpBuffer;

    private byte[] readBuffer;
    private byte[] imeiBuffer;
    private byte[] imsiBuffer;
    private byte[] iccidBuffer;

    public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException {
        SampleApplet sampleApplet = new SampleApplet();
        sampleApplet.register();

        sampleApplet.initUiccToolkit();
    }

    private void initUiccToolkit() {
        toolkitRegistry = ToolkitRegistrySystem.getEntry();

        menuItem1 = toolkitRegistry.initMenuEntry(menuItem1Text, (short) 0, (short) menuItem1Text.length,
            PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0);
        menuItem2 = toolkitRegistry.initMenuEntry(menuItem2Text, (short) 0, (short) menuItem2Text.length,
            PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0);

        uiccFileView = UICCSystem.getTheUICCView(JCSystem.NOT_A_TRANSIENT_OBJECT);
        usimAppFileView = UICCSystem.getTheFileView(usimAID, (short) 0, (byte) usimAID.length, JCSystem.NOT_A_TRANSIENT_OBJECT);
    }

    private SampleApplet() {
        diag = new DiagUtil();
        debugBuffer = JCSystem.makeTransientByteArray((short) 16, JCSystem.CLEAR_ON_RESET);
        debugMemBuffer = JCSystem.makeTransientShortArray((short) 2, JCSystem.CLEAR_ON_RESET);
        bcdBuffer = JCSystem.makeTransientByteArray((short) 10, JCSystem.CLEAR_ON_RESET);
        tmpBuffer = JCSystem.makeTransientByteArray((short) 64, JCSystem.CLEAR_ON_RESET);

        readBuffer = JCSystem.makeTransientByteArray((short) 256, JCSystem.CLEAR_ON_RESET);
        imeiBuffer = JCSystem.makeTransientByteArray((short) 15, JCSystem.CLEAR_ON_RESET);
        imsiBuffer = JCSystem.makeTransientByteArray((short) (1 + 15), JCSystem.CLEAR_ON_RESET);
        iccidBuffer = JCSystem.makeTransientByteArray((short) (1 + 20), JCSystem.CLEAR_ON_RESET);
    }

    public Shareable getShareableInterfaceObject(AID clientAID, byte parameter) {
        if (clientAID == null) {
            return this;
        }
        return null;
    }

    @Override
    public void process(APDU apdu) throws ISOException {

    }

    @Override
    public void processToolkit(short event) throws ToolkitException {
        // 本メソッドが例外を発生させても外部から観測できないため、デバッグが困難になります。
        // 必ず全ての例外をキャッチして、デバッグ用に記録するようにします。
        try {
            processToolkitEvent(event);
        } catch (UserException e) {
            Util.setShort(debugBuffer, (short) 4, e.getReason());
        } catch (ToolkitException e) {
            Util.setShort(debugBuffer, (short) 6, e.getReason());
            throw e;
        } catch (Exception e) {
            //その他の例外の発生回数を記録
            short i = Util.getShort(debugBuffer, (short) 2);
            Util.setShort(debugBuffer, (short) 2, (short) (i + 1));
        }
    }

    public void processToolkitEvent(short event) throws ToolkitException, UserException {
        if (event == EVENT_MENU_SELECTION) {
            // ユーザがSIM Toolkitアプリを端末上で開き、メニュー項目をタップすると呼ばれる
            EnvelopeHandler envHdlr = EnvelopeHandlerSystem.getTheHandler();
            byte selectedItemId = envHdlr.getItemIdentifier();

            if (selectedItemId == menuItem1) {
                DiagUtil.text(menuItem1Text); //'Debug'
                diag.displayBytes(debugBuffer, (short) 0, (short) debugBuffer.length);

                short pos, bcdBytes;
                //NVRAMの残量を表示
                JCSystem.getAvailableMemory(debugMemBuffer, (short) 0, JCSystem.MEMORY_TYPE_PERSISTENT);
                Util.setShort(bcdBuffer, (short) 0, debugMemBuffer[0]);
                Util.setShort(bcdBuffer, (short) 2, debugMemBuffer[1]);
                bcdBytes = BCDUtil.convertToBCD(bcdBuffer, (short) 0, (short) 4, bcdBuffer, (short) 0);
                pos = Util.arrayCopy(nvramText, (short) 0, tmpBuffer, (short) 0, (short) nvramText.length);
                pos = ByteUtil.bcdToCharArray(bcdBuffer, bcdBytes, tmpBuffer, pos);
                diag.text(tmpBuffer, (short) 0, pos);

                //RAMの残量を表示
                JCSystem.getAvailableMemory(debugMemBuffer, (short) 0, JCSystem.MEMORY_TYPE_TRANSIENT_RESET);
                Util.setShort(bcdBuffer, (short) 0, debugMemBuffer[0]);
                Util.setShort(bcdBuffer, (short) 2, debugMemBuffer[1]);
                bcdBytes = BCDUtil.convertToBCD(bcdBuffer, (short) 0, (short) 4, bcdBuffer, (short) 0);
                pos = Util.arrayCopy(ramText, (short) 0, tmpBuffer, (short) 0, (short) ramText.length);
                pos = ByteUtil.bcdToCharArray(bcdBuffer, bcdBytes, tmpBuffer, pos);
                diag.text(tmpBuffer, (short) 0, pos);
            }
            if (selectedItemId == menuItem2) {
                loadIMEI();
                DiagUtil.text(imeiBuffer);
                loadIMSI();
                DiagUtil.text(imsiBuffer, (short) 1, (short) imsiBuffer[0]);
                loadICCID(true);
                DiagUtil.text(iccidBuffer, (short) 1, (short) iccidBuffer[0]);
            }
        }

    }

    private void loadIMEI() throws ToolkitException, UserException {
        short length = 0;
        imeiBuffer[0] = 0;

        // 端末に コマンド PROVIDE LOCAL INFORMATION を送り、IMEIを取得する
        // 参照規格: ETSI TS 102223 Clause 6.4.15, 6.6.15など
        ProactiveHandler ph = ProactiveHandlerSystem.getTheHandler();
        ProactiveResponseHandler rh = ProactiveResponseHandlerSystem.getTheHandler();

        ph.init(PRO_CMD_PROVIDE_LOCAL_INFORMATION, (byte) 0x01, DEV_ID_TERMINAL);
        ph.send();

        if (rh.getGeneralResult() == RES_CMD_PERF) {
            length = rh.findAndCopyValue(TAG_IMEI, readBuffer, (short) 0);
        } else {
            UserException.throwIt((short) 0x7001);
        }
        if (length != 8) {
            UserException.throwIt((short) 0x7002);
        }
        // IMEIのデータ形式を考慮して文字列に変換し、チェックデジットを計算する
        // 参照規格: ETSI TS 102223 Clause 8.20, ETSI TS 124008 (3GPP TS 24.008), ETSI TS 123003 など
        ByteUtil.nibbleSwap(readBuffer, (short) 0, length);
        ByteUtil.bytesToHex(readBuffer, (short) 0, length, tmpBuffer, (short) 0);
        short checkDigit = ByteUtil.calcCheckDigitByLuhn(tmpBuffer, (short) 1, (short) 14);
        tmpBuffer[15] = (byte) (checkDigit + '0');
        Util.arrayCopyNonAtomic(tmpBuffer, (short) 1, imeiBuffer, (short) 0, (short) 15);
    }

    private void loadICCID(boolean removePadding) throws ToolkitException {
        //ICCIDをEF_ICCIDから読み出す
        // 参照規格: ETSI TS 102221 Clause 13.2
        short length = 10;
        short digits = 20;
        readBinaryFromEF(uiccFileView, UICCConstants.FID_EF_ICCID, readBuffer, (short) 0, length);
        //文字列に変換
        ByteUtil.nibbleSwap(readBuffer, (short) 0, length);
        ByteUtil.bytesToHex(readBuffer, (short) 0, length, iccidBuffer, (short) 1);

        if (removePadding) {
            // 末尾が'F'の場合は桁数をつめる
            while (iccidBuffer[(short) (digits)] == (byte) 'F') {
                digits--;
            }
        }
        iccidBuffer[0] = (byte) digits;
    }

    private void loadIMSI() throws ToolkitException {
        //IMSI (固定長9バイト)をUSIM ApplicationのEF_IMSIから読み出す
        short length = 9;
        readBinaryFromEF(usimAppFileView, USIMConstants.FID_EF_IMSI, readBuffer, (short) 0, length);

        // IMSIのデータ形式を考慮して文字列に変換する
        length = readBuffer[0];
        if (length == 0 || length > 8) {
            imsiBuffer[0] = 0;
            return;
        }
        ByteUtil.nibbleSwap(readBuffer, (short) 1, length);
        ByteUtil.bytesToHex(readBuffer, (short) 1, length, tmpBuffer, (short) 0);

        // 末尾が'F'の時は桁数をつめる
        // 参照規格: 3GPP TS 31.102 Clause 4.2.2 など
        short digits = (short) (length * 2 - 1);
        if (tmpBuffer[(short) (digits - 1)] == (byte) 'F') {
            digits--;
        }
        imsiBuffer[0] = (byte) digits;
        Util.arrayCopyNonAtomic(tmpBuffer, (short) 1, imsiBuffer, (short) 1, digits);
    }

    private short readBinaryFromEF(FileView fileView, short FID, byte[] dstBuffer, short dstOffset, short readLength) {
        fileView.select(FID);
        return fileView.readBinary((short) 0, dstBuffer, (short) dstOffset, readLength);
    }

}
