import { assert } from "../assert.js";
import { objectKeyByValue } from "../model/object/objectKeyByValue.js";

/**
 *
 * @enum {number}
 */
export const EdgeDirectionType = {
    Undirected: 3,
    Forward: 1,
    Backward: 2
};

/**
 * @template N
 */
export class Edge {
    /**
     * @template N
     * @param {N} a
     * @param {N} b
     */
    constructor(a, b) {
        assert.defined(a, 'a');
        assert.defined(b, 'b');

        /**
         *
         * @type {N}
         */
        this.first = a;

        /**
         *
         * @type {N}
         */
        this.second = b;

        /**
         * @type {EdgeDirectionType}
         */
        this.direction = EdgeDirectionType.Undirected;
    }

    /**
     *
     * @param {N} node
     * @return {boolean}
     */
    contains(node) {
        return this.first === node || this.second === node;
    }

    /**
     *
     * @param {N} source
     * @param {N} target
     * @returns {boolean} True  iff the edge contains both source and target and allows transition from source to target
     */
    validateTransition(source, target) {
        const a = this.first;
        const b = this.second;

        return (a === source && b === target && this.traversableForward())
            || (b === source && a === target && this.traversableBackward())
            ;
    }

    /**
     * Provided one of the associated nodes - returns the other one, if supplied node is not connecting the edge - returns first node (unintended behaviour)
     * @param {N} node
     * @returns {N}
     */
    other(node) {
        return (node === this.first) ? this.second : this.first;
    }

    /**
     *
     * @returns {boolean}
     */
    traversableForward() {
        return (this.direction & EdgeDirectionType.Forward) !== 0;
    }

    /**
     *
     * @returns {boolean}
     */
    traversableBackward() {
        return (this.direction & EdgeDirectionType.Backward) !== 0;
    }

    /**
     * Checks direction of the edge, if the edge is directed towards supplied node - returns true, false otherwise
     * @param {N} node
     * @returns {boolean}
     */
    isDirectedTowards(node) {
        return (this.first === node && this.direction === EdgeDirectionType.Backward)
            || (this.second === node && this.direction === EdgeDirectionType.Forward);
    }

    /**
     * Checks direction of the edge, if the edge is directed away from the supplied node - returns true, false otherwise
     * @param {N} node
     * @returns {boolean}
     */
    isDirectedAwayFrom(node) {
        return (this.first === node && this.direction === EdgeDirectionType.Forward)
            || (this.second === node && this.direction === EdgeDirectionType.Backward)
    }

    /**
     *
     * @returns {N[]}
     */
    get nodes() {
        return [this.first, this.second];
    }

    toString() {
        return `Edge{ first=${this.first}, second=${this.second}, direction=${objectKeyByValue(EdgeDirectionType, this.direction)} }`
    }

}

/**
 *
 * @type {boolean}
 */
Edge.prototype.isEdge = true;
