    /** @module Logic */
    /**
     * definitions of logic states and basic logic functions used in the simulation
     */
    export default class Logic {
        /**
         * Enum for logic states.
         *
         * States:
         * - `off`
         * - `on`
         * - `unknown`
         * - `oscillating`
         * @type {Number}
         */
        static get state() {
            return {
                off: 0,
                on: 1,
                unknown: 2,
                oscillating: 3
            };
        }

        /**
         * list of all states that can be used in the simulation
         *
         * This getter iterates over Logic.state and returns an array containing all values of Logic.state's members
         * @type {Array}
         */
        static get stateList() {
            let states = [];

            // iterate over all defined states and add their values to the states array
            Object.keys(Logic.state).forEach(key => {
                states.push(Logic.state[key]);
            });

            return states;
        }

        /**
         * Logic AND
         * @param  {Logic.state} a first input state
         * @param  {Logic.state} b second input state
         * @return {Logic.state}   output state
         */
        static and(a, b) {
            return Logic.runSymmetricRules(a, b, [
                [Logic.state.on, Logic.state.on, Logic.state.on],
                [Logic.state.on, Logic.state.off, Logic.state.off],
                [Logic.state.on, Logic.state.unknown, Logic.state.unknown],
                [Logic.state.on, Logic.state.oscillating, Logic.state.oscillating],

                [Logic.state.off, Logic.state.off, Logic.state.off],
                [Logic.state.off, Logic.state.unknown, Logic.state.off],
                [Logic.state.off, Logic.state.oscillating, Logic.state.off],

                [Logic.state.unknown, Logic.state.unknown, Logic.state.unknown],
                [Logic.state.unknown, Logic.state.oscillating, Logic.state.unknown],

                [Logic.state.oscillating, Logic.state.oscillating, Logic.state.oscillating]
            ]);
        }
        /**
         * Logic NAND
         * @param  {Logic.state} a first input state
         * @param  {Logic.state} b second input state
         * @return {Logic.state}   output state
         */
        static nand(a, b) {
            return Logic.not(Logic.and(a, b));
        }

        /**
         * Logic NOR
         * @param  {Logic.state} a first input state
         * @param  {Logic.state} b second input state
         * @return {Logic.state}   output state
         */
        static nor(a, b) {
            return Logic.not(Logic.or(a, b));
        }

        /**
         * Logic NOT
         * @param  {Logic.state} a first input state
         * @return {Logic.state}   output state
         */
        static not(a) {
            if (a === Logic.state.on) {
                return Logic.state.off;
            } else if (a === Logic.state.off) {
                return Logic.state.on;
            } else {
                return a;
            }
        }

        /**
         * Logic OR
         * @param  {Logic.state} a first input state
         * @param  {Logic.state} b second input state
         * @return {Logic.state}   output state
         */
        static or(a, b) {
            return Logic.runSymmetricRules(a, b, [
                [Logic.state.on, Logic.state.on, Logic.state.on],
                [Logic.state.on, Logic.state.off, Logic.state.on],
                [Logic.state.on, Logic.state.unknown, Logic.state.on],
                [Logic.state.on, Logic.state.oscillating, Logic.state.on],

                [Logic.state.off, Logic.state.off, Logic.state.off],
                [Logic.state.off, Logic.state.unknown, Logic.state.unknown],
                [Logic.state.off, Logic.state.oscillating, Logic.state.oscillating],

                [Logic.state.unknown, Logic.state.unknown, Logic.state.unknown],
                [Logic.state.unknown, Logic.state.oscillating, Logic.state.unknown],

                [Logic.state.oscillating, Logic.state.oscillating, Logic.state.oscillating]
            ]);
        }

        /**
         * Logic XNOR
         * @param  {Logic.state} a first input state
         * @param  {Logic.state} b second input state
         * @return {Logic.state}   output state
         */
        static xnor(a, b) {
            return Logic.not(Logic.xor(a, b));
        }

        /**
         * Logic XOR
         * @param  {Logic.state} a first input state
         * @param  {Logic.state} b second input state
         * @return {Logic.state}   output state
         */
        static xor(a, b) {
            return Logic.runSymmetricRules(a, b, [
                [Logic.state.on, Logic.state.on, Logic.state.off],
                [Logic.state.on, Logic.state.off, Logic.state.on],
                [Logic.state.on, Logic.state.unknown, Logic.state.unknown],
                [Logic.state.on, Logic.state.oscillating, Logic.state.oscillating],

                [Logic.state.off, Logic.state.off, Logic.state.off],
                [Logic.state.off, Logic.state.unknown, Logic.state.unknown],
                [Logic.state.off, Logic.state.oscillating, Logic.state.oscillating],

                [Logic.state.unknown, Logic.state.unknown, Logic.state.unknown],
                [Logic.state.unknown, Logic.state.oscillating, Logic.state.unknown],

                [Logic.state.oscillating, Logic.state.oscillating, Logic.state.oscillating]
            ]);
        }

        /**
         * Finds the correct rule in the array of rules and returns the corresponding return value.
         * This function expects rules to be symmetric (so `a RULE b` should returns the same value as `b RULE a`),
         * which allows to cut down on the `rules` array quite a bit
         * @param  {Logic.state} a     first input state
         * @param  {Logic.state} b     second input state
         * @param  {Array} rules       Array of arrays. Each inner array represents a rule in the format [input1, input2, output].
         *                             The function finds an array, where `a === input1` and `b === input1` (or `a === input2` and `b === input1`)
         *                             and returns `output` from this array.
         * @return {Logic.state}       output state
         */
        static runSymmetricRules(a, b, rules) {
            // iterate through all the rules
            for (const rule of rules) {
                if ((rule[0] === a && rule[1] === b) || (rule[0] === b && rule[1] === a)) {
                    return rule[2];
                }
            }

            // if no rule matches, the output state is unknown
            return Logic.state.unknown;
        }
    }