import Physics from "./Physics";
import Collider, { CircleCollider, RectCollider, ColliderType, ColliderGroup, PointCollider } from "./Collider";
import DebugMgr from "../Mgr/DebugMgr";
import { arrayRemove } from "../Global/GUtils";
import SpTimer from "./SpTimer";

let hashCode1: number = null;
let hashCode2: number = null;
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() {
        const ins = PhycicsSystem.instance;
        egret.MainContext.instance.stage.addEventListener(egret.Event.ENTER_FRAME, ins.onUpdate, ins);
        // egret.startTick(ins.onUpdate, ins);
    }

    private _enabled: boolean = false;
    public set enabled(v: boolean) {
        this._enabled = v;

        this.clear();
    }
    public get enabled() {
        return this._enabled;
    }

    public pause: boolean = false;


    private phycicsList: Physics[] = [];

    private clear() {
        for (let i = 0; i <= GroupMaxIndex; i++) {
            this.colliderList[i] = [];
        }
        this.phycicsList = [];
    }

    private readonly dtFactor = 60 / 1000;

    private onUpdate() {
        if (this.pause) return;

        const dt = SpTimer.deltaTime * this.dtFactor;
        for (let i of PhycicsSystem.instance.phycicsList) {
            if (this.enabled == false) return;
            i.onFixedUpdate(dt);
        }

        this.detectCollision();


        return false;
    }

    public add(phycics: Physics) {
        this.phycicsList.push(phycics);
    }
    public remove(phycics: Physics) {
        this.phycicsList = arrayRemove(this.phycicsList, phycics);
    }

    private colliderList: Collider[][] = [];
    private collisions: number[][] = [];

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

    public addCollider(collider: Collider) {
        if (typeof this.colliderList[collider.group] == "undefined") {
            this.colliderList[collider.group] = [];
        }
        this.colliderList[collider.group].push(collider);
    }

    public removeCollider(collider: Collider) {
        this.collisions = this.collisions.filter(e => {
            if (e[0] != collider.owner.hashCode && e[1] != collider.owner.hashCode) {
                return true;
            } else
                return false;
        });

        this.colliderList[collider.group] = arrayRemove(this.colliderList[collider.group], collider);
    }

    /**检测碰撞 */
    public detectCollision() {
        //查找所有碰撞
        let i = 0,
            j = 0,
            m = 0,
            n = 0,
            length = this.colliderList.length,
            group1: Collider[] = null,
            group2: Collider[] = null;
        for (i = 0; i <= length - 1; i++) {
            for (j = i + 1; j <= length - 1; j++) {
                if (CollisionMap[i] & 1 << j) {
                    if (this.enabled == false) return;
                    group1 = this.colliderList[i];
                    group2 = this.colliderList[j];
                    for (m = 0; m <= group1.length - 1; m++) {
                        for (n = 0; n <= group2.length - 1; n++) {
                            // if (this.enabled == false) return;
                            this.detectTraverse(group1[m], group2[n]);
                        }
                    }
                }
            }
        }
    }

    private drawCollider(collider: Collider) {
        switch (collider.type) {
            case ColliderType.Circle:
                let circleCollider = collider as CircleCollider;
                let worldCenter = collider.owner.localToGlobal(circleCollider.center[0], circleCollider.center[1]);
                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) {
        hashCode1 = collider1.owner.hashCode;
        hashCode2 = collider2.owner.hashCode;

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

function callDetectFunc(collider1: Collider, collider2: Collider): boolean {
    if (collider1.type == ColliderType.Circle) {
        if (collider2.type == ColliderType.Circle) {
            return circleToCircle(collider1 as CircleCollider, collider2 as CircleCollider);
        } else if (collider2.type == ColliderType.Rect) {
            return circleToRect(collider1 as CircleCollider, collider2 as RectCollider);
        } else if (collider2.type == ColliderType.Point) {
            return circleToPoint(collider1 as CircleCollider, collider2 as PointCollider)
        }
    } else if (collider1.type == ColliderType.Rect) {
        if (collider2.type == ColliderType.Circle) {
            return circleToRect(collider2 as CircleCollider, collider1 as RectCollider);
        } else if (collider2.type == ColliderType.Rect) {
            return rectToRect(collider1 as RectCollider, collider2 as RectCollider);
        } else if (collider2.type == ColliderType.Point) {
            return pointToRect(collider2 as PointCollider, collider1 as RectCollider);
        }
    } else if (collider1.type == ColliderType.Point) {
        if (collider2.type == ColliderType.Circle) {
            return circleToPoint(collider2 as CircleCollider, collider1 as PointCollider);
        } else if (collider2.type == ColliderType.Rect) {
            return pointToRect(collider1 as PointCollider, collider2 as RectCollider);
        }
    }
    return false;
}

let tempPoint1: number[] = [];
let tempPoint2: number[] = [];

function distance(p1: number[], p2: number[]) {
    return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
}

function circleToCircle(collider1: CircleCollider, collider2: CircleCollider): boolean {
    collider1.getWorldPosition(tempPoint1);
    collider2.getWorldPosition(tempPoint2);
    return distance(tempPoint1, tempPoint2) < (collider1.radius + collider2.radius);
}

function circleToRect(circleCollider: CircleCollider, rectCollider: RectCollider) {
    circleCollider.getWorldPosition(tempPoint1);
    rectCollider.getWorldPosition(tempPoint2);

    let closestPoint = new egret.Point();
    if (tempPoint1[1] < tempPoint2[1]) {
        closestPoint.y = tempPoint2[1];
    } else if (tempPoint1[1] > tempPoint2[1] + rectCollider.rect.height) {
        closestPoint.y = tempPoint2[1] + rectCollider.rect.height;
    } else {
        closestPoint.y = tempPoint1[1];
    }

    if (tempPoint1[0] < tempPoint2[0]) {
        closestPoint.x = tempPoint2[0];
    } else if (tempPoint1[0] > tempPoint2[0] + rectCollider.rect.width) {
        closestPoint.x = tempPoint2[0] + rectCollider.rect.width;
    } else {
        closestPoint.x = tempPoint1[0];
    }

    return distance([closestPoint.x, closestPoint.y], tempPoint1) < circleCollider.radius;
}

function rectToRect(collider1: RectCollider, collider2: RectCollider) {
    let rect1 = collider1.rect,
        rect2 = collider2.rect;
    collider1.getWorldPosition(tempPoint1);
    collider2.getWorldPosition(tempPoint2);

    let rectW1 = tempPoint1[0] + rect1.width,
        rectW2 = tempPoint2[0] + rect2.width,
        rectH1 = rect1.height + tempPoint1[1],
        rectH2 = tempPoint2[1] + rect2.height;
    if (tempPoint1[0] <= rectW2 &&
        rectW1 >= tempPoint2[0] &&
        tempPoint1[1] <= rectH2 &&
        rectH1 >= tempPoint2[1]) { //发生碰撞
        return true;
    } else
        return false;
}


function circleToPoint(circle: CircleCollider, point: PointCollider) {
    circle.getWorldPosition(tempPoint1);
    point.getWorldPosition(tempPoint2);

    if (distance(tempPoint1, tempPoint2) < circle.radius) {
        return true;
    } else {
        return false;
    }
}

function pointToRect(point: PointCollider, rect: RectCollider) {
    rect.getWorldPosition(tempPoint1);
    point.getWorldPosition(tempPoint2);
    if (tempPoint2[1] < tempPoint1[1] + rect.rect.height
        && tempPoint2[1] > tempPoint1[1]
        && tempPoint2[0] > tempPoint1[0]
        && tempPoint2[0] < tempPoint1[0] + rect.rect.width
    ) {
        return true;
    } else {
        return false;
    }
}

const CollisionMap = {
    0: 0b00000000,
    1: 0b01011100,
    2: 0b10000010,
    3: 0b01100010,
    4: 0b00100010,
    5: 0b01011000,
    6: 0b00101010,
    7: 0b00000010
}

const GroupMaxIndex = 7