import Car from "./Car";
import { MConst } from "../Global/MConst";
import PlayerController from "./PlayerController";
import Ball from "./Ball";
import Physics from "../Phycics/Physics";
import GameObject from "./GameObject";
import { RectCollider, ColliderGroup } from "../Phycics/Collider";
import { MConfigs } from "../Global/MConfigs";
import { MTween as TweenMgr } from "../Component/MTween";
import Drop from "./Drop";
import { MUtils } from "../Global/MUtils";
import { arrayRemove, getBallScore } from "../Global/GUtils";
import MTimer from "../Global/MTimer";
import PhycicsSystem from "../Phycics/PhycicsSystem";
import Bullet, { HorizontalMoveMgr } from "./Bullet";
import { BallPoolGroup } from "../Pools/BallPool";
import AnimationPoolGroup from "../Pools/AnimationPool";
import FrameAnimation, { FrameAnimationMgr } from "../Component/FrameAnimation";
import { DuangAltaData, NumFontData, BoomAnimationData } from "../Global/JsonData";
import SpriteFontLabel, { SpriteFont } from "../Component/SpriteFont";
import GuideMgr from "../Mgr/GuideMgr";
import { Pool, PoolGroup, PoolElement } from "../Component/Pool";
import { BulletPool } from "../Pools/BulletPool";
import SpBoomEffectPoolGroup, { SpBoomEffectMgr } from "./SpBoomEffect";
import DropPoolGroup from "../Pools/DropPool";
import DropBlinkMgr from "../Mgr/DropBlinkMgr";
import GameMgr from "../Mgr/GameMgr";
import { DataMgr } from "../Mgr/DataMgr";
import { initNECaptcha } from "../Component/InitNECaptcha";
import { NetUtils } from "../Global/NetUtils";
import Net from "../Global/Net";

type LayerName = "Bullet" | "Ball" | "Drop" | "Effect";

export default class Game {
    /****** property ******/
    /**
     * 
     *  当前得分
     * @readonly
     */
    public get score(): number {
        return this._score;
    }

    /**
     * 当前计时器时间
     * @readonly
     */
    public get timer() {
        return this._timer;
    }

    /**
     * 子弹分数
     * @readonly
     */
    public get bulletScore(): number {
        return this._bulletScore;
    }

    /**
     * 火力分数
     * @readonly
     */
    public get powerScore(): number {
        return this._powerScore;
    }

    /**设置游戏暂停状态 */
    public set pause(v: boolean) {
        this._pause = v;

        this.timing = !this._pause; //取反

        for (let mgr of this.mgrs) {
            mgr.pause = this._pause;
        }
    }
    public get pause() {
        return this._pause;
    }
    private _pause: boolean = false;

    /**游戏结束回调函数 */
    public onGameOver: () => void;

    /**分数改变时的回调函数 delta是分数的变化量 */
    public onScoreChange: (delta: number) => void;

    /****** public method ******/
    /**复活 */
    public revive() {
        this._car.revive();
        this.pause = false;
    }

    /**销毁游戏 */
    public destroy() {
        this.node.destroy();
        for (let mgr of this.mgrs) {
            mgr.onDestroy();
        }
        MTimer.removeOnFrame("Game");
        MTimer.removeOnFrame("Car");
    }

    private mgrs: GameMgr[] = [];
    public _tweenMgr: TweenMgr = new TweenMgr();
    public _phycicsSystem: PhycicsSystem = new PhycicsSystem();
    public _boomEffectMgr: SpBoomEffectMgr = new SpBoomEffectMgr();
    public _animationMgr: FrameAnimationMgr = new FrameAnimationMgr();
    public _horizontalMoveMgr: HorizontalMoveMgr = new HorizontalMoveMgr();

    private constantSubmitSeq = 1;
    private needSubmitCount = 0; //实际分数减去ConstantSubmitScoreNum的次数
    private isSubmiting = false;
    public startId: number = 0;

    private _localScore: number = 0;
    public get localScore(): number {
        return this._localScore;
    }

    public set localScore(v: number) {
        this._localScore = v;

        if (this._localScore >= MConst.ConstantSubmitScoreNum) {
            let count = Math.floor(this._localScore / MConst.ConstantSubmitScoreNum);
            this.needSubmitCount += count;
            this._localScore -= MConst.ConstantSubmitScoreNum * count;
            if (!this.isSubmiting) {
                this.constantSubmit();
            }
        }
    }

    private constantSubmit(score: number = MConst.ConstantSubmitScoreNum) {
        if (this.isSubmiting) {
            return;
        }
        if (this.needSubmitCount == 0) {
            return;
        } else if (this.needSubmitCount < 0) {
            //异常，有bug
            console.error("invalid needSubmitCount value");
            return;
        }

        this.isSubmiting = true;
        let seq = NetUtils.encryptSeq(this.constantSubmitSeq);

        return new Promise((resolve, reject) => {
            Net.sendPost(Net.Url.constantSubmit, {
                startId: this.startId,
                seq: seq,
                score: score,
                token: NetUtils.md5(this.startId.toString() + seq.toString() + score.toString() + "qzBankRPW@dui88")
            }, () => {
                this.constantSubmitSeq++;
                console.error("submited", this.constantSubmitSeq, this.needSubmitCount);
                this.needSubmitCount--;
                this.isSubmiting = false;

                this.onConstantSubmited && this.onConstantSubmited();

                resolve();
            }, (res) => {
                this.isSubmiting = false;
                reject();
            });
        });
    }

    private onConstantSubmited: () => void;

    public async finalSubmit(propsId?: string, onSubmited?: () => void) {
        await new Promise(resolve => {
            if (this.needSubmitCount > 0 && this.isSubmiting) { //正在提交
                this.onConstantSubmited = () => {
                    resolve();
                }
            } else {
                resolve();
            }
        });

        while (this.needSubmitCount > 0) {
            await this.constantSubmit();
        }

        const score = this.score;
        let serverScore = this.localScore + (this.constantSubmitSeq - 1 + this.needSubmitCount) * MConst.ConstantSubmitScoreNum;
        if (serverScore != score) {
            console.error(`[score error] server:${serverScore} local:${score}`);
        } else {
            console.log(`[score] server:${serverScore} local:${score}`);
        }

        //中途提交最后一次
        if (this.localScore > 0) {
            this.needSubmitCount++;
            await this.constantSubmit(this.localScore);
            console.log("extra submit:" + this.localScore);
        }

        //滑块验证
        let validate = await new Promise<string>(resolve => {
            if (typeof engine.env.minEnableCaptchaScore == "number" && score >= engine.env.minEnableCaptchaScore) {
                initNECaptcha({
                    captchaId: MConst.captchaId,
                    callback: (ret: { validate: string }) => {
                        resolve(ret.validate);
                    }
                });
            } else {
                resolve("");
            }
        });

        engine.globalEvent.dispatchEvent("ShootPlanetFinalSubmit", {
            startId: this.startId,
            score: score,
            useSpId: propsId || "",
            token: NetUtils.md5(this.startId.toString() + score.toString() + "qzBankRPW@dui88"),
            validate: validate
        });
    }

    private loadRes() {
        FrameAnimation.loadRes(RES.getRes("217164f4-a185-429c-8706-818137a4e438"), DuangAltaData);
        SpriteFont.load(RES.getRes("b4d821e8-8274-4b60-95d0-47da803ac5cf"), NumFontData);
        FrameAnimation.loadRes(RES.getRes("53c65221-3fbc-41d9-8cef-8a846876fe06"), BoomAnimationData);
    }

    private initMgrs() {
        this.mgrs = [
            this._dropBlinkMgr,
            this._tweenMgr,
            this._phycicsSystem,
            this._boomEffectMgr,
            this._animationMgr,
            this._horizontalMoveMgr
        ];

        for (let mgr of this.mgrs) {
            mgr.onInit();
        }
    }
    constructor(parent: engine.Container) {
        DataMgr.game = this;
        this.initMgrs();
        this.pause = false;

        this.loadRes();

        parent.addChild(this.node);
        this.node.width = MConst.DesignResolution.width;
        this.node.height = MConst.DesignResolution.height;
        let bg = new engine.Image(RES.getRes("d99368b8-af5d-4d9e-981e-7bce3e1c1e84"));
        bg.x = -10;
        bg.y = -10;
        this.node.addChild(bg);
        //子弹层
        this.node.addChild(this.layers.bullet);
        //炮车
        let car = new Car();
        car.x = this.node.width / 2 - car.width / 2;
        car.y = MConst.GroundLine - car.height + 30;
        this.node.addChild(car);
        car.addComponent(Physics);
        car.onDied = () => {
            this.over();
        };
        this._car = car;

        //球的层
        this.node.addChild(this.layers.ball);
        //掉落物层
        this.node.addChild(this.layers.drop);
        //特效层
        this.node.addChild(this.layers.effect);
        //动画层
        this.node.addChild(this.layers.animation);

        //禁用层级交互事件
        for (let k in this.layers) {
            this.layers[k].mouseChildren = false;
            this.layers[k].mouseEnabled = false;
        }

        //玩家控制器
        let playerController = new PlayerController();
        playerController.onTouchMove = (deltaX) => {
            if (!this.pause) {
                car.move(deltaX);
            }
        };
        this.node.addChild(playerController);

        //添加监听器
        MTimer.onFrame("Game", this.onUpdate, this);

        this.createPools();

        //创建墙和地面
        this.createWall();

        //开始倒计时
        this.timing = true;

        // this._BulletScore = 140;
        // this._PowerScore = 2000;
    }


    /****** private method ******/

    private over() {
        this.pause = true;
        engine.globalEvent.dispatchEvent("ShootPlanetGameOver");
        this.finalSubmit();
        this.onGameOver && this.onGameOver();
    }

    private createWall() {
        let topWall = new GameObject();
        topWall.x = -500;
        topWall.y = -500
        this.node.addChild(topWall);
        //添加天花板碰撞器
        let topWallCollider = topWall.addComponent<RectCollider>(RectCollider);
        topWallCollider.setData(0, 0, MConst.DesignResolution.width + 1000, 490);
        topWallCollider.group = ColliderGroup.Top;

        //创建地面节点
        let ground = new GameObject();
        ground.x = -500;
        ground.y = MConst.GroundLine;
        this.node.addChild(ground);
        //添加地面碰撞器
        let groundCollider = ground.addComponent<RectCollider>(RectCollider);
        groundCollider.setData(0, 0, MConst.DesignResolution.width + 1000, 1000);
        groundCollider.group = ColliderGroup.Ground;

        //创建左墙节点
        let leftWall = new GameObject();
        leftWall.x = -500;
        leftWall.y = -500
        this.node.addChild(leftWall);
        //添加左墙碰撞器
        let leftWallCollider = leftWall.addComponent<RectCollider>(RectCollider);
        leftWallCollider.setData(0, 0, 500, MConst.DesignResolution.height + 1000);
        leftWallCollider.group = ColliderGroup.Wall;

        //创建右墙节点
        let rightWall = new GameObject();
        rightWall.x = MConst.DesignResolution.width;
        rightWall.y = -500
        this.node.addChild(rightWall);
        //添加右墙碰撞器
        let rightWallCollider = rightWall.addComponent<RectCollider>(RectCollider);
        rightWallCollider.setData(0, 0, 500, MConst.DesignResolution.height + 1000);
        rightWallCollider.group = ColliderGroup.Wall;
    }


    private createBall() {
        let size = MUtils.randomInt(0, MConfigs.size.length);
        if (GuideMgr.instance.guideFlag == true) {
            size = 3;
        }
        let color = MUtils.randomInt(size, MConst.MaxColorIndex);
        let ball = this._pool.ball.spwan(size);
        let dir: 1 | -1 = Math.random() > 0.5 ? -1 : 1;
        const score = getBallScore(this._BulletScore, this._PowerScore, color);
        ball.init(color, dir, score);
        ball.startBornStage(dir);

        this.createBallCD = 1500;
    }


    private onUpdate() {
        for (let i = 0; i < this._ballList.length; i++) {
            this._ballList[i].updateScoreLabel();
        }

        //分数更新
        if (this.bulletScoreUpdateFlag) {
            let score = this._BulletScore;
            //更新最大球共存数量
            this.updateMaxBallNum(score);
            this.bulletScoreUpdateFlag = false;
        }

        //检查球的创建
        if (this.createBallCD > 0) {
            this.createBallCD -= MTimer.deltaTime;
        }

        if (this._ballList.length < this.curMaxBallNum) {
            if (this.createBallCD <= 0) {
                this.createBall();
            }
        }

        //倒计时
        if (this.timing) {
            if (this._timer > 0) {
                this._timer -= MTimer.deltaTime;
            } else {
                this._timer = 0;
                this.timing = false;
                //时间到
                this.pause = true;
                engine.globalEvent.dispatchEvent("ShootPlanetGameOver");
                this.finalSubmit();
                this.onGameOver && this.onGameOver();
            }
        }

        this.outputEvent();
    }

    private outputEvent() {
        engine.globalEvent.dispatchEvent("ShootPlaneUpdate", {
            score: this.score,
            bulletScore: this.bulletScore,
            powerScore: this.powerScore,
            countDown: this.timer
        });
    }

    private updateMaxBallNum(bulletScore: number) {
        let num = 0;
        if (bulletScore <= 20) num = 1;
        else if (bulletScore <= 60) num = 2;
        else if (bulletScore <= 100) num = 3
        else if (bulletScore <= 130) num = 4;
        else if (Math.random() < 0.3) num = 6;
        else num = 7;
        this.curMaxBallNum = num;
    }

    /**
     * Ball类专用
     * 不建议调用
    */
    public _shake() {
        this._tweenMgr.removeTweens(this.node);

        let count = 0;
        let callback = () => {
            if (count > 1) return;
            count++;

            this.node.x = 10;
            this._tweenMgr.get(this.node)
                .wait(1, true)
                .to({ x: 0, y: -10 }, 1, true)
                .to({ x: -10, y: 0 }, 1, true)
                .to({ x: 0, y: 10 }, 1, true)
                .to({ x: 0, y: 0 }, 1, true)
                .call(callback);
        }

        callback();
    }

    public _pool: {
        ball: BallPoolGroup,
        bullet: BulletPool,
        boomEffect: SpBoomEffectPoolGroup
        animation: AnimationPoolGroup,
        drop: DropPoolGroup
    } = null;

    private createPools() {
        this._pool = {
            ball: new BallPoolGroup(this.layers.ball),
            bullet: new BulletPool(this.layers.bullet),
            animation: new AnimationPoolGroup(this.layers.animation),
            boomEffect: new SpBoomEffectPoolGroup(this.layers.effect),
            drop: new DropPoolGroup(this.layers.drop)
        };
    }

    public _playBoomEffect(position: engine.Point, size: number) {
        const effect = this._pool.boomEffect.spwan(MUtils.randomInt(0, 3), size);
        effect.position = position;
    }

    public _createAnimation(key: string) {
        const clip = this._pool.animation.spwan(key);
        clip.onCompleted = () => {
            this._pool.animation.recycle(key, clip);
        }
        return clip;
    }


    /****** private field ******/

    public _dropBlinkMgr = new DropBlinkMgr();

    private layers = {
        bullet: new engine.Container(),
        ball: new engine.Container(),
        drop: new engine.Container(),
        effect: new engine.Container(),
        animation: new engine.Container()
    };
    /**不建议使用 */
    public get _score(): number {
        return this.__score;
    }
    public set _score(v: number) {
        this.localScore += (v - this.__score);
        this.__score = v;
    }
    /**不建议使用 */
    public _car: Car = null;
    private __score: number = 0;
    private curMaxBallNum = 1;
    private timing = false;
    private createBallCD: number = 0;
    private bulletScoreUpdateFlag = false;
    /**不建议使用 */
    public _ballList: Ball[] = [];
    private _timer: number = MConfigs.countDown * 1000;
    private node: engine.Container = new engine.Container();

    /**不建议使用 */
    public get _BulletScore(): number {
        return this._bulletScore;
    }
    public set _BulletScore(v: number) {
        if (v == this._bulletScore) return;
        v = Math.max(v, 0);
        this._bulletScore = v;
        this.bulletScoreUpdateFlag = true;
    }
    private _bulletScore: number = 0;

    /**不建议使用 */
    public get _PowerScore(): number {
        return this._powerScore;
    }
    public set _PowerScore(v: number) {
        v = Math.max(v, 100);
        this._powerScore = v;
    }
    private _powerScore: number = 100;
}

