import { Util } from '../..';
import { Pair } from '../Detection/Pair';
import { Projection } from '../../Math/projection';
import { Vector } from '../../Math/vector';
import { BoundingBox } from '../BoundingBox';
import { DynamicTree } from '../Detection/DynamicTree';
import { DynamicTreeCollisionProcessor } from '../Detection/DynamicTreeCollisionProcessor';
import { Collider } from './Collider';
import { DefaultPhysicsConfig } from '../PhysicsConfig';
export class CompositeCollider extends Collider {
    /**
     * Treat composite collider's member colliders as either separate colliders for the purposes of onCollisionStart/onCollision
     * or as a single collider together.
     *
     * This property can be overridden on individual [[CompositeColliders]].
     *
     * For composites without gaps or small groups of colliders, you probably want 'together'
     *
     * For composites with deliberate gaps, like a platforming level layout, you probably want 'separate'
     *
     * Default is 'together' if unset
     */
    set compositeStrategy(value) {
        this._compositeStrategy = value;
    }
    get compositeStrategy() {
        return this._compositeStrategy;
    }
    constructor(colliders) {
        super();
        this._collisionProcessor = new DynamicTreeCollisionProcessor(DefaultPhysicsConfig);
        this._dynamicAABBTree = new DynamicTree(DefaultPhysicsConfig.dynamicTree);
        this._colliders = [];
        for (const c of colliders) {
            this.addCollider(c);
        }
    }
    clearColliders() {
        this._colliders = [];
    }
    addCollider(collider) {
        let colliders;
        if (collider instanceof CompositeCollider) {
            colliders = collider.getColliders();
            colliders.forEach((c) => c.offset.addEqual(collider.offset));
        }
        else {
            colliders = [collider];
        }
        // Flatten composites
        for (const c of colliders) {
            c.events.pipe(this.events);
            c.composite = this;
            this._colliders.push(c);
            this._collisionProcessor.track(c);
            this._dynamicAABBTree.trackCollider(c);
        }
    }
    removeCollider(collider) {
        collider.events.pipe(this.events);
        collider.composite = null;
        Util.removeItemFromArray(collider, this._colliders);
        this._collisionProcessor.untrack(collider);
        this._dynamicAABBTree.untrackCollider(collider);
    }
    getColliders() {
        return this._colliders;
    }
    get worldPos() {
        var _a, _b;
        return ((_b = (_a = this._transform) === null || _a === void 0 ? void 0 : _a.pos) !== null && _b !== void 0 ? _b : Vector.Zero).add(this.offset);
    }
    get center() {
        var _a, _b;
        return ((_b = (_a = this._transform) === null || _a === void 0 ? void 0 : _a.pos) !== null && _b !== void 0 ? _b : Vector.Zero).add(this.offset);
    }
    get bounds() {
        var _a, _b;
        // TODO cache this
        const colliders = this.getColliders();
        const results = colliders.reduce((acc, collider) => acc.combine(collider.bounds), (_b = (_a = colliders[0]) === null || _a === void 0 ? void 0 : _a.bounds) !== null && _b !== void 0 ? _b : new BoundingBox().translate(this.worldPos));
        return results.translate(this.offset);
    }
    get localBounds() {
        var _a, _b;
        // TODO cache this
        const colliders = this.getColliders();
        const results = colliders.reduce((acc, collider) => acc.combine(collider.localBounds), (_b = (_a = colliders[0]) === null || _a === void 0 ? void 0 : _a.localBounds) !== null && _b !== void 0 ? _b : new BoundingBox());
        return results;
    }
    get axes() {
        // TODO cache this
        const colliders = this.getColliders();
        let axes = [];
        for (const collider of colliders) {
            axes = axes.concat(collider.axes);
        }
        return axes;
    }
    getFurthestPoint(direction) {
        const colliders = this.getColliders();
        const furthestPoints = [];
        for (const collider of colliders) {
            furthestPoints.push(collider.getFurthestPoint(direction));
        }
        // Pick best point from all colliders
        let bestPoint = furthestPoints[0];
        let maxDistance = -Number.MAX_VALUE;
        for (const point of furthestPoints) {
            const distance = point.dot(direction);
            if (distance > maxDistance) {
                bestPoint = point;
                maxDistance = distance;
            }
        }
        return bestPoint;
    }
    getInertia(mass) {
        const colliders = this.getColliders();
        let totalInertia = 0;
        for (const collider of colliders) {
            totalInertia += collider.getInertia(mass);
        }
        return totalInertia;
    }
    collide(other) {
        let otherColliders = [other];
        if (other instanceof CompositeCollider) {
            otherColliders = other.getColliders();
        }
        const pairs = [];
        for (const c of otherColliders) {
            this._dynamicAABBTree.query(c, (potentialCollider) => {
                pairs.push(new Pair(c, potentialCollider));
                return false;
            });
        }
        let contacts = [];
        for (const p of pairs) {
            contacts = contacts.concat(p.collide());
        }
        return contacts;
    }
    getClosestLineBetween(other) {
        const colliders = this.getColliders();
        const lines = [];
        if (other instanceof CompositeCollider) {
            const otherColliders = other.getColliders();
            for (const colliderA of colliders) {
                for (const colliderB of otherColliders) {
                    const maybeLine = colliderA.getClosestLineBetween(colliderB);
                    if (maybeLine) {
                        lines.push(maybeLine);
                    }
                }
            }
        }
        else {
            for (const collider of colliders) {
                const maybeLine = other.getClosestLineBetween(collider);
                if (maybeLine) {
                    lines.push(maybeLine);
                }
            }
        }
        if (lines.length) {
            let minLength = lines[0].getLength();
            let minLine = lines[0];
            for (const line of lines) {
                const length = line.getLength();
                if (length < minLength) {
                    minLength = length;
                    minLine = line;
                }
            }
            return minLine;
        }
        return null;
    }
    contains(point) {
        const colliders = this.getColliders();
        for (const collider of colliders) {
            if (collider.contains(point)) {
                return true;
            }
        }
        return false;
    }
    rayCast(ray, max) {
        const colliders = this.getColliders();
        const hits = [];
        for (const collider of colliders) {
            const hit = collider.rayCast(ray, max);
            if (hit) {
                hits.push(hit);
            }
        }
        if (hits.length) {
            let minHit = hits[0];
            let minDistance = minHit.point.dot(ray.dir);
            for (const hit of hits) {
                const distance = ray.dir.dot(hit.point);
                if (distance < minDistance) {
                    minHit = hit;
                    minDistance = distance;
                }
            }
            return minHit;
        }
        return null;
    }
    project(axis) {
        const colliders = this.getColliders();
        const projections = [];
        for (const collider of colliders) {
            const proj = collider.project(axis);
            if (proj) {
                projections.push(proj);
            }
        }
        // Merge all proj's on the same axis
        if (projections.length) {
            const newProjection = new Projection(projections[0].min, projections[0].max);
            for (const proj of projections) {
                newProjection.min = Math.min(proj.min, newProjection.min);
                newProjection.max = Math.max(proj.max, newProjection.max);
            }
            return newProjection;
        }
        return null;
    }
    update(transform) {
        if (transform) {
            const colliders = this.getColliders();
            for (const collider of colliders) {
                collider.owner = this.owner;
                collider.update(transform);
            }
        }
    }
    debug(ex, color, options) {
        const colliders = this.getColliders();
        ex.save();
        ex.translate(this.offset.x, this.offset.y);
        for (const collider of colliders) {
            collider.debug(ex, color, options);
        }
        ex.restore();
    }
    clone() {
        const result = new CompositeCollider(this._colliders.map((c) => c.clone()));
        result.offset = this.offset.clone();
        return result;
    }
}
//# sourceMappingURL=CompositeCollider.js.map