const ComputerParameterMode = require("./ComputerParameterMode");

/**
 * The stack, or memory, for the Intcode Computer
 *
 * @author Apis Necros
 */
module.exports = class Stack {
    constructor(stack) {
        this.pointer = 0;
        this._stack = stack;
    }

    /**
     * Move the stack's pointer to the right by 1
     * @returns {void}
     */
    Next() {
        this.pointer++;
        return this;
    }

    /**
     * Move the stack's pointer to the left by 1
     * @returns {void}
     */
    Prev() {
        this.pointer--;
    }

    /**
     * Get the value from the stack by the pointer's current position
     *
     * @param {number} [parameterMode=0] The Parameter Mode to use to retrieve the value
     * @returns {number} The value at the current pointer position on the stack
     */
    Get(parameterMode = ComputerParameterMode.POSITION_MODE) {
        let value = this._stack[this.pointer];
        if (parameterMode == ComputerParameterMode.POSITION_MODE) {
            value = this._stack[value];
        }

        return value;
    }

    /**
     * Get a value at a given index on the stack
     *
     * @param {number} index The index of the value to retrieve
     * @param {number} [parameterMode=0] The Parameter Mode to use to retrieve the value
     * @returns {number} The value at the given index, or 0 if the index hasn't been defined yet
     */
    GetAtIndex(index, parameterMode = ComputerParameterMode.POSITION_MODE) {
        if (index == null || Number.isNaN(index) || !Number.isInteger(index)) {
            throw new TypeError("index must be an integer");
        }

        try {
            let value = this._stack[index];
            if (parameterMode == ComputerParameterMode.POSITION_MODE) {
                value = this._stack[value];
            }
            return value;
        }
        catch (e) {
            return 0;
        }
    }

    /**
     * Use the value at the current pointer to get a value at another position
     *
     * This is essentially shorthand for `stack.Get(stack.Get())`
     *
     * @returns {number} The value at the index given by the pointer's current position
     */
    GetUsingStackValue() {
        return this.Get(this._stack[this.pointer]);
    }

    /**
     * Push a new value onto the end of the stack
     *
     * @param {number} value The value to add
     * @returns {void}
     */
    Push(value) {
        this._stack[++this.pointer] = value;
    }

    /**
     * Pop a value off the end of the stack
     *
     * @returns {number}
     * @returns {void}
     */
    Pop() {
        return this._stack.pop();
    }

    /**
     * Overwrite the value at a given index with a new value
     *
     * The index need not be defined
     *
     * @param {number} index The index on the stack to write a value to
     * @param {number} value The value to write to that position
     * @returns {void}
     */
    Put(index, value) {
        this._stack[index] = value;
    }

    /**
     * Sets the pointer to a new address in memory
     *
     * If the address is out of the upper bounds of the current memory stack,
     * the stack will be expanded, filling with 0's as needed.
     *
     * @param {number} newAddress The new pointer address
     * @returns {void}
     */
    SetPointerAddress(newAddress) {
        if (newAddress > this._stack.length) {
            // Expand the stack if needed
            const oldLength = this._stack.length;
            this._stack[newAddress] = 0;
            this._stack.fill(0, oldLength, newAddress);
        }

        this.pointer = newAddress;
    }

    /**
     * Return the whole stack
     *
     * @returns {number[]} The current stack
     */
    Dump() {
        return this._stack;
    }
};