import { assert } from "../../../../core/assert.js";
import AABB2 from "../../../../core/geom/2d/aabb/AABB2.js";
import { QuadTreeNode } from "../../../../core/geom/2d/quad-tree/QuadTreeNode.js";
import { PathEndPointKind } from "./PathEndPointKind.js";
import { readMarkerNodeGroupId } from "./readMarkerNodeGroupId.js";

/**
 *
 * @type {QuadTreeDatum<RoadConnection>[]}
 */
const scratch_paths = [];

const bounds = new AABB2();

export class RoadConnectionNetwork {

    /**
     *
     * @type {QuadTreeNode<RoadConnection>}
     */
    index = new QuadTreeNode();

    /**
     *
     * @type {number}
     */
    width = -1;

    /**
     *
     * @type {Map<number, RoadConnection[]>}
     */
    groupLookup = new Map();

    /**
     *
     * @param {RoadConnection[]} result
     */
    requestAll(result) {
        this.index.getRawData(result);
    }

    /**
     *
     * @param {number} id
     * @returns {boolean}
     */
    isGroupConnected(id) {
        return this.groupLookup.has(id);
    }

    /**
     * A connection exists where source is of group A and target is of group B or vice-versa
     * @param {number} a Group ID
     * @param {number} b Group ID
     * @returns {boolean}
     */
    directGroupConnectionExists(a, b) {
        const connections = this.groupLookup.get(a);

        if (connections === undefined) {
            return false;
        }

        const n = connections.length;

        for (let i = 0; i < n; i++) {
            const road = connections[i];

            if (road.isAttachedToMarkerGroup(b)) {
                return true;
            }
        }

        return false;
    }

    /**
     *
     * @param {RoadConnection} path
     * @param {PathEndPoint} endpoint
     * @private
     */
    __registerPathEndpoint(path, endpoint) {

        if (endpoint.type === PathEndPointKind.Node) {
            const groupId = readMarkerNodeGroupId(endpoint.attachment);


            let connections = this.groupLookup.get(groupId);

            if (connections === undefined) {
                connections = [path];
                this.groupLookup.set(groupId, connections);
            } else {
                connections.push(path);
            }

        }
    }

    /**
     *
     * @param {RoadConnection} path
     */
    add(path) {
        path.computeBounds(bounds, this.width);

        this.index.add(path, bounds.x0, bounds.y0, bounds.x1, bounds.y1);

        this.__registerPathEndpoint(path, path.source);
        this.__registerPathEndpoint(path, path.target);

    }

    /**
     *
     * @param {RoadConnection[]} result
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    requestPathsAt(result, x, y) {

        assert.greaterThanOrEqual(this.width, 0, 'width must be greater or equal to 0');

        const q_x0 = x - 0.1;
        const q_y0 = y - 0.1;

        const q_x1 = x + 0.1;
        const q_y1 = y + 0.1;

        const intersection_count = this.index.requestDatumIntersectionsRectangle(
            scratch_paths,
            q_x0, q_y0,
            q_x1, q_y1
        );

        let result_count = 0;

        if (intersection_count > 0) {

            const index = x + y * this.width;

            for (let j = 0; j < intersection_count; j++) {

                const datum = scratch_paths[j];

                const path = datum.data;

                if (path.test(index)) {

                    result[result_count++] = path;

                }

            }
        }

        return result_count;
    }
}
