import { _decorator, ccenum, Component, Enum, Eventify, PhysicsGroup, PhysicsSystem2D, v2, Vec2 } from "cc";

const { property, ccclass } = _decorator;


export enum EShapeType {
    Rect = 0,
    Circle = 1,
}

const ShapeType = Enum(EShapeType);

enum QuadTreeNodeEvent {
    COLLISION = "collision",
}

enum QuadTreeNodeType {
    Moveable = 0,
    Static = 1,
}

ccenum(QuadTreeNodeType);

@ccclass("QuadTreeNode")
export class QuadTreeNode extends Eventify(Component) {

    static EventType = QuadTreeNodeEvent;

    static staticCaches: QuadTreeNode[] = [];
    static caches: QuadTreeNode[] = [];

    @property({ type: QuadTreeNodeType })
    type = QuadTreeNodeType.Moveable;

    @property({ type: PhysicsGroup })
    group = PhysicsGroup.DEFAULT;

    @property({ type: ShapeType, displayName: "Shape" })
    shapeType = ShapeType.Rect; //for bound culling

    @property
    tag: number = 0;

    @property({ type: Vec2 })
    offset: Vec2 = v2(0, 0);

    @property({
        visible() {
            return this.shapeType == EShapeType.Rect;
        }
    })
    width: number = 0;

    @property({
        visible() {
            return this.shapeType == EShapeType.Rect;
        }
    })
    height: number = 0;

    @property({
        visible() {
            return this.shapeType == EShapeType.Circle;
        }
    })
    radius: number = 0;

    get worldScaleX(): number {
        return this.node.worldScale.x;
    }

    get worldScaleY(): number {
        return this.node.worldScale.y;
    }

    get x(): number {
        return this.node.worldPosition.x + this.offset.x * this.worldScaleX;
    }

    get y(): number {
        return this.node.worldPosition.y + this.offset.y * this.worldScaleY; // for 2d
    }

    // get z(): number {
    //     return this.node.position.z;
    // }

    mask: number = 0;


    protected onLoad() {
        this.mask = PhysicsSystem2D.instance.collisionMatrix[this.group];

        if (this.type == QuadTreeNodeType.Static) {
            QuadTreeNode.staticCaches.push(this);
        } else {
            QuadTreeNode.caches.push(this);
        }
    }

    onDestroy() {
        if (this.type == QuadTreeNodeType.Static) {
            QuadTreeNode.staticCaches.splice(QuadTreeNode.staticCaches.indexOf(this), 1);
        } else {
            QuadTreeNode.caches.splice(QuadTreeNode.caches.indexOf(this), 1);
        }
    }

    intersects(other: QuadTreeNode): boolean {
        if (this == other) return false;

        const { group, shapeType } = this;

        if (!(group & other.mask)) return false;

        if (shapeType == EShapeType.Rect) {
            return this.intersectsRect(other);
        } else if (shapeType == EShapeType.Circle) {
            return this.intersectsCircle(other);
        }

    }

    intersectsCircle(other: QuadTreeNode): boolean {
        if (this.shapeType == EShapeType.Rect) {
            return this.intersectsRectAndCircle(this, other);
        }

        const totalRadius = this.radius * this.worldScaleX + other.radius * other.worldScaleX;
        const totalRadiusSquared = totalRadius * totalRadius;

        return Vec2.squaredDistance(other, this) <= totalRadiusSquared;
    }

    intersectsRect(other: QuadTreeNode): boolean {
        if (this.shapeType == EShapeType.Circle) {
            this.intersectsRectAndCircle(other, this);
        } else {

            const {
                x: sX, y: sY,
                worldScaleX: sSx, worldScaleY: sSy,
                width: sW, height: sH
            } = this;

            const {
                x: oX, y: oY,
                worldScaleX: oSx, worldScaleY: oSy,
                width: oW, height: oH
            } = other;

            const maxAx = sX + sW * sSx;
            const maxAy = sY + sH * sSy;
            const maxBx = oX + oW * oSx;
            const maxBy = oY + oH * oSy;
            return !(maxAx <= oX || maxBx <= sX || maxAy <= oY || maxBy <= sY);
        }
        return false;
    }

    intersectsRectAndCircle(rect: QuadTreeNode, circle: QuadTreeNode): boolean {

        const cx = circle.x;
        const cy = circle.y;

        const rx = rect.x;
        const ry = rect.y;
        const rw = rect.width * rect.worldScaleX;
        const rh = rect.height * rect.worldScaleX;

        // temporary variables to set edges for testing
        let testX = cx;
        let testY = cy;

        // which edge is closest?
        if (cx < rx) testX = rx;      // test left edge
        else if (cx > rx + rw) testX = rx + rw;   // right edge
        if (cy < ry) testY = ry;      // top edge
        else if (cy > ry + rh) testY = ry + rh;   // bottom edge

        // get distance from closest edges
        const distX = cx - testX;
        const distY = cy - testY;
        const disSquared = (distX * distX) + (distY * distY);

        // if the distance is less than the radius, collision!
        const totalRadius = circle.radius * circle.worldScaleX;
        return disSquared <= totalRadius * totalRadius;

    }

}