/**
 * Copyright (c) 2024 Opal Kelly Incorporated
 *
 * This source code is licensed under the FrontPanel license.
 * See the LICENSE file found in the root directory of this project.
 */

import { IFrontPanel, AddressRange } from "../core";
import { ByteCount, PipeAddress } from "../core";

import { WireAddress, WireValue, WireMask, WireWidth } from "../core";
import {
    TriggerVectorAddress,
    TriggerVector,
    TriggerVectorMask,
    TriggerVectorWidth
} from "../core";
import { RegisterAddress, RegisterValue } from "../core";

import MockDataBlock from "./MockDataBlock";

/**
 * Class representing a mock implementation of the IFrontPanel interface used for testing.
 */
class MockFrontPanel implements IFrontPanel {
    /**
     * Address range for WireIn endpoints.
     */
    public static readonly WIREIN_ADDRESS_RANGE: AddressRange = { minimum: 0x00, maximum: 0x1f };

    /**
     * Address range for WireOut endpoints.
     */
    public static readonly WIREOUT_ADDRESS_RANGE: AddressRange = { minimum: 0x20, maximum: 0x3f };

    /**
     * Address range for TriggerIn endpoints.
     */
    public static readonly TRIGGERIN_ADDRESS_RANGE: AddressRange = { minimum: 0x40, maximum: 0x5f };

    /**
     * Address range for TriggerOut endpoints.
     */
    public static readonly TRIGGEROUT_ADDRESS_RANGE: AddressRange = {
        minimum: 0x60,
        maximum: 0x7f
    };

    /**
     * Address range for PipeIn endpoints.
     */
    public static readonly PIPEIN_ADDRESS_RANGE: AddressRange = { minimum: 0x80, maximum: 0x9f };

    /**
     * Address range for PipeOut endpoints
     */
    public static readonly PIPEOUT_ADDRESS_RANGE: AddressRange = { minimum: 0xa0, maximum: 0xbf };

    /**
     * Address range for Register endpoints
     */
    public static readonly REGISTER_ADDRESS_RANGE: AddressRange = { minimum: 0x00, maximum: 0x1f };

    private readonly _WireInBlock: MockDataBlock;
    private readonly _WireOutBlock: MockDataBlock;

    private readonly _TriggerOutVectors: MockDataBlock;

    private readonly _RegisterBlock: MockDataBlock;

    /**
     * The WireIn data block
     */
    get WireInBlock(): MockDataBlock {
        return this._WireInBlock;
    }

    /**
     * The WireOut data block
     */
    get WireOutBlock(): MockDataBlock {
        return this._WireOutBlock;
    }

    /**
     * The TriggerOut data block
     */
    get TriggerOutBlock(): MockDataBlock {
        return this._TriggerOutVectors;
    }

    /**
     * The Register data block
     */
    get RegisterBlock(): MockDataBlock {
        return this._RegisterBlock;
    }

    /**
     *
     * @param wireWidth - The width of the mock WireIn and WireOut endpoints, measured in bits.
     * @param triggerVectorWidth - The width of the mock TriggerOut vectors, measured in bits.
     */
    constructor(wireWidth: WireWidth, triggerVectorWidth: TriggerVectorWidth) {
        this._WireInBlock = MockDataBlock.FromAddressRange(
            MockFrontPanel.WIREIN_ADDRESS_RANGE,
            wireWidth
        );
        this._WireOutBlock = MockDataBlock.FromAddressRange(
            MockFrontPanel.WIREOUT_ADDRESS_RANGE,
            wireWidth
        );

        this._TriggerOutVectors = MockDataBlock.FromAddressRange(
            MockFrontPanel.TRIGGEROUT_ADDRESS_RANGE,
            triggerVectorWidth
        );

        this._RegisterBlock = MockDataBlock.FromAddressRange(
            MockFrontPanel.REGISTER_ADDRESS_RANGE,
            32
        );
    }

    /**
     * Gets the value of the mock WireIn endpoint at the specified address.
     * @param address - The address of the WireIn endpoint.
     * @returns {WireValue} - The value of the mock WireIn endpoint.
     */
    public getWireInValue(address: WireAddress): WireValue {
        return this._WireInBlock.getValue(address) ?? 0;
    }

    /**
     * Sets the value of the mock WireIn endpoint at the specified address.
     * @param address - The address of the mock WireIn endpoint.
     * @param value - The value to set.
     * @param mask - The mask to apply to the value.
     */
    public setWireInValue(
        address: WireAddress,
        value: WireValue,
        mask: WireMask
    ): void {
        this._WireInBlock.setValue(address, value, mask);
    }

    /**
     * Updates all mock WireIn endpoints.
     * @returns {Promise<void>} - A promise that resolves when all mock WireIn endpoints have been updated.
     */
    public async updateWireIns(): Promise<void> {
        return;
    }

    /**
     * Gets the value of the mock WireOut endpoint at the specified address.
     * @param address - The address of the wire out.
     * @returns {WireValue} - The value of the wire out.
     */
    public getWireOutValue(address: WireAddress): WireValue {
        return this._WireOutBlock.getValue(address) ?? 0;
    }

    /**
     * Updates all mock WireOut endpoints.
     * @returns {Promise<void>} - A promise that resolves when all mock WireOut endpoints have been updated.
     */
    public async updateWireOuts(): Promise<void> {
        return;
    }

    /**
     * Activates the mock TriggerIn endpoint at the specified address and bit.
     * @param address - The address of the mock TriggerIn vector.
     * @param bit - The bit to activate.
     * @returns {Promise<void>} - A promise that resolves when the mock TriggerIn endpoint has been activated.
     */
    public async activateTriggerIn(address: TriggerVectorAddress, bit: number): Promise<void> {
        console.log(
            "MockFrontPanel.activateTriggerIn: address=" + address.toString(16) + " bit=" + bit
        );

        return;
    }

    /**
     * Gets the mock TriggerOut vector at the specified address.
     * @param address - The address of the mock TriggerOut vector.
     * @returns {TriggerVector} - The mock TriggerOut vector.
     */
    public getTriggerOutVector(address: TriggerVectorAddress): TriggerVector {
        return this._TriggerOutVectors.getValue(address) ?? 0;
    }
    /**
     * Checks if the mock TriggerOut endpoint at the specified address is active by applying the mask.
     * @param address - The address of the mock TriggerOut vector.
     * @param mask - The mask to apply to the mock TriggerOut vector.
     * @returns {boolean} - True if the TriggerOut endpoint is active, or false otherwise.
     */
    public isTriggered(
        address: TriggerVectorAddress,
        mask: TriggerVectorMask
    ): boolean {
        const vector = this._TriggerOutVectors.getValue(address) ?? 0;

        return (vector & mask) === mask;
    }

    /**
     * Updates all mock TriggerOut vectors.
     * @returns {Promise<void>} - A promise that resolves when all mock TriggerOut vectors have been updated.
     */
    public async updateTriggerOuts(): Promise<void> {
        return;
    }

    /**
     * Writes data to the PipeIn at the specified address.
     * @param address - The address of the PipeIn endpoint.
     * @param length - The length of the data to write in bytes.
     * @param data - The buffer containing the data to write.
     * @returns {Promise<ByteCount>} - A promise that resolves when the data has been written
     * indicating the number of bytes that were successfully written.
     */
    public async writeToPipeIn(
        _address: PipeAddress,
        _length: ByteCount,
        _data: ArrayBuffer
    ): Promise<ByteCount> {
        return 0;
    }

    /**
     * Writes data to the Block Throttle PipeIn at the specified address.
     * @param address - The address of the PipeIn endpoint.
     * @param blockSize - The size of the blocks to write in bytes.
     * @param length - The length of the data to write in bytes.
     * @param data - The buffer containing the data to write.
     * @returns {Promise<ByteCount>} - A promise that resolves when the data has been written
     * indicating the number of bytes that were successfully written.
     */
    public async writeToBlockPipeIn(
        _address: PipeAddress,
        _blockSize: ByteCount,
        _length: ByteCount,
        _data: ArrayBuffer
    ): Promise<ByteCount> {
        return 0;
    }

    /**
     * Reads data from the PipeOut at the specified address.
     * @param address - The address of the PipeOut endpoint.
     * @param length - The length of the data to read in bytes.
     * @param buffer - The buffer to store the data that is read.
     * @returns {Promise<ByteCount>} - A promise that resolves when the data has been read
     * indicating the number of bytes that were successfully read.
     */
    public async readFromPipeOut(
        _address: PipeAddress,
        _length: ByteCount,
        _buffer: ArrayBuffer
    ): Promise<ByteCount> {
        return 0;
    }
    /**
     * Reads data from the Block Throttle PipeOut at the specified address.
     * @param address - The address of the PipeOut endpoint.
     * @param blockSize - The size of the blocks to read in bytes.
     * @param length - The length of the data to read in bytes.
     * @param buffer - The buffer to store the data that is read.
     * @returns {Promise<ByteCount>} - A promise that resolves when the data has been read
     * indicating the number of bytes that were successfully read.
     */
    public async readFromBlockPipeOut(
        _address: PipeAddress,
        _blockSize: ByteCount,
        _length: ByteCount,
        _buffer: ArrayBuffer
    ): Promise<ByteCount> {
        return 0;
    }

    /**
     * Reads the value of the mock Register at the specified address.
     * @param address - The address of the mock Register.
     * @returns {Promise<RegisterValue>} - A promise that resolves to the value of the mock Register.
     */
    public async readRegister(address: RegisterAddress): Promise<RegisterValue> {
        return this._RegisterBlock.getValue(address) ?? 0;
    }

    /**
     * Reads the data of the Registers at the specified addresses and stores the data in the
     * corresponding array element.
     * @param registers - The array containing address and data pairs for each register.
     * @returns {Promise<void>} - A promise that resolves when all the registers have
     * been read.
     */
    public async readRegisters(
        _registers: Array<{ address: RegisterAddress; data: RegisterValue }>
    ): Promise<void> {
        return;
    }

    /**
     * Writes a value to the mock Register at the specified address.
     * @param address - The address of the mock Register.
     * @param value - The value to write.
     * @returns {Promise<void>} - A promise that resolves when the value has been written.
     */
    public async writeRegister(address: RegisterAddress, value: RegisterValue): Promise<void> {
        this._RegisterBlock.setValue(address, value, 0xffffffff);
    }

    /**
     * Writes data to each of the Registers at the specified addresses.
     * @param registers - The array containing address and data pairs for each register.
     * @returns {Promise<void>} - A promise that resolves when the register values have been written.
     */
    public async writeRegisters(
        _registers: Array<{ address: RegisterAddress; data: RegisterValue }>
    ): Promise<void> {
        return;
    }
}

export default MockFrontPanel;
