import Physics from "./Physics";
import Collider, { CircleCollider, RectCollider, ColliderType } from "./Collider";
import DebugMgr from "../Mgr/DebugMgr";

export default class PhycicsSystem {
    private static _instance: PhycicsSystem = null;
    public static get instance(): PhycicsSystem {
        if (!this._instance) {
            this._instance = new PhycicsSystem();
        }

        return this._instance;
    }

    public static init() {
        egret.MainContext.instance.stage.addEventListener(egret.Event.ENTER_FRAME, PhycicsSystem.instance.onUpdate, PhycicsSystem.instance);
    }

    public pause: boolean = false;


    private phycicsList: Physics[] = [];

    private onUpdate() {
        if (this.pause) return;
        
        for (let i of PhycicsSystem.instance.phycicsList) {
            i.onFixedUpdate();
        }

        this.detectCollision();
    }

    public add(phycics: Physics) {
        this.phycicsList.push(phycics);
    }
    public remove(phycics: Physics) {
        this.phycicsList = this.phycicsList.filter((v) => v != phycics);
    }

    private colliderList: Array<Collider> = new Array<Collider>();
    private collisions: Array<{ collider1: number, collider2: number }> = [];

    /**
     * 判断两个碰撞器之前是否发生过碰撞
     * @returns 未找到返回-1
     */
    private getCollisionIndex(collider1: number, collider2: number): number {
        for (let i = 0; i < this.collisions.length; i++) {
            if ((this.collisions[i].collider1 == collider1 && this.collisions[i].collider2 == collider2) || (this.collisions[i].collider1 == collider2 && this.collisions[i].collider2 == collider1)) {
                return i;
            }
        }
        //未碰撞
        return -1;
    }

    public addCollider(collider: Collider) {
        this.colliderList.push(collider);
    }

    public removeCollider(collider: Collider) {
        DebugMgr.instance.clearShape(collider.owner.hashCode.toString());
        this.colliderList = this.colliderList.filter(v => v != collider);
    }

    /**检测碰撞 */
    public detectCollision() {
        //查找所有碰撞
        for (let i = 0; i <= this.colliderList.length - 1; i++) {
            this.drawCollider(this.colliderList[i]);
            for (let j = i + 1; j <= this.colliderList.length - 1; j++) {
                this.detectTraverse(this.colliderList[i], this.colliderList[j]);
            }
        }
    }

    private drawCollider(collider: Collider) {
        switch (collider.type) {
            case ColliderType.Circle:
                let circleCollider = collider as CircleCollider;
                let worldCenter = collider.owner.localToGlobal(circleCollider.center.x, circleCollider.center.y);
                DebugMgr.instance.updateCircle(collider.owner.hashCode.toString(), worldCenter.x, worldCenter.y, circleCollider.radius);
                break;
            case ColliderType.Rect:
                let rectCollider = collider as RectCollider;
                let worldPos = rectCollider.owner.localToGlobal(rectCollider.rect.topLeft.x, rectCollider.rect.topLeft.y);
                DebugMgr.instance.updateRect(rectCollider.owner.hashCode.toString(), new egret.Rectangle(worldPos.x, worldPos.y, rectCollider.rect.width, rectCollider.rect.height));
                break;

        }
    }

    private detectTraverse(collider1: Collider, collider2: Collider) {
        if (collider1.group == collider2.group) return;

        let collisionIndex = this.getCollisionIndex(collider1.owner.hashCode, collider2.owner.hashCode);

        let isCollided = false;
        let detectFunc = getDetectFunc(collider1, collider2);
        if (!detectFunc) {
            detectFunc = getDetectFunc(collider2, collider1)
            if (!detectFunc) {
                return;
            } else {
                isCollided = detectFunc(collider2, collider1);
            }
        } else {
            isCollided = detectFunc(collider1, collider2);
        }

        if (isCollided) {
            if (collisionIndex >= 0) { //之前就发生了碰撞
                collider1.owner.onCollisionStay(collider2);
                collider2.owner.onCollisionStay(collider1);
            } else { //之前没有碰撞
                //添加到索引表
                this.collisions.push({
                    collider1: collider1.owner.hashCode,
                    collider2: collider2.owner.hashCode
                });
                collider1.owner.onCollisionEnter(collider2);
                collider2.owner.onCollisionEnter(collider1);
            }
        } else { //未发生碰撞
            //碰撞退出
            if (collisionIndex > -1) { //之前有发生碰撞
                //在碰撞列表中删除这一对碰撞
                this.collisions.splice(collisionIndex, 1);
                collider1.owner.onCollisionExit(collider2);
                collider2.owner.onCollisionExit(collider1);
            }
        }
    }
}

function getDetectFunc(collider1: Collider, collider2: Collider): (collider1: Collider, collider2: Collider) => boolean {
    if (collider1.type == ColliderType.Circle) {
        if (collider2.type == ColliderType.Circle) {
            return circleToCircle;
        } else if (collider2.type == ColliderType.Rect) {
            return circleToRect;
        }
    }
    return null;
}

function getWorldPoint(collider: Collider, point: egret.Point, out: egret.Point) {
    return collider.owner.localToGlobal(point.x, point.y, out);
}

function circleToCircle(collider1: CircleCollider, collider2: CircleCollider): boolean {
    let worldCenter1 = new egret.Point();
    let worldCenter2 = new egret.Point();
    getWorldPoint(collider2, collider2.center, worldCenter1);
    getWorldPoint(collider1, collider1.center, worldCenter2);
    return egret.Point.distance(worldCenter1, worldCenter2) < (collider1.radius + collider2.radius);
}

function circleToRect(circleCollider: CircleCollider, rectCollider: RectCollider) {
    let circleCenter = new egret.Point();
    getWorldPoint(circleCollider, circleCollider.center, circleCenter);
    let rectPos = new egret.Point();
    getWorldPoint(rectCollider, rectCollider.rect.topLeft, rectPos);

    let closestPoint = new egret.Point();
    if (circleCenter.y < rectPos.y) {
        closestPoint.y = rectPos.y;
    } else if (circleCenter.y > rectPos.y + rectCollider.rect.height) {
        closestPoint.y = rectPos.y + rectCollider.rect.height;
    } else {
        closestPoint.y = circleCenter.y;
    }

    if (circleCenter.x < rectPos.x) {
        closestPoint.x = rectPos.x;
    } else if (circleCenter.x > rectPos.x + rectCollider.rect.width) {
        closestPoint.x = rectPos.x + rectCollider.rect.width;
    } else {
        closestPoint.x = circleCenter.x;
    }

    return egret.Point.distance(closestPoint, circleCenter) < circleCollider.radius;
}
