package com.dangerousthings.aliro;

import javacard.framework.JCSystem;
import javacard.framework.Util;

/**
 * Cross-applet session state that survives an AID change (EXPEDITED →
 * STEP_UP) but not a card reset. Since Java Card installs distinct
 * applet instances per AID, {@link AliroApplet} and {@link StepUpApplet}
 * need a shared holder to hand off {@code StepUpSK}, the session-bound
 * 32-byte key derived during AUTH1 and consumed in the step-up phase.
 *
 * <p>Storage is a {@link JCSystem#CLEAR_ON_RESET} transient array,
 * lazily allocated on first access so that neither applet's constructor
 * assumes a particular install order. The reference is a static field;
 * both applets see the same underlying array.
 *
 * <p>State machine:
 * <ul>
 *   <li>Initial / post-reset: disarmed, {@code StepUpSK} zeroed.
 *   <li>{@link AliroApplet} SELECT or failure path: calls
 *       {@link #disarmStepUp()} to clear any stale state.
 *   <li>{@link AliroApplet} AUTH1 success: calls
 *       {@link #armStepUp(byte[], short)} to load the freshly-derived
 *       StepUpSK and flip the armed flag.
 *   <li>{@link StepUpApplet}: reads via {@link #isStepUpArmed()} + (future)
 *       an internal {@code copyStepUpSK} call to derive session keys.
 * </ul>
 */
final class SessionContext {

    static final short STEP_UP_SK_LEN = 32;

    private static byte[] stepUpSK;
    private static boolean[] armedFlag;  // length 1 — boolean transient arrays are per-JC convention

    private SessionContext() {}

    /** Allocates the transient storage on first call. Subsequent calls are no-ops. */
    static void ensureInitialized() {
        if (stepUpSK == null) {
            stepUpSK = JCSystem.makeTransientByteArray(STEP_UP_SK_LEN, JCSystem.CLEAR_ON_RESET);
            armedFlag = JCSystem.makeTransientBooleanArray((short) 1, JCSystem.CLEAR_ON_RESET);
        }
    }

    static boolean isStepUpArmed() {
        return armedFlag != null && armedFlag[0];
    }

    /** Loads {@code StepUpSK} from {@code src[srcOff..srcOff+32)} and arms the context. */
    static void armStepUp(byte[] src, short srcOff) {
        ensureInitialized();
        Util.arrayCopyNonAtomic(src, srcOff, stepUpSK, (short) 0, STEP_UP_SK_LEN);
        armedFlag[0] = true;
    }

    /** Zeroes {@code StepUpSK} and flips the armed flag off. */
    static void disarmStepUp() {
        if (stepUpSK != null) {
            Util.arrayFillNonAtomic(stepUpSK, (short) 0, STEP_UP_SK_LEN, (byte) 0);
        }
        if (armedFlag != null) {
            armedFlag[0] = false;
        }
    }

    /** Copies the armed StepUpSK into {@code dst[dstOff..]}. Only legal if {@link #isStepUpArmed()}. */
    static short copyStepUpSK(byte[] dst, short dstOff) {
        Util.arrayCopyNonAtomic(stepUpSK, (short) 0, dst, dstOff, STEP_UP_SK_LEN);
        return STEP_UP_SK_LEN;
    }

    /** Test-only accessor that skips the armed-flag precondition. */
    static short copyStepUpSKForTesting(byte[] dst, short dstOff) {
        return copyStepUpSK(dst, dstOff);
    }
}
