const proxy = require("udp-proxy")
const cp = require("child_process")
const appcenter = require("./appcenter")
const regionServer = require("./regionServer")
const cloud = require("./generated/secret")
const {
    initConfig, splitPacket, upload, decodeProto, log, setupHost, KPacket, debug, checkUpdate,
    brotliCompressSync, brotliDecompressSync, checkGameIsRunning, checkPortIsUsing
} = require("./utils")
const { exportData } = require("./export")
const { enablePrivilege, pause } = require("./generated/native")

const onExit = () => {
    setupHost(true)
    console.log("按任意键退出")
    pause()
};

(async () => {
    try {
        process.on("SIGHUP", () => setupHost(true))
        process.on("unhandledRejection", (reason, promise) => {
            console.log("Unhandled Rejection at: ", promise, "\n0Reason:", reason)
        })
        process.on("uncaughtException", (err, origin) => {
            appcenter.uploadError(err, true)
            console.log(err)
            console.log(`Origin: ${origin}`)
            process.exit(1)
        })
        process.on("exit", onExit)
        process.on("SIGINT", onExit)
        try {
            enablePrivilege()
        } catch (e) {
            console.log("请使用管理员身份运行此程序")
            process.exit(-1)
        }
        appcenter.startup()
        let conf = await initConfig()
        cloud.init(conf)
        checkPortIsUsing()
        checkGameIsRunning()
        log("检查更新")
        await checkUpdate()
        let gameProcess
        let unexpectedExit = true
        regionServer.create(conf,() => {
            setupHost()
            gameProcess = cp.execFile(conf.executable, { cwd: conf.path },err => {
                if (err !== null && !err.killed) {
                    throw err
                }
            })
            log("启动原神")
            gameProcess.on("exit", () => {
                if (unexpectedExit) {
                    console.log("游戏进程异常退出")
                    process.exit(0)
                }
            })
        },(ip, port, hServer) => {
            let login = false
            let cache = new Map()
            let lastRecvTimestamp = 0
            // noinspection JSUnusedGlobalSymbols
            const options = {
                address: ip,
                port: port,
                localaddress: "127.0.0.1",
                localport: 45678,
                middleware: {
                    message: (msg, sender, next) => {
                        const buf = Buffer.from(msg)
                        if (!(login && buf.readUInt8(8) === 0x51)) {
                            next(msg, sender)
                        }
                    },
                    proxyMsg: (msg, sender, peer, next) => {
                        try { next(msg, sender, peer) } catch (e) {}
                    }
                }
            }
            let monitor;
            let stopped = false
            const createMonitor = () => {
                monitor = setInterval(async () => {
                    if (login && lastRecvTimestamp + 2 < parseInt(Date.now() / 1000) && !stopped) {
                        stopped = true
                        unexpectedExit = false
                        server.close(() => {})
                        hServer.close()
                        gameProcess.kill()
                        clearInterval(monitor)
                        setupHost(true)
                        log("正在处理数据，请稍后...")
                        let packets = Array.from(cache.values())
                        cache.clear()
                        packets.sort((a, b) => a.frg - b.frg)
                            .sort((a, b) => a.sn - b.sn)
                            .filter(i => i.data.byteLength !== 0)
                            .forEach(i => {
                                const psn = i.sn + i.frg
                                cache.has(psn) ? (() => {
                                    const arr = cache.get(psn)
                                    arr.push(i.data)
                                    cache.set(psn, arr)
                                })() : cache.set(psn, [i.data])
                            })
                        packets = Array.from(cache.values())
                            .map(arr => {
                                const data = Buffer.concat(arr)
                                const len = Buffer.alloc(4)
                                len.writeUInt32LE(data.length)
                                return Buffer.concat([len, data])
                            })
                        const merged = Buffer.concat(packets)
                        const compressed = brotliCompressSync(merged)
                        const response = await upload(compressed)
                        const data = brotliDecompressSync(response.data)
                        if (response.status !== 200) {
                            log(`发生错误: ${data}`)
                            log(`请求ID: ${response.headers["x-api-requestid"]}`)
                            log("请联系开发者以获取帮助")
                        } else {
                            const proto = await decodeProto(data, "Notify1")
                            await exportData(proto)
                        }
                        process.exit(0)
                    }
                },1000)
            }
            const server = proxy.createServer(options)
            server.on("message", (msg, _) => {
                if (msg.byteLength > 500) {
                    login = true
                }
            })
            server.on("error", err => console.log(`Proxy error: ${err.message}` + err.message))
            server.on("proxyError", err => console.log(`Proxy error: ${err.message}` + err.message))
            server.on("proxyMsg", (msg, _) => {
                lastRecvTimestamp = parseInt(Date.now() / 1000)
                let buf = Buffer.from(msg)
                if (buf.byteLength <= 20) {
                    switch(buf.readUInt32BE(0)) {
                        case 325:
                            createMonitor()
                            debug("Connection established.")
                            break
                        case 404:
                            debug("Connection terminated.")
                            lastRecvTimestamp = parseInt(Date.now() / 1000) - 2333
                            break
                        default:
                            console.log(`Unhandled: ${buf.toString("hex")}`)
                            process.exit(2)
                            break
                    }
                    return
                }
                splitPacket(buf).forEach(sb => {
                    if (sb.readUInt8(8) === 0x51) {
                        const p = new KPacket(sb)
                        if (!cache.has(p.hash)) cache.set(p.hash, p)
                    }
                })
            })
            return server
        }).then(() => log("加载完毕"))
    } catch (e) {
        console.log(e)
        if (e instanceof Error) {
            appcenter.uploadError(e, true)
        } else {
            appcenter.uploadError(Error(e), true)
        }
        process.exit(1)
    }
})()
