export const FREQUENCY_DELTA = 100000;

export type Callback<T> = (owner: T, params?: any) => void;

export type RomInfo = {
    name?: string;
    data?: Uint8Array;
    size?: number;
    extra?: Record<string, string | undefined>;
};

export type BenchmarkResult = {
    delta: number;
    count: number;
    cycles: number;
    frequency_mhz: number;
};

export type Entry = {
    text: string;
    url?: string;
};

/**
 * Enumeration to be used to describe the set of
 * features that a certain emulator supports, this
 * is going to condition its runtime execution.
 */
export enum Feature {
    Debug = 1,
    Palettes,
    Benchmark,
    Keyboard,
    KeyboardChip8,
    KeyboardGB
}

/**
 * Enumeration that describes the multiple pixel
 * formats and the associated size in bytes.
 */
export enum PixelFormat {
    RGB = 3,
    RGBA = 4
}

export interface ObservableI {
    bind(event: string, callback: Callback<this>): void;
    unbind(event: string, callback: Callback<this>): void;
    trigger(event: string): void;
}

/**
 * Top level interface that declares the main abstract
 * interface of an emulator structured entity.
 * Should allow typical hardware operations to be performed.
 */
export interface Emulator extends ObservableI {
    /**
     * The descriptive name of the emulator.
     */
    get name(): string;

    /**
     * The information on the hardware that is being emulated
     * by the emulator (eg: Super Nintendo), can contain a URL
     * that describes the device that is being emulated by
     * the emulator (eg: Wikipedia link).
     */
    get device(): Entry;

    /**
     * A semantic version string for the current version
     * of the emulator, can include a URL pointing to a
     * changelog or equivalent document.
     *
     * @see {@link https://semver.org}
     */
    get version(): Entry | undefined;

    /**
     * Information about the source code repository where
     * the emulator source code is being stored.
     */
    get repository(): Entry | undefined;

    /**
     * The features available and compatible with the emulator,
     * these values will influence the associated GUIs.
     */
    get features(): Feature[];

    /**
     * The complete set of engine names that can be used
     * in the re-boot operation.
     */
    get engines(): string[];

    /**
     * The name of the current execution engine being used
     * by the emulator.
     */
    get engine(): string | null;

    /**
     * The complete set of file extensions that this emulator
     * supports.
     */
    get romExts(): string[];

    /**
     * The pixel format of the emulator's display
     * image buffer (eg: RGB).
     */
    get pixelFormat(): PixelFormat;

    /**
     * Gets the complete image buffer as a sequence of
     * bytes that respects the current pixel format from
     * `getPixelFormat()`. This method returns an in memory
     * pointer to the heap and not a copy.
     */
    get imageBuffer(): Uint8Array;

    /**
     * Gets information about the ROM that is currently
     * loaded in the emulator, using a structure containing
     * the information about the ROM that is currently
     * loaded in the emulator.
     */
    get romInfo(): RomInfo;

    /**
     * The current CPU frequency (logic) of the emulator,
     * should impact other elements of the emulator.
     */
    get frequency(): number;
    set frequency(value: number);

    /**
     * The recommended frequency delta in hertz for scale up
     * and scale down operations in the CPU frequency.
     */
    get frequencyDelta(): number | null;

    /**
     * The current logic framerate of the running emulator.
     */
    get framerate(): number;

    /**
     * A dictionary that contains the register names associated
     * with their value either as strings or numbers.
     */
    get registers(): Record<string, string | number>;

    /**
     * The palette as a string name that is currently
     * set in the emulator for display.
     */
    get palette(): string | undefined;
    set palette(value: string | undefined);

    /**
     * Obtains the pixel buffer for the VRAM tile at the given
     * index.
     *
     * @param index The index of the tile to obtain pixel buffer.
     * @returns The pixel buffer of the tile at the given index.
     */
    getTile(index: number): Uint8Array;

    /**
     * Boot (or reboots) the emulator according to the provided
     * set of options.
     *
     * @param options The options that are going to be used for
     * the booting operation of the emulator.
     */
    boot(options: any): void;

    /**
     * Toggle the running state of the emulator between paused
     * and running, prevents consumers from the need to access
     * the current running state of the emulator to implement
     * a logic toggle.
     */
    toggleRunning(): void;
    pause(): void;
    resume(): void;

    /**
     * Resets the emulator machine to the start state and
     * re-loads the ROM that is currently set in the emulator.
     */
    reset(): void;

    keyPress(key: string): void;

    keyLift(key: string): void;

    /**
     * Changes the palette of the emulator to the "next" one,
     * the order in which the palette is chosen is defined by
     * the concrete emulator implementation.
     *
     * @returns The name of the palette that has been selected.
     */
    changePalette?: { (): string };

    /**
     * Runs a benchmark operation in the emulator, effectively
     * measuring the performance of it.
     *
     * @param count The number of benchmark iterations to be
     * run, increasing this value will make the benchmark take
     * more time to be executed.
     * @returns The result metrics from the benchmark run.
     */
    benchmark?: { (count?: number): BenchmarkResult };
}

/**
 * Abstract class that implements the basic functionality
 * part of the definition of the Observer pattern.
 *
 * @see {@link https://en.wikipedia.org/wiki/Observer_pattern}
 */
export class Observable {
    private events: Record<string, [Callback<this>]> = {};

    bind(event: string, callback: Callback<this>) {
        const callbacks = this.events[event] ?? [];
        if (callbacks.includes(callback)) return;
        callbacks.push(callback);
        this.events[event] = callbacks;
    }

    unbind(event: string, callback: Callback<this>) {
        const callbacks = this.events[event] ?? [];
        if (!callbacks.includes(callback)) return;
        const index = callbacks.indexOf(callback);
        callbacks.splice(index, 1);
        this.events[event] = callbacks;
    }

    trigger(event: string, params?: any) {
        const callbacks = this.events[event] ?? [];
        callbacks.forEach((c) => c(this, params));
    }
}

export class EmulatorBase extends Observable {
    get version(): Entry | undefined {
        return undefined;
    }

    get repository(): Entry | undefined {
        return undefined;
    }

    get features(): Feature[] {
        return [];
    }

    get frequencyDelta(): number | null {
        return FREQUENCY_DELTA;
    }

    get palette(): string | undefined {
        return undefined;
    }

    set palette(value: string | undefined) {}
}