package de.ladkau.schwertundmagieonpebblecompanionapp

import java.nio.ByteBuffer

/**
 * JNI bridge to the VICE headless C64 emulator.
 *
 * The native library is built from app/src/main/jni/ using the Android NDK.
 * VICE source must be placed at app/src/main/jni/vice-src/ before building
 * (download from https://vice-emu.sourceforge.io/).
 *
 * ROM files (kernal, basic, chargen) and .d64 disk images must be copied to
 * the app's external files directory before calling initEmulator().
 * That directory is accessible via USB file transfer (no adb needed) at:
 *   Android/data/de.ladkau.schwertundmagieonpebblecompanionapp/files/
 */
class C64Engine {

    /** Initialize VICE. romDir should contain kernal, basic, chargen. */
    external fun initEmulator(romDir: String): Boolean

    /** Reset the machine and autostart from this disk image (use for A-side episode disks). */
    external fun loadDisk(path: String): Boolean

    /** Hot-swap disk in drive 8 without resetting (use for B-side / hero disks). */
    external fun attachDisk(path: String): Boolean

    /** Eject the disk and hard-reset the C64 to a BASIC ready prompt. */
    external fun resetMachine(): Boolean

    /** Save a full machine snapshot to [path] (queued to the VICE thread). */
    external fun saveState(path: String): Boolean

    /** Restore a machine snapshot from [path] (queued to the VICE thread). */
    external fun loadState(path: String): Boolean

    /** Run the emulator for one video frame (~20 000 CPU cycles). */
    external fun runFrame()

    /**
     * Copy the current 320×200 ARGB frame into [buffer].
     * The buffer must have capacity >= 320 * 200 * 4 bytes.
     */
    external fun getVideoBuffer(buffer: ByteBuffer)

    /**
     * Inject a C64 keyboard event.
     * [keyCode] uses VICE's row/column encoding: (row << 8) | col (0-based).
     */
    external fun injectKey(keyCode: Int, pressed: Boolean)

    /** Enable or disable audio output. Disabled by default. */
    external fun setSoundEnabled(enabled: Boolean)

    /** Returns true if drive 8's LED is currently on (disk read/write in progress). */
    external fun getDriveLed(): Boolean

    /** Monotonically increasing frame counter — incremented once per rendered C64 frame. */
    external fun getFrameCount(): Int

    /** Current 40x25 C64 text screen as ASCII, rows separated by '\n'. */
    external fun getScreenText(): String

    /**
     * Converts a disk image file at [inPath] to the format implied by [outPath]'s
     * extension (e.g. .nbz/.nib -> .g64/.d64) via nibtools' nibconv, run in-process.
     * Returns false (without writing [outPath]) if nibtools wasn't compiled in.
     */
    external fun convertDiskImage(inPath: String, outPath: String): Boolean

    companion object {
        init { System.loadLibrary("vice_jni") }

        // C64 keyboard matrix: row/col from the hardware scan matrix
        // Row\Col  0      1      2      3      4      5      6      7
        //   0:    DEL    RET   C-R/L   F7     F1     F3     F5   C-U/D
        //   1:     3      W      A      4      Z      S      E    LSHFT
        //   2:     5      R      D      6      C      F      T      X
        //   3:     7      Y      G      8      B      H      U      V
        //   4:     9      I      J      0      M      K      O      N
        //   5:     +      P      L      -      .      :      @      ,
        //   6:     £      *      ;    HOME   RSHFT    =      ^      /
        //   7:     1    A-LFT  CTRL    2    SPACE    C=      Q    R/S
        const val KEY_1       = (7 shl 8) or 0
        const val KEY_2       = (7 shl 8) or 3
        const val KEY_3       = (1 shl 8) or 0
        const val KEY_4       = (1 shl 8) or 3
        const val KEY_5       = (2 shl 8) or 0
        const val KEY_6       = (2 shl 8) or 3
        const val KEY_7       = (3 shl 8) or 0
        const val KEY_8       = (3 shl 8) or 3
        const val KEY_9       = (4 shl 8) or 0
        const val KEY_0       = (4 shl 8) or 3
        // Letters
        const val KEY_Q       = (7 shl 8) or 6
        const val KEY_W       = (1 shl 8) or 1
        const val KEY_E       = (1 shl 8) or 6
        const val KEY_R       = (2 shl 8) or 1
        const val KEY_T       = (2 shl 8) or 6
        const val KEY_Y       = (3 shl 8) or 1
        const val KEY_U       = (3 shl 8) or 6
        const val KEY_I       = (4 shl 8) or 1
        const val KEY_O       = (4 shl 8) or 6
        const val KEY_P       = (5 shl 8) or 1
        const val KEY_A       = (1 shl 8) or 2
        const val KEY_S       = (1 shl 8) or 5
        const val KEY_D       = (2 shl 8) or 2
        const val KEY_F       = (2 shl 8) or 5
        const val KEY_G       = (3 shl 8) or 2
        const val KEY_H       = (3 shl 8) or 5
        const val KEY_J       = (4 shl 8) or 2
        const val KEY_K       = (4 shl 8) or 5
        const val KEY_L       = (5 shl 8) or 2
        const val KEY_Z       = (1 shl 8) or 4
        const val KEY_X       = (2 shl 8) or 7
        const val KEY_C       = (2 shl 8) or 4
        const val KEY_V       = (3 shl 8) or 7
        const val KEY_B       = (3 shl 8) or 4
        const val KEY_N       = (4 shl 8) or 7
        const val KEY_M       = (4 shl 8) or 4
        // Control / special
        const val KEY_RETURN  = (0 shl 8) or 1
        const val KEY_SPACE   = (7 shl 8) or 4
        const val KEY_DEL     = (0 shl 8) or 0
        const val KEY_HOME    = (6 shl 8) or 3
        const val KEY_LSHIFT  = (1 shl 8) or 7
        const val KEY_RSHIFT  = (6 shl 8) or 4
        const val KEY_CTRL    = (7 shl 8) or 2
        const val KEY_CBM     = (7 shl 8) or 5
        const val KEY_CUR_UD  = (0 shl 8) or 7   // alone = down; SHIFT+this = up
        const val KEY_CUR_LR  = (0 shl 8) or 2   // alone = right; SHIFT+this = left
        // Function
        const val KEY_F1      = (0 shl 8) or 4
        const val KEY_F3      = (0 shl 8) or 5
        const val KEY_F5      = (0 shl 8) or 6
        const val KEY_F7      = (0 shl 8) or 3
        const val KEY_RUNSTOP = (7 shl 8) or 7
    }
}
