import { GameClient } from "../../shared/gameClient/gameClient";
import { ConnectionInputOperate, GameSyncFrame } from "../../shared/gameClient/GameSyncFrame";
import { MsgAfterFrames } from "../../shared/gameClient/protocols/MsgAfterFrames";
import { MsgSyncFrame } from "../../shared/gameClient/protocols/MsgSyncFrame";

export interface InputHandler {
    [key: string]: ((connId: string, inputFrame: ConnectionInputOperate, dt: number) => void);
    /*
    execInput_NewPlayer(connId: string, inputFrame: ConnectionInputOperate, dt: number): void;
    execInput_RemovePlayer(connId: string, inputFrame: ConnectionInputOperate, dt: number): void;
    execInput_MoveDirStart(connId: string, inputFrame: ConnectionInputOperate, dt: number): void
    execInput_MoveDirEnd(connId: string, inputFrame: ConnectionInputOperate, dt: number): void;*/
}

/**帧同步执行器,对接帧同步的实现*/
export class FrameSyncExecutor {

    inputHandler: any;
    serverSyncFrameRate = 60;
    renderFrameInvMs = 1000 / this.serverSyncFrameRate;
    renderFrameDt = 1 / this.serverSyncFrameRate;
    afterFrames: GameSyncFrame[] = [];
    stateData: any = null;
    stateFrameIndex = -1;

    /**当前执行到的帧索引*/
    executeFrameIndex = -1;
    /**执行帧已经停止*/
    executeFrameStop = true;
    executeNextFrameHandler: any;
    executeNextFrameTimerHD: any = 0;
    gameClient: GameClient | undefined;
    /**最大可执行帧索引*/
    maxCanRenderFrameIndex = -1;

    public onSyncStateData: (stateData: any) => void;
    public onExecFrame: (dt: number, frameIndex: number) => void;
    public getSyncState?: () => any;

    /**
     * 
     * @date 2022/2/21 - 下午3:43:43
     *
     * @constructor
     * @param inputHandler 输入帧处理器,实现函数: execInput_<操作类型>(connId: string, inputFrame: ConnectionInputOperate, dt: number)
     * @param onSyncStateData 触发追帧时,需要同步状态数据,可以当作重新初始化游戏数据,一般只发生在刚连上服务器或断线重连成功时
     * @param onExecFrame 执行每一帧的回调,数据帧和渲染帧要区分开来
     * @param getSyncState 当要求状态同步时,需要返回当前状态数据, 方便快速追帧, 不传表示禁用本功能
     */
    constructor(gameClient: GameClient, inputHandler: any,
        onSyncStateData: (stateData: any) => void, onExecFrame: (dt: number, frameIndex: number) => void, getSyncState?: () => any) {
        this.gameClient = gameClient;
        this.inputHandler = inputHandler;
        this.onSyncStateData = onSyncStateData;
        this.onExecFrame = onExecFrame;
        this.getSyncState = getSyncState;
        this.executeNextFrameHandler = this.executeNextFrame.bind(this);

        gameClient.client.listenMsg("AfterFrames", msg => {
            this.onMsgAfterFrames(msg);
        });
        gameClient.client.listenMsg("SyncFrame", msg => {
            this.onSyncFrame(msg);
        });
        gameClient.client.listenMsg("RequireSyncState", msg => {
            if (this.getSyncState) {
                const stateData = this.getSyncState();
                const stateFrameIndex = this.executeFrameIndex;
                const arrIndexEnd = this.getAfterFramesArrIndex(stateFrameIndex);
                this.stateData = stateData;
                this.stateFrameIndex = stateFrameIndex;
                this.afterFrames.splice(0, arrIndexEnd);

                gameClient.client.sendMsg("SyncState", {
                    stateData: stateData,
                    stateFrameIndex: stateFrameIndex,
                });
            }
        });
    }
    public async dispose(): Promise<void> {
        this.stopExecuteFrame();
        await this.gameClient?.disconnect();
    }

    onMsgAfterFrames(msg: MsgAfterFrames) {
        this.serverSyncFrameRate = msg.serverSyncFrameRate;
        this.renderFrameInvMs = 1000 / this.serverSyncFrameRate;
        this.renderFrameDt = 1 / this.serverSyncFrameRate;
        this.afterFrames = msg.afterFrames;
        this.stateData = msg.stateData;
        this.stateFrameIndex = msg.stateFrameIndex;
        this.maxCanRenderFrameIndex = msg.maxSyncFrameIndex;
        this.executeFrameIndex = this.stateFrameIndex;

        this.onSyncStateData?.call(this, this.stateData);

        if (this.executeFrameStop) {
            //如果执行已经停下来,则开始执行
            this.executeNextFrame();
        }
    }
    onSyncFrame(frame: MsgSyncFrame) {
        const arrIndex = this.getAfterFramesArrIndex(frame.frameIndex);
        this.afterFrames[arrIndex] = frame.syncFrame;

        if (this.maxCanRenderFrameIndex + 1 == frame.frameIndex) {
            //同步下来的帧,是按顺序的,直接更新最大可执行帧索引
            this.maxCanRenderFrameIndex = frame.frameIndex;
            //正常tcp通讯,数据包顺序不会错乱,但为了防止错乱,这里做一下处理
            //接下去判断之后的是否不为空,直接推到不为空的前一个索引
            for (let fi = this.maxCanRenderFrameIndex + 1, i = this.getAfterFramesArrIndex(fi);
                i < this.afterFrames.length; fi++, i++) {
                if (this.afterFrames[i]) {
                    this.maxCanRenderFrameIndex = fi;
                } else {
                    break;
                }
            }
        }

        if (this.executeFrameStop) {
            //如果执行已经停下来,则开始执行
            this.executeNextFrame();
        }
    }
    getAfterFramesArrIndex(frameIndex: number) {
        return frameIndex - this.stateFrameIndex - 1;
    }
    /**执行一帧服务端的同步帧,并返回是否执行完所有帧了*/
    executeOneFrame(dt: number): boolean {
        const frameIndex = this.executeFrameIndex + 1;
        if (frameIndex > this.maxCanRenderFrameIndex) return true;
        this.executeFrameIndex = frameIndex;
        const arrIndex = this.getAfterFramesArrIndex(frameIndex);
        const frame = this.afterFrames[arrIndex];
        if(!frame){
            console.error(`this.afterFrames[${arrIndex}]为空,frameIndex:${frameIndex}`);
        }else{
            const inputs = frame.connectionInputs;
            if (inputs) {
                for (let i = 0; i < inputs.length; i++) {
                    const inp = inputs[i];
                    for (let j = 0; j < inp.operates.length; j++) {
                        const op = inp.operates[j];
                        const fn = this.inputHandler['execInput_' + op.inputType];
                        if (fn) fn.call(this.inputHandler, inp.connectionId, op, dt);
                    }
                }
            }
        }

        this.onExecFrame?.call(this, dt, frameIndex);

        if (frameIndex >= this.maxCanRenderFrameIndex) {
            return true;
        } else {
            return false;
        }
    }
    /**执行下一帧,并且自动根据情况执行之后的帧,执行完则自动停下来,设置renderFrameStop=true*/
    executeNextFrame() {
        //处理帧信息
        if (this.executeOneFrame(this.renderFrameDt)) {
            //已经执行完所有帧了,等待服务器新消息
            this.executeFrameStop = true;
            return;
        }

        //为执行的帧数
        const unRenderFrameCount = this.stateFrameIndex + 1 + this.afterFrames.length - this.executeFrameIndex;
        //根据性能动态计算,下一帧要执行的间隔
        let frameInv = 0;
        if (unRenderFrameCount > 10) {
            //当缓存帧过多时,一次处理多个帧信息(卡着线程处理,会让有些物理引擎计算偏差)
            for (let i = 0; i < unRenderFrameCount; i++) {
                if (this.executeOneFrame(this.renderFrameDt)) {
                    //已经执行完所有帧了,等待服务器新消息
                    this.executeFrameStop = true;
                    return;
                }
            }
            frameInv = 0;
        } else if (unRenderFrameCount > 3) {
            //相差一点,开始追帧(setTimetout 0,不卡死又能快速执行)
            frameInv = 0;
        } else {
            //正常速度
            frameInv = this.renderFrameInvMs;
        }
        if (this.executeFrameStop) return;

        /*
        //累计1k清理一波过期帧
        if (this.currFrameIndex > 1000) {
            this.frames.splice(0, this.currFrameIndex);
            this.currFrameIndex = 0;
        }
        */

        this.executeNextFrameTimerHD = setTimeout(
            this.executeNextFrameHandler,
            frameInv
        );
    }
    stopExecuteFrame() {
        this.executeFrameStop = true;
        clearTimeout(this.executeNextFrameTimerHD);
    }

    public sendInputFrames(ops: ConnectionInputOperate[]) {
        //正常输入实现,应当:即时操作推到一个数组,定时器发送然后清空
        this.gameClient?.client.sendMsg("InpFrame", {
            operates: ops
        });
    }
    public sendInputFrame(op: ConnectionInputOperate) {
        //正常输入实现,应当:即时操作推到一个数组,定时器发送然后清空
        this.gameClient?.client.sendMsg("InpFrame", {
            operates: [op]
        });
    }
}