const Core = require('./core');

class Unreal2 extends Core {
    constructor() {
        super();
        this.encoding = 'latin1';
    }
    async run(state) {
        let extraInfoReader;
        {
            const b = await this.sendPacket(0, true);
            const reader = this.reader(b);
            state.raw.serverid = reader.uint(4);
            state.raw.ip = this.readUnrealString(reader);
            state.gamePort = reader.uint(4);
            state.raw.queryport = reader.uint(4);
            state.name = this.readUnrealString(reader, true);
            state.map = this.readUnrealString(reader, true);
            state.raw.gametype = this.readUnrealString(reader, true);
            state.raw.numplayers = reader.uint(4);
            state.maxplayers = reader.uint(4);
            this.logger.debug(log => {
                log("UNREAL2 EXTRA INFO", reader.buffer.slice(reader.i));
            });
            extraInfoReader = reader;
        }

        {
            const b = await this.sendPacket(1,true);
            const reader = this.reader(b);
            state.raw.mutators = [];
            state.raw.rules = {};
            while(!reader.done()) {
                const key = this.readUnrealString(reader,true);
                const value = this.readUnrealString(reader,true);
                this.logger.debug(key+'='+value);
                if(key === 'Mutator' || key === 'mutator') {
                    state.raw.mutators.push(value);
                } else if (key || value) {
                    if (state.raw.rules.hasOwnProperty(key)) {
                        state.raw.rules[key] += ',' + value;
                    } else {
                        state.raw.rules[key] = value;
                    }
                }
            }
            if('GamePassword' in state.raw.rules)
                state.password = state.raw.rules.GamePassword !== 'True';
        }

        if (state.raw.mutators.includes('KillingFloorMut')
            || state.raw.rules['Num trader weapons']
            || state.raw.rules['Server Version'] === '1065'
        ) {
            // Killing Floor
            state.raw.wavecurrent = extraInfoReader.uint(4);
            state.raw.wavetotal = extraInfoReader.uint(4);
            state.raw.ping = extraInfoReader.uint(4);
            state.raw.flags = extraInfoReader.uint(4);
            state.raw.skillLevel = this.readUnrealString(extraInfoReader, true);
        } else {
            state.raw.ping = extraInfoReader.uint(4);
            // These fields were added in later revisions of unreal engine
            if (extraInfoReader.remaining() >= 8) {
                state.raw.flags = extraInfoReader.uint(4);
                state.raw.skill = this.readUnrealString(extraInfoReader, true);
            }
        }

        {
            const b = await this.sendPacket(2,false);
            const reader = this.reader(b);

            state.raw.scoreboard = {};
            while(!reader.done()) {
                const player = {};
                player.id = reader.uint(4);
                player.name = this.readUnrealString(reader,true);
                player.ping = reader.uint(4);
                player.score = reader.int(4);
                player.statsId = reader.uint(4);
                this.logger.debug(player);

                if (!player.id) {
                    state.raw.scoreboard[player.name] = player.score;
                } else if (!player.ping) {
                    state.bots.push(player);
                } else {
                    state.players.push(player);
                }
            }
        }
    }

    readUnrealString(reader, stripColor) {
        let length = reader.uint(1), ucs2 = false;
        if(length >= 0x80) {
            // This is flagged as a UCS-2 String
            length = (length&0x7f)*2;
            ucs2 = true;

            // For UCS-2 strings, some unreal 2 games randomly insert an extra 0x01 here,
            // not included in the length. Skip it if present (hopefully this never happens legitimately)
            const peek = reader.uint(1);
            if (peek !== 1) reader.skip(-1);

            this.debugLog(log => {
                log("UCS2 STRING");
                log("UCS2 Length: " + length);
                log(reader.buffer.slice(reader.i,reader.i+length));
            });
        }

        let out = '';
        if (ucs2) {
            out = reader.string({encoding:'ucs2',length:length});
            this.debugLog("UCS2 String decoded: " + out);
        } else if (length > 0) {
            out = reader.string();
        }

        // Sometimes the string has a null at the end (included with the length)
        // Strip it if present
        if(out.charCodeAt(out.length-1) === 0) {
            out = out.substring(0, out.length - 1);
        }

        if(stripColor) {
            out = out.replace(/\x1b...|[\x00-\x1a]/gus,'');
        }

        return out;
    }

    async sendPacket(type,required) {
        const outbuffer = Buffer.from([0x79,0,0,0,type]);

        const packets = [];
        return await this.udpSend(outbuffer,(buffer) => {
            const reader = this.reader(buffer);
            const header = reader.uint(4);
            const iType = reader.uint(1);
            if(iType !== type) return;
            packets.push(reader.rest());
        }, () => {
            if(!packets.length && required) return;
            return Buffer.concat(packets);
        });
    }
}

module.exports = Unreal2;
