import { Color } from '../../Color';
import { BoundingBox } from '../BoundingBox';
/**
 * QuadTree spatial data structure. Useful for quickly retrieving all objects that might
 * be in a specific location.
 */
export class QuadTree {
    constructor(bounds, options) {
        this.bounds = bounds;
        this.options = options;
        this._defaultOptions = {
            maxDepth: 10,
            capacity: 10,
            level: 0
        };
        this.items = [];
        this._isDivided = false;
        this.topLeft = null;
        this.topRight = null;
        this.bottomLeft = null;
        this.bottomRight = null;
        this.options = { ...this._defaultOptions, ...options };
        this.halfWidth = bounds.width / 2;
        this.halfHeight = bounds.height / 2;
    }
    /**
     * Splits the quad tree one level deeper
     */
    _split() {
        this._isDivided = true;
        const newLevelOptions = {
            maxDepth: this.options.maxDepth,
            capacity: this.options.capacity,
            level: this.options.level + 1
        };
        this.topLeft = new QuadTree(new BoundingBox({
            left: this.bounds.left,
            top: this.bounds.top,
            right: this.bounds.left + this.halfWidth,
            bottom: this.bounds.top + this.halfHeight
        }), newLevelOptions);
        this.topRight = new QuadTree(new BoundingBox({
            left: this.bounds.left + this.halfWidth,
            top: this.bounds.top,
            right: this.bounds.right,
            bottom: this.bounds.top + this.halfHeight
        }), newLevelOptions);
        this.bottomLeft = new QuadTree(new BoundingBox({
            left: this.bounds.left,
            top: this.bounds.top + this.halfHeight,
            right: this.bounds.left + this.halfWidth,
            bottom: this.bounds.bottom
        }), newLevelOptions);
        this.bottomRight = new QuadTree(new BoundingBox({
            left: this.bounds.left + this.halfWidth,
            top: this.bounds.top + this.halfHeight,
            right: this.bounds.right,
            bottom: this.bounds.bottom
        }), newLevelOptions);
    }
    _insertIntoSubNodes(item) {
        var _a, _b, _c, _d;
        if ((_a = this.topLeft) === null || _a === void 0 ? void 0 : _a.bounds.overlaps(item.bounds)) {
            this.topLeft.insert(item);
        }
        if ((_b = this.topRight) === null || _b === void 0 ? void 0 : _b.bounds.overlaps(item.bounds)) {
            this.topRight.insert(item);
        }
        if ((_c = this.bottomLeft) === null || _c === void 0 ? void 0 : _c.bounds.overlaps(item.bounds)) {
            this.bottomLeft.insert(item);
        }
        if ((_d = this.bottomRight) === null || _d === void 0 ? void 0 : _d.bounds.overlaps(item.bounds)) {
            this.bottomRight.insert(item);
        }
    }
    /**
     * Insert an item to be tracked in the QuadTree
     * @param item
     */
    insert(item) {
        // add to subnodes if it matches
        if (this._isDivided) {
            this._insertIntoSubNodes(item);
            return;
        }
        // leaf case
        this.items.push(item);
        // capacity
        if (this.items.length > this.options.capacity && this.options.level < this.options.maxDepth) {
            if (!this._isDivided) {
                this._split();
            }
            // divide this level's items into it's subnodes
            for (const item of this.items) {
                this._insertIntoSubNodes(item);
            }
            // clear this level
            this.items.length = 0;
        }
    }
    /**
     * Remove a tracked item in the QuadTree
     * @param item
     */
    remove(item) {
        var _a, _b, _c, _d;
        if (!this.bounds.overlaps(item.bounds)) {
            return;
        }
        if (!this._isDivided) {
            const index = this.items.indexOf(item);
            if (index > -1) {
                this.items.splice(index, 1);
            }
            return;
        }
        if ((_a = this.topLeft) === null || _a === void 0 ? void 0 : _a.bounds.overlaps(item.bounds)) {
            this.topLeft.remove(item);
        }
        if ((_b = this.topRight) === null || _b === void 0 ? void 0 : _b.bounds.overlaps(item.bounds)) {
            this.topRight.remove(item);
        }
        if ((_c = this.bottomLeft) === null || _c === void 0 ? void 0 : _c.bounds.overlaps(item.bounds)) {
            this.bottomLeft.remove(item);
        }
        if ((_d = this.bottomRight) === null || _d === void 0 ? void 0 : _d.bounds.overlaps(item.bounds)) {
            this.bottomRight.remove(item);
        }
    }
    /**
     * Query the structure for all objects that intersect the bounding box
     * @param boundingBox
     * @returns items
     */
    query(boundingBox) {
        let results = this.items;
        if (this._isDivided) {
            if (this.topLeft.bounds.overlaps(boundingBox)) {
                results = results.concat(this.topLeft.query(boundingBox));
            }
            if (this.topRight.bounds.overlaps(boundingBox)) {
                results = results.concat(this.topRight.query(boundingBox));
            }
            if (this.bottomLeft.bounds.overlaps(boundingBox)) {
                results = results.concat(this.bottomLeft.query(boundingBox));
            }
            if (this.bottomRight.bounds.overlaps(boundingBox)) {
                results = results.concat(this.bottomRight.query(boundingBox));
            }
        }
        results = results.filter((item, index) => {
            return results.indexOf(item) >= index;
        });
        return results;
    }
    clear() {
        this.items = [];
        this._isDivided = false;
        this.topLeft = null;
        this.topRight = null;
        this.bottomLeft = null;
        this.bottomRight = null;
    }
    getAllItems() {
        let results = this.items;
        if (this._isDivided) {
            results = results.concat(this.topLeft.getAllItems());
            results = results.concat(this.topRight.getAllItems());
            results = results.concat(this.bottomLeft.getAllItems());
            results = results.concat(this.bottomRight.getAllItems());
        }
        results = results.filter((item, index) => {
            return results.indexOf(item) >= index;
        });
        return results;
    }
    getTreeDepth() {
        if (!this._isDivided) {
            return 0;
        }
        return (1 +
            Math.max(this.topLeft.getTreeDepth(), this.topRight.getTreeDepth(), this.bottomLeft.getTreeDepth(), this.bottomRight.getTreeDepth()));
    }
    debug(ctx) {
        this.bounds.draw(ctx, Color.Yellow);
        if (this._isDivided) {
            this.topLeft.bounds.draw(ctx, Color.Yellow);
            this.topRight.bounds.draw(ctx, Color.Yellow);
            this.bottomLeft.bounds.draw(ctx, Color.Yellow);
            this.bottomRight.bounds.draw(ctx, Color.Yellow);
        }
    }
}
//# sourceMappingURL=QuadTree.js.map