import Logic from '../Logic';

import Box from './Box';

/** @module editorElements.Gate */

/**
 * Gate is a box that processes the states of its input connectors and returns the result in its output connectors.
 * @extends Box
 */
export default class Gate extends Box {
    /**
     * @param {App} appInstance  instance of [App](./module-App.html)
     * @param {string} name       name of the gate (and, not, xor...)
     */
    constructor(appInstance, name) {
        const width = 9;
        const height = 4;

        super(appInstance, name, 'gate', width, height);

        // ADD CONNECTORS

        let specialNodes = [];

        // output
        this.addConnector(width, height / 2, false);

        // block the output connector
        specialNodes.push({
            x: width,
            y: height / 2
        });

        if (this.name === 'not' || this.name === 'repeater') {
            // input
            this.addConnector(0, height / 2, true);
            // block the input connector
            specialNodes.push({
                x: 0,
                y: height / 2
            });
        } else {
            // input
            this.addConnector(0, height / 4, true);
            this.addConnector(0, height / (4 / 3), true);

            // block the input connectors
            specialNodes.push({
                x: 0,
                y: height / 4
            });
            specialNodes.push({
                x: 0,
                y: height / (4 / 3)
            });

            // add one blocked node between the inputs (for better looking wiring)
            specialNodes.push({
                x: 0,
                y: height / 2
            });
        }

        this.generateBlockNodes(...specialNodes);

        this.refreshState();
    }

    /**
     * array of valid gate names
     * @type {Set}
     */
    static get validGates() {
        // return new Set(["not", "and", "or", "nand", "nor", "xor", "xnor", "repeater"]);
        return new Set(['not', 'and', 'or', 'nand', 'nor', 'xor', 'xnor']);
    }

    generateBlockNodes(...specialNodes) {
        if (specialNodes !== undefined) {
            super.generateBlockNodes(0, 1, 0, 1, ...specialNodes);
        } else {
            super.generateBlockNodes(0, 1, 0, 1);
        }
    }

    /**
     * proccess the input connector states and reflect them in the output connector states according
     * to the logic corresponding to this gate's name
     */
    refreshState() {
        // map gate names to their logic functions
        const stateMap = {
            and: () => Logic.and(this.connectors[1].state, this.connectors[2].state),
            nand: () => Logic.nand(this.connectors[1].state, this.connectors[2].state),
            nor: () => Logic.nor(this.connectors[1].state, this.connectors[2].state),
            not: () => Logic.not(this.connectors[1].state),
            or: () => Logic.or(this.connectors[1].state, this.connectors[2].state),
            xnor: () => Logic.xnor(this.connectors[1].state, this.connectors[2].state),
            xor: () => Logic.xor(this.connectors[1].state, this.connectors[2].state),
            repeater: () => this.connectors[1].state
        };

        let state = Logic.state.unknown;

        if (stateMap[this.name]) {
            state = stateMap[this.name]();
        }

        // notify the simulator about this change
        this.appInstance.simulation.notifyChange(this.connectors[0].id, state);
    }
}