import { assert } from "../../core/assert.js";
import { qt_collect_by_circle } from "../../core/geom/2d/quad-tree/qt_collect_by_circle.js";
import { QuadTreeNode } from "../../core/geom/2d/quad-tree/QuadTreeNode.js";
import { max2 } from "../../core/math/max2.js";
import { OffsetScaleTransform2D } from "../../engine/ecs/terrain/ecs/OffsetScaleTransform2D.js";

/**
 *
 * @type {QuadTreeDatum<MarkerNode>[]}
 */
const marker_collection = [];

export class GridData {

    width = 0;
    height = 0;

    /**
     * @readonly
     * @type {OffsetScaleTransform2D}
     */
    transform = new OffsetScaleTransform2D();

    /**
     * @readonly
     * @type {QuadTreeNode<MarkerNode>}
     */
    markers = new QuadTreeNode();

    /**
     * Discrete data layers
     * @readonly
     * @type {GridDataLayer[]}
     */
    layers = [];


    /**
     *
     * @param {GridDataLayer} layer
     */
    addLayer(layer) {
        assert.equal(layer.isGridDataLayer, true, 'layer.GridDataLayer !== true');


        const existing = this.getLayerById(layer.id);


        if (existing === layer) {
            //layer is already attached
            return;
        }

        if (existing !== undefined) {
            throw new Error(`Layer with ID '${layer.id}' already exists. ID must be unique`);
        }

        layer.resize(this.width, this.height);

        this.layers.push(layer);
    }

    /**
     *
     * @param {string} id
     * @returns {GridDataLayer|undefined}
     */
    getLayerById(id) {
        assert.isString(id, 'id');

        const layers = this.layers;

        const n = layers.length;

        for (let i = 0; i < n; i++) {
            const layer = layers[i];

            if (layer.id === id) {
                return layer;
            }
        }

        return undefined;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} radius
     * @param {MarkerNodeMatcher} matcher
     * @returns {boolean}
     */
    containsMarkerInCircle(x, y, radius, matcher) {
        return this.countMarkerInCircle(x, y, radius, matcher) > 0;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} radius
     * @param {MarkerNodeMatcher} matcher
     * @returns {number}
     */
    countMarkerInCircle(x, y, radius, matcher) {
        const match_count = qt_collect_by_circle(marker_collection, 0, this.markers, x, y, radius);

        let result = 0;

        for (let i = 0; i < match_count; i++) {
            const marker_datum = marker_collection[i];

            /**
             *
             * @type {MarkerNode|null}
             */
            const marker = marker_datum.data;

            const distance_limit = radius + marker.size;

            const distance_limit_sqr = distance_limit * distance_limit;

            // check refined containment
            const position = marker.position;

            const dx = x - position.x;
            const dy = y - position.y;

            const distance_sqr = dx * dx + dy * dy;

            if (distance_sqr > distance_limit_sqr) {
                continue;
            }

            if (matcher.match(marker)) {
                result++;
            }
        }

        return result;
    }

    /**
     *
     * @param tileSize
     */
    computeScale(tileSize) {
        this.transform.scale_x = tileSize;
        this.transform.scale_y = tileSize;

        this.transform.offset_x = tileSize / 2;
        this.transform.offset_y = tileSize / 2;
    }

    /**
     *
     * @param {MarkerNode} node
     */
    addMarker(node) {
        const p = node.position;

        const x = p.x;
        const y = p.y;

        const r = node.size;

        this.markers.add(node, x - r, y - r, x + r, y + r);
    }

    /**
     *
     * @param {number} width
     * @param {number} height
     */
    resize(width, height) {
        assert.isNumber(width, 'width');
        assert.isNumber(height, 'height');

        assert.isNonNegativeInteger(width, 'width');
        assert.isNonNegativeInteger(height, 'height');

        assert.isFinite(width, 'width');
        assert.isFinite(height, 'width');

        if (width === this.width && height === this.height) {
            //no need, already the right size
            return;
        }

        this.width = width;
        this.height = height;

        const layers = this.layers;

        const n = layers.length;

        for (let i = 0; i < n; i++) {
            const layer = layers[i];

            layer.resize(width, height);
        }

        // resize the quad tree pre-emptively, to avoid expensive re-balancing operations later on
        this.markers.resize(
            -max2(1, width * 0.5),
            -max2(1, height * 0.5),
            max2(width + 1, width * 1.5),
            max2(height + 1, height * 1.5)
        );
    }

}


/**
 * @readonly
 * @type {boolean}
 */
GridData.prototype.isGridData = true;
