// warning: experimental, incomplete and likely to change in the future
// if you find problems, please send a pull request

/**
 * The type of disk/bios/state images
 *
 * `async` defaults to false, but this is subject to change. If true, the file
 * is downloaded completely, otherwise in chunks (see below for the chunking
 * method). BIOS, multiboot, bzimage and state files are always downloaded
 * completely, as they’re required before emulation can start.
 *
 * Files with `async: true` and `use_parts: false` are downloaded using HTTP
 * Range requests. Note that not all web servers support this header correctly,
 * and it inherently disables HTTP compression. The `url` fields points
 * directly to the disk images.
 *
 * If you specify `use_parts: true`, v86 expects the image to be split in files
 * of `fixed_chunk_size` bytes. You can use `tools/split-image.py` in the
 * repository to split an image. V86 appends `-<start-byte>-<end-byte>` to the
 * url.
 *
 * state images and fixed-size chunks (but not other image types) that end with
 * .zst are automatically decompressed using a built-in zstd decompressor. This
 * has a performance overhead compared to HTTP compression, but will result in
 * better compression ration.
 */
export type V86Image =
    | {
          url: string;
          async?: boolean;
          size: number;
          use_parts?: boolean;
          fixed_chunk_size?: number;
      }
    //| { buffer: File; async?: boolean; }; // only in browsers: https://developer.mozilla.org/en-US/docs/Web/API/File
    | { buffer: ArrayBuffer };

export enum LogLevel {
    LOG_ALL = -1,
    LOG_NONE = 0,
    LOG_OTHER = 0x000001,
    LOG_CPU = 0x000002,
    LOG_FPU = 0x000004,
    LOG_MEM = 0x000008,
    LOG_DMA = 0x000010,
    LOG_IO = 0x000020,
    LOG_PS2 = 0x000040,
    LOG_PIC = 0x000080,
    LOG_VGA = 0x000100,
    LOG_PIT = 0x000200,
    LOG_MOUSE = 0x000400,
    LOG_PCI = 0x000800,
    LOG_BIOS = 0x001000,
    LOG_FLOPPY = 0x002000,
    LOG_SERIAL = 0x004000,
    LOG_DISK = 0x008000,
    LOG_RTC = 0x010000,
    LOG_HPET = 0x020000,
    LOG_ACPI = 0x040000,
    LOG_APIC = 0x080000,
    LOG_NET = 0x100000,
    LOG_VIRTIO = 0x200000,
    LOG_9P = 0x400000,
    LOG_SB16 = 0x800000,
}

export enum BootOrder {
    AUTO = 0,
    CD_FLOPPY_HARDDISK = 0x213,
    CD_HARDDISK_FLOPPY = 0x123,
    FLOPPY_CD_HARDDISK = 0x231,
    FLOPPY_HARDDISK_CD = 0x321,
    HARDDISK_CD_FLOPPY = 0x132,
}

export enum Event {
    FS_ATTACH = "9p-attach",
    FS_READ_END = "9p-read-end",
    FS_READ_START = "9p-read-start",
    FS_WRITE_END = "9p-write-end",
    DOWNLOAD_ERROR = "download-error",
    DOWNLOAD_PROGRESS = "download-progress",
    EMULATOR_LOADED = "emulator-loaded",
    EMULATOR_READY = "emulator-ready",
    EMULATOR_STARTED = "emulator-started",
    EMULATOR_STOPPED = "emulator-stopped",
    ETH_RECEIVE_END = "eth-receive-end",
    ETH_TRANSMIT_END = "eth-transmit-end",
    IDE_READ_END = "ide-read-end",
    IDE_READ_START = "ide-read-start",
    IDE_WRITE_END = "ide-write-end",
    MOUSE_ENABLE = "mouse-enable",
    NET0_SEND = "net0-send",
    SCREEN_PUT_CHAR = "screen-put-char",
    SCREEN_SET_SIZE = "screen-set-size",
    SERIAL0_OUTPUT_BYTE = "serial0-output-byte",
    VIRTIO_CONSOLE0_OUTPUT_BYTES = "virtio-console0-output-bytes",
}

/**
 * emulator instance constructor options.
 */
export interface V86Options {
    /**
     * Reference to the v86 wasm exorted function.
     */
    wasm_fn?: (options: WebAssembly.Imports) => Promise<WebAssembly.Exports>;

    /**
     * Path to v86 wasm artifact
     * @default "build/v86.wasm" or "build/v86-debug.wasm" when debug mode enabled
     */
    wasm_path?: string;

    /**
     * The memory size in bytes, should be a power of 2.
     * @example 16 * 1024 * 1024
     * @default 64 * 1024 * 1024
     */
    memory_size?: number;

    /**
     * VGA memory size in bytes.
     * @example 8 * 1024 * 1024
     * @default 8 * 1024 * 1024
     */
    vga_memory_size?: number;

    /**
     * If emulation should be started when emulator is ready.
     * @default false
     */
    autostart?: boolean;

    /**
     * If keyboard should be disabled.
     */
    disable_keyboard?: boolean;

    /**
     * If mouse should be disabled.
     */
    disable_mouse?: boolean;

    /**
     * If speaker should be disabled.
     */
    disable_speaker?: boolean;

    /**
     * Either a url pointing to a bios or an ArrayBuffer
     */
    bios?: V86Image;

    /**
     * VGA bios
     */
    vga_bios?: V86Image;

    /**
     * First hard disk
     */
    hda?: V86Image;

    /**
     * Second hard disk
     */
    hdb?: V86Image;

    /**
     * First floppy disk
     */
    fda?: V86Image;

    /**
     * Second floppy disk
     */
    fdb?: V86Image;

    /**
     * CD-ROM
     * By default, an ejected CD-ROM drive is emulated
     */
    cdrom?: V86Image;

    /**
     * A Linux kernel image to boot (only bzimage format)
     */
    bzimage?: V86Image;

    /**
     * Kernel boot cmdline
     */
    cmdline?: string;

    /**
     * A Linux ramdisk image
     */
    initrd?: V86Image;

    /**
     * Automatically fetch bzimage and initrd from the 9p filesystem
     */
    bzimage_initrd_from_filesystem?: boolean;

    /**
     * multiboot image
     */
    multiboot?: V86Image;

    /**
     * An initial state to load
     * See `save_state`
     */
    initial_state?: V86Image;

    /**
     * Should the MAC address be preserved from the state image, for operating systems that don't allow you to reload the network card driver
     */
    preserve_mac_from_state_image?: boolean;

    /**
     * A 9p filesystem is supported by the emulator, using a virtio transport. Using it, files can be exchanged with the guest OS
     * If `basefs` and `baseurl` are omitted, an empty 9p filesystem is created.
     */
    filesystem?: {
        /**
         * json file created using [fs2json](https://github.com/copy/v86/blob/master/tools/fs2json.py).
         */
        baseurl?: string;

        /**
         * A directory of 9p files, as created by [copy-to-sha256.py](https://github.com/copy/v86/blob/master/tools/copy-to-sha256.py).
         * For more details, see docs/filesystem.md
         */
        basefs?: string;

        /**
         * A function that will be called for each 9p request.
         * If specified, this will back Virtio9p instead of a filesystem.
         * Use this to build or connect to a custom 9p server.
         */
        handle9p?: (reqbuf: Uint8Array, reply: (replybuf: Uint8Array) => void) => void;

        /**
         * A URL to a websocket proxy for 9p.
         * If specified, this will back Virtio9p instead of a filesystem.
         * Use this to connect to a custom 9p server over websocket.
         */
        proxy_url?: string;
    };

    /**
     * A textarea that will receive and send data to the emulated serial terminal.
     * Alternatively the serial terminal can also be accessed programatically, see [serial.html](../examples/serial.html).
     */
    serial_container?: HTMLTextAreaElement;

    /**
     * Xtermjs serial terminal container. When set, serial_container option is ignored.
     */
    serial_container_xtermjs?: HTMLElement;

    /**
     * An HTMLElement. This should have a certain structure, see [basic.html](../examples/basic.html).
     */
    screen_container?: HTMLElement | null;

    /**
     * Enable ACPI (also enables APIC). Experimental and only partially implemented.
     * @default false
     */
    acpi?: boolean;

    /**
     * log level
     * @default LogLevel.LOG_NONE
     */
    log_level?: LogLevel;

    /**
     * boot order
     * @default BootOrder.AUTO
     */
    boot_order?: BootOrder;

    /**
     * fast boot, skips boot menu in bochs bios
     */
    fastboot?: boolean;

    /**
     * create a virtio balloon device
     * @default false
     */
    virtio_balloon?: boolean;

    /**
     * create a virtio console device
     * @default false
     */
    virtio_console?: boolean;

    /**
     * override the maximum supported cpuid level
     * used for some versions of Windows, see docs/windows-nt.md
     */
    cpuid_level?: number;

    /**
     * turn off the x86-to-wasm jit
     * @default false
     */
    disable_jit?: boolean;

    /**
     * The url of a server running websockproxy
     * Deprecated in favor of the net_device config below
     */
    network_relay_url?: string;

    /**
     * Network device configuration, see docs/networking.md for more infos
     */
    net_device?: {
        type?: "ne2k" | "virtio";
        relay_url?: string;
        id?: number;
        router_mac?: string;
        router_ip?: string;
        vm_ip?: string;
        masquerade?: boolean;
        dns_method?: "static" | "doh";
        doh_server?: string;
        cors_proxy?: string;
    };
}

export class V86 {
    constructor(options: V86Options);

    /**
     * Start emulation. Do nothing if emulator is running already. Can be asynchronous.
     */
    run(): void;

    /**
     * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
     */
    stop(): Promise<void>;

    /**
     * Free resources associated with this instance
     */
    destroy(): Promise<void>;

    /**
     * Restart (force a reboot).
     */
    restart(): void;

    /**
     * Add an event listener (the emulator is an event emitter).
     *
     * The callback function gets a single argument which depends on the event.
     *
     * @param event Name of the event.
     * @param listener The callback function.
     */
    add_listener(event: Event, listener: Function): void;

    /**
     * Remove an event listener.
     *
     * @param event
     * @param listener
     */
    remove_listener(event: Event, listener: Function): void;

    /**
     * Restore the emulator state from the given state, which must be an
     * ArrayBuffer returned by
     * [`save_state`](#save_statefunctionobject-arraybuffer-callback).
     *
     * Note that the state can only be restored correctly if this constructor has
     * been created with the same options as the original instance (e.g., same disk
     * images, memory size, etc.).
     *
     * Different versions of the emulator might use a different format for the
     * state buffer.
     *
     * @param state
     */
    restore_state(state: ArrayBuffer): Promise<void>;

    /**
     * Asynchronously save the current state of the emulator.
     */
    save_state(): Promise<ArrayBuffer>;

    get_instruction_counter(): number;
    is_running(): boolean;

    /**
     * Set the image inserted in the floppy drive. Can be changed at runtime, as
     * when physically changing the floppy disk.
     */
    set_fda(image: V86Image): Promise<void>;

    /**
     * Eject the floppy drive.
     */
    eject_fda(): void;

    /**
     * Set the image inserted in the CD-ROM drive. Can be changed at runtime, as
     * when physically changing the CD-ROM.
     */
    set_cdrom(image: V86Image): Promise<void>;

    /**
     * Eject the CD-ROM.
     */
    eject_cdrom(): void;

    /**
     * Send a sequence of scan codes to the emulated PS2 controller. A list of
     * codes can be found at http://stanislavs.org/helppc/make_codes.html.
     * Do nothing if there is no keyboard controller.
     *
     * @param codes
     */
    keyboard_send_scancodes(codes: number[]): void;

    /**
     * Send translated keys
     */
    keyboard_send_keys(codes: number[]): void;

    /**
     * Send text, assuming the guest OS uses a US keyboard layout
     */
    keyboard_send_text(string: string): void;

    /**
     * Download a screenshot (returns an <img> element, only works in browsers)
     */
    screen_make_screenshot(): HTMLElement;

    /**
     * Set the scaling level of the emulated screen.
     *
     * @param {number} sx
     * @param {number} sy
     */
    screen_set_scale(sx: number, sy: number): void;

    /**
     * Go fullscreen (only browsers)
     */
    screen_go_fullscreen(): void;

    /**
     * Lock the mouse cursor: It becomes invisble and is not moved out of the browser window (only browsers)
     */
    lock_mouse(): void;

    /**
     * Enable or disable sending mouse events to the emulated PS2 controller.
     */
    mouse_set_enabled(enabled: boolean): void;

    /**
     * Enable or disable sending keyboard events to the emulated PS2 controller.
     */
    keyboard_set_enabled(enabled: boolean): void;

    /**
     * Send a string to the first emulated serial terminal.
     *
     * @param data
     */
    serial0_send(data: string): void;

    /**
     * Send bytes to a serial port (to be received by the emulated PC).
     *
     * @param serial the index of the serial port
     * @param data
     */
    serial_send_bytes(serial: number, data: Uint8Array): void;

    /**
     * Write to a file in the 9p filesystem. Nothing happens if no filesystem has
     * been initialized.
     *
     * @param file
     * @param data
     * @param callback
     */
    create_file(file: string, data: Uint8Array): Promise<void>;

    /**
     * Read a file in the 9p filesystem.
     *
     * @param {string} file
     */
    read_file(file: string): Promise<Uint8Array>;
}
