import EventEmitter from 'events';
import {existsSync, readdirSync, readFileSync, writeFileSync} from 'fs';
import {parse} from 'json5';
import * as mathjs from 'mathjs';
import {join, resolve} from 'path';
import {argv, env} from 'process';
import {HttpServer, WsClient, WsServer} from 'tsrpc';
import {ServiceType as ServiceTypeCross} from './cross/protocols/serviceProto';
import {MyEvent} from './event';
import {addListener, gEventType} from './globalListener';
import localConfig from './localConfig';
import {ServiceType as ServiceTypeHttp} from './monopoly/protocols/serviceProto';
import {SchedulerManage} from './public/scheduler/scheduler';
import {_mongodb} from './setMongodb';
import {redisJsonFun} from './setRedis';
import {ResGetList} from './shared/protocols/pay/PtlGetList';
import {ServiceType as ServiceTypeWs} from './shared/protocols/serviceProto';
import {PublicShared} from './shared/public/public';
import {clusterRunOnce} from './clusterUtils';
import * as ramda from 'ramda'
import Redis from 'ioredis';

declare global {
    var G: _G & Partial<{
        /**仅类型映射 */
        otherBuff: any;
    }>;
    var mathJs: typeof mathjs;

    var R

    type k_v<T> = { [k: string]: T; };
    type atn = { a: string, t: string | number | any, n: number; colour?: number; shiwuBuff?: any; };

    /**类型过滤 */
    type FilterConditionally<Source, Condition> = Pick<Source,
        {
            [K in keyof Source]: Source[K] extends Condition ? K : never
        }[keyof Source]>;

    interface Array<T> {
        /**数组随机取值 */
        random(): T;

        /**取一个数组在当前数组中的交集 */
        intersection(other: T[]): T[];

        /**取一个数组在当前数组中的差集 */
        difference(other: T[]): T[];

        /**数组是否存在重复元素 */
        isDuplication(): boolean;

        /**打乱数组 */
        shuffle(): this;
    }

    interface Uint8Array {
        /**是否已经加密 */
        encrypt: boolean;
    }
}

class _G {
    /**游戏配置 */
    gc: Partial<_gcType> = {};
    /**服务启动时命令行参数解析 */
    argv: {
        /**服务器类型 */
        serverType: 'msg' | 'cross',
        /**服务器日志模式 error | debug*/
        logModel: string;
    } = {
        serverType: 'msg',
        logModel: 'error'
    };
    /**当前时间对象 */
    date: Date;
    /**当前时间戳 */
    time: number;
    // /**serverType==msg时记录所有玩家的uid, 剔除全局变量，转移到redis  */
    // uids?: string[];
    /**服务器配置 */
    config: Partial<typeof localConfig> & k_v<any> = {};
    /**当serverType==msg时才会启动的http */
    http?: HttpServer<ServiceTypeHttp>;
    /**当serverType!=cross时才会启动的ws */
    server?: WsServer<ServiceTypeWs>;
    /**当serverType==cross时才会启动的ws */
    serverCross?: WsServer<ServiceTypeCross>;
    /**当serverType==msg时才会启动的跨服连接 */
    clientCross?: WsClient<ServiceTypeCross>;
    /**redis连接对象 */
    redis: redisJsonFun;
    /**ioredis连接对象 */
    ioredis: Redis;
    /** 跨服 ioredis 连接对象 */
    crossioredis: Redis;
    /**mongodb连接对象 */
    mongodb: _mongodb;
    /**crossmongodb连接对象 */
    crossmongodb: _mongodb;
    // mongodbClient: any;
    /**所有玩家的充值记录 */
    allPlayerPayLog: k_v<ResGetList['list']> = {};

    /**跨服活动——消费竞赛的开启状态 */
    huodong = {
        xfjs: false,
        xfjsId: ''
    };

    private event = new EventEmitter();

    /**映射开服时间 */
    get openTime() {
        return this.config.openTime;
    }

    get getEvent(): new (...args: any) => {
        on(type: any, callback: any, caller?: any): any;
        once(type: any, callback: any): any;
        off(type: any, callback: any, caller?: any): void;
        emit(type: any, ...args: any[]): void;
        debug(): any;
        removeAllListeners(type?: any): void;
    } {
        return MyEvent as any;
    }

    constructor(argArr: string[]) {
        let configJson = existsSync(resolve(__dirname, 'config.json')) ? JSON.parse(readFileSync(resolve(__dirname, 'config.json'), 'utf-8')) : {};

        for (let index = 0; index < argArr.length; index++) {
            let arg = argArr[index];
            if (arg == '-serverType') {
                this.argv.serverType = argArr[index + 1] as any;
            } else if (arg == '-logModel') {
                this.argv.logModel = argArr[index + 1];
            }
        }

        Object.assign(localConfig, configJson);
        this.config = localConfig;
        if (env.SERVER_ID) {
            this.config.serverId = ~~env.SERVER_ID;
        }

        this.initGc();
    }

    on<T extends keyof gEventType>(event: T, callback: gEventType[T]) {
        return this.event.on(event, (...args) => {
            try {
                callback.call(this, ...args);
            } catch (e) {
                console.error(e)
            }
        });
    }

    once<T extends keyof gEventType>(event: T, callback: gEventType[T]) {
        return this.event.once(event, callback);
    }

    emit<T extends keyof gEventType>(event: T, ...args: Parameters<gEventType[T]>) {
        return this.event.emit(event, ...args);
    }

    /**加载json */
    initGc() {
        let jsonPath = join(__dirname, 'json');
        readdirSync(jsonPath).forEach(file => {
            if (file.endsWith('.json5')) {
                let json = parse(readFileSync(join(jsonPath, file), 'utf-8'));
                this.gc[file.split('.')[0]] = json;
            } else if (file.endsWith('.json')) {
                let json = JSON.parse(readFileSync(join(jsonPath, file), 'utf-8'));
                this.gc[file.split('.')[0]] = json;
            }
        });
    }

    /**更新时间 每秒刷新 */
    updateTime() {
        if (G.config.debug && G.config.time) {
            try {
                let conf = JSON.parse(readFileSync(resolve(__dirname, 'config.json'), 'utf-8'));
                this.date = new Date(conf.time);
                this.date = new Date(this.date.getTime() + 1000);
                this.time = Math.round(this.date.getTime() / 1000);
                conf.time = this.date.format('YYYY-MM-DD hh:mm:ss');
                // writeFileSync(resolve(__dirname, 'config.json'), JSON.stringify(conf, null, 2));
            } catch (error) {

            }
        } else {
            // let date = new Date();
            // let time = date.getTime();
            // let offset = date.getTimezoneOffset() / 60 + this.config.utc;  
            // this.date = new Date(time + offset * 3600 * 1000);
            this.date = new Date();
            this.time = Math.round(this.date.getTime() / 1000);
        }
    }

    /**检查redis路径是否首字符为数字 */
    formatRedisKey(key: string) {
        let firstStr = key.toString().substring(0, 1);
        if (!isNaN(Number(firstStr))) return '_' + key;
        return key;
    }

    /**改写开区时间（外部请求 games/open 改写）*/
    set openTime(_opentine) {
        try {
            let conf = JSON.parse(readFileSync(resolve(__dirname, 'config.json'), 'utf-8'));
            G.date = new Date(_opentine);
            G.date = new Date(this.date.getTime() + 1000);
            conf.openTime = this.date.format('YYYY-MM-DD hh:mm:ss');
            // this.openTime = this.date.format('YYYY-MM-DD hh:mm:ss');    // 外部调用时直接改写了，不然会无限重复调用
            this.config.openTime = this.date.format('YYYY-MM-DD hh:mm:ss')
            // writeFileSync(resolve(__dirname, 'config.json'), JSON.stringify(conf, null, 2));
        } catch (error) {

        }
    }

    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms))
    }

}


export function ctor() {
    globalThis.mathJs = mathjs;
    globalThis.G = new _G(argv);
    G.updateTime();
    globalThis.R = ramda
}

Array.prototype.random = function (this: Array<any>) {
    return this[PublicShared.randomNum(0, this.length - 1)];
};

Array.prototype.intersection = function (this: Array<any>, other: Array<any>) {
    let a = new Set(this);
    let b = new Set(other);

    return Array.from(new Set([...a].filter(x => b.has(x))));
};

Array.prototype.difference = function (this: Array<any>, other: Array<any>) {
    let a = new Set(this);
    let b = new Set(other);

    return Array.from(new Set([...a].filter(x => !b.has(x))));
};

Array.prototype.isDuplication = function (this: Array<any>) {
    return this.length != [...new Set(this)].length;
};

Array.prototype.shuffle = function (this: Array<any>) {
    this.forEach((v, k) => {
        let _k = 0 | (Math.random() * (k + 1));
        var tmp = this[k];
        this[k] = this[_k];
        this[_k] = tmp;
    });
    return this;
}


