package org._1935711.gosia;

import javacard.framework.Util;

public class Msx {
    // @formatter:off
    // static private final byte APDU_CLA = (byte) 0xA0;
    // static private final short APDU_OFFSET_CLA = 0;
    static private final short APDU_OFFSET_INS = 1;
    static private final short APDU_OFFSET_P1 = 2;
    static private final short APDU_OFFSET_P2 = 3;
    // static private final short APDU_OFFSET_P3 = 4;
    // static private final short APDU_OFFSET_LE = 4;
    // static private final short APDU_OFFSET_LC = 4;
    static private final short APDU_OFFSET_DATA = 5;

    static public final byte APDU_INS_PLAY = (byte) 0xCF;
    // static public final byte APDU_INS_LOAD_A0 = (byte) 0xA0;
    static public final byte APDU_INS_LOAD_A1 = (byte) 0xA1;
    static public final byte APDU_INS_LOAD_A2 = (byte) 0xA2;
    static public final byte APDU_INS_LOAD_A3 = (byte) 0xA3;
    static public final byte APDU_INS_LOAD_A4 = (byte) 0xA4;
    static public final byte APDU_INS_LOAD_A5 = (byte) 0xA5;
    // static public final byte APDU_INS_LOAD_B0 = (byte) 0xB0;
    static public final byte APDU_INS_LOAD_B1 = (byte) 0xB1;
    static public final byte APDU_INS_LOAD_B2 = (byte) 0xB2;
    static public final byte APDU_INS_LOAD_B3 = (byte) 0xB3;
    static public final byte APDU_INS_LOAD_B4 = (byte) 0xB4;
    static public final byte APDU_INS_LOAD_B5 = (byte) 0xB5;

    static public final short BLOCK_LENGTH = 256;

    // How many APDUs needed to transfer a single sample.
    static private final short SAMPLE_A_CHUNK_COUNT = 127;
    static private final short SAMPLE_B_CHUNK_COUNT = 26;
    // Approximation of the sample size using chunks of size BLOCK_LENGTH.
    static private final short SAMPLE_A_LENGTH = BLOCK_LENGTH * SAMPLE_A_CHUNK_COUNT;
    static private final short SAMPLE_B_LENGTH = BLOCK_LENGTH * SAMPLE_B_CHUNK_COUNT;
    static private final short SAMPLE_COUNT = 6;
    static public final short STATE_SAMPLE_LENGTH_SIZE = 2;
    // private byte[] sampleA0 = new byte[SAMPLE_A_LENGTH];
    // private byte[] sampleB0 = new byte[SAMPLE_B_LENGTH];
    private byte[] sampleA1 = new byte[SAMPLE_A_LENGTH];
    private byte[] sampleB1 = new byte[SAMPLE_B_LENGTH];
    private byte[] sampleA2 = new byte[SAMPLE_A_LENGTH];
    private byte[] sampleB2 = new byte[SAMPLE_B_LENGTH];
    private byte[] sampleA3 = new byte[SAMPLE_A_LENGTH];
    private byte[] sampleB3 = new byte[SAMPLE_B_LENGTH];
    private byte[] sampleA4 = new byte[SAMPLE_A_LENGTH];
    private byte[] sampleB4 = new byte[SAMPLE_B_LENGTH];
    private byte[] sampleA5 = new byte[SAMPLE_A_LENGTH];
    private byte[] sampleB5 = new byte[SAMPLE_B_LENGTH];

    static public final short MEMORY_SIZE = BLOCK_LENGTH;
    public byte[] memory;

    static private final short EFFECT_LEN_0 = (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT) * 2;
    static private final short EFFECT_LEN_1 = (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT) * 2;
    static private final short EFFECT_LEN_2 = (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT) * 5;
    static private final short EFFECT_LEN_3 = (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT) * 4;
    static private final short EFFECT_LEN_4 = (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT) * 1;
    // static private final short EFFECT_LEN_5 = (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT) * 1;

    static private final short EFFECT_END_0 = EFFECT_LEN_0;
    static private final short EFFECT_END_1 = EFFECT_LEN_0 + EFFECT_LEN_1;
    static private final short EFFECT_END_2 = EFFECT_LEN_0 + EFFECT_LEN_1 + EFFECT_LEN_2;
    static private final short EFFECT_END_3 = EFFECT_LEN_0 + EFFECT_LEN_1 + EFFECT_LEN_2 + EFFECT_LEN_3;
    static private final short EFFECT_END_4 = EFFECT_LEN_0 + EFFECT_LEN_1 + EFFECT_LEN_2 + EFFECT_LEN_3 + EFFECT_LEN_4;
    // static private final short EFFECT_END_5 = EFFECT_LEN_0 + EFFECT_LEN_1 + EFFECT_LEN_2 + EFFECT_LEN_3 + EFFECT_LEN_4 + EFFECT_LEN_5;
    // @formatter:on

    public Msx(byte[] _memory) {
        memory = _memory;
    }

    public byte controlPlay(byte[] apduBuffer) {
        final short p1 = apduBuffer[APDU_OFFSET_P1];
        final short p2 = apduBuffer[APDU_OFFSET_P2];
        final short frameIndex = (short) ((((p1 & 0xFF) << 8) | (p2 & 0xFF)));

        byte sampleId = (byte) 0x20;

        if (frameIndex >= 0 && frameIndex < EFFECT_END_0) {
            final short frameIndexRelative = (short) (frameIndex
                    % (short) (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT));
            if (frameIndexRelative < SAMPLE_A_CHUNK_COUNT) {
                final short sampleIndex = (short) (frameIndexRelative * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleA1, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 1;
            } else {
                final short sampleIndex = (short) ((frameIndexRelative - SAMPLE_A_CHUNK_COUNT) * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleB1, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 0x11;
            }
        } else if (frameIndex >= EFFECT_END_0 && frameIndex < EFFECT_END_1) {
            final short frameIndexRelative = (short) ((short) (frameIndex - EFFECT_END_0)
                    % (short) (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT));
            if (frameIndexRelative < SAMPLE_A_CHUNK_COUNT) {
                final short sampleIndex = (short) (frameIndexRelative * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleA2, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 2;
            } else {
                final short sampleIndex = (short) ((frameIndexRelative - SAMPLE_A_CHUNK_COUNT) * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleB2, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 0x12;
            }
        } else if (frameIndex >= EFFECT_END_1 && frameIndex < EFFECT_END_2) {
            final short frameIndexRelative = (short) ((short) (frameIndex - EFFECT_END_1)
                    % (short) (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT));
            if (frameIndexRelative < SAMPLE_A_CHUNK_COUNT) {
                final short sampleIndex = (short) (frameIndexRelative * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleA3, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 3;
            } else {
                final short sampleIndex = (short) ((frameIndexRelative - SAMPLE_A_CHUNK_COUNT) * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleB3, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 0x13;
            }
        } else if (frameIndex >= EFFECT_END_2 && frameIndex < EFFECT_END_3) {
            final short frameIndexRelative = (short) ((short) (frameIndex - EFFECT_END_2)
                    % (short) (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT));
            if (frameIndexRelative < SAMPLE_A_CHUNK_COUNT) {
                final short sampleIndex = (short) (frameIndexRelative * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleA4, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 4;
            } else {
                final short sampleIndex = (short) ((frameIndexRelative - SAMPLE_A_CHUNK_COUNT) * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleB4, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 0x14;
            }
        } else if (frameIndex >= EFFECT_END_3 && frameIndex < EFFECT_END_4) {
            final short frameIndexRelative = (short) ((short) (frameIndex - EFFECT_END_3)
                    % (short) (SAMPLE_A_CHUNK_COUNT + SAMPLE_B_CHUNK_COUNT));
            if (frameIndexRelative < SAMPLE_A_CHUNK_COUNT) {
                final short sampleIndex = (short) (frameIndexRelative * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleA5, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 5;
            } else {
                final short sampleIndex = (short) ((frameIndexRelative - SAMPLE_A_CHUNK_COUNT) * BLOCK_LENGTH);
                Util.arrayCopyNonAtomic(sampleB5, sampleIndex, memory, (short) 0, BLOCK_LENGTH);
                sampleId = 0x15;
            }
        } else {
            Util.arrayFillNonAtomic(memory, (short) 0, BLOCK_LENGTH, (byte) 0);
        }
        return sampleId;
    }

    public void controlLoad(final byte[] apduBuffer, final short readCount) {
        final short ins = apduBuffer[APDU_OFFSET_INS];
        final short p1 = apduBuffer[APDU_OFFSET_P1];
        final short p2 = apduBuffer[APDU_OFFSET_P2];
        final short sampleIndex = (short) ((((p1 & 0xFF) << 8) | (p2 & 0xFF)));

        switch (ins) {
            // @formatter:off
            // case APDU_INS_LOAD_A0:
            //     Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleA0, sampleIndex, readCount);
            //     break;
            // @formatter:on
            case APDU_INS_LOAD_A1:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleA1, sampleIndex, readCount);
                break;
            case APDU_INS_LOAD_A2:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleA2, sampleIndex, readCount);
                break;
            case APDU_INS_LOAD_A3:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleA3, sampleIndex, readCount);
                break;
            case APDU_INS_LOAD_A4:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleA4, sampleIndex, readCount);
                break;
            case APDU_INS_LOAD_A5:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleA5, sampleIndex, readCount);
                break;
            // @formatter:off
            // case APDU_INS_LOAD_B0:
            //     Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleB0, sampleIndex, readCount);
            //     break;
            // @formatter:on
            case APDU_INS_LOAD_B1:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleB1, sampleIndex, readCount);
                break;
            case APDU_INS_LOAD_B2:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleB2, sampleIndex, readCount);
                break;
            case APDU_INS_LOAD_B3:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleB3, sampleIndex, readCount);
                break;
            case APDU_INS_LOAD_B4:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleB4, sampleIndex, readCount);
                break;
            case APDU_INS_LOAD_B5:
                Util.arrayCopyNonAtomic(apduBuffer, APDU_OFFSET_DATA, sampleB5, sampleIndex, readCount);
                break;
            default:
                break;
        }

    }
}
