import GameBasic from "./common/game_basic.js";
import NPC from "./common/npc.js";
import Shooter from "./common/shooter.js";
import fetch from "node-fetch";

import initDb from './db.js';

import { createHash } from "crypto";

let db = await initDb();

function sround(n) {
    return Math.round(n * 1000) / 1000;
}

class Game extends GameBasic {
    constructor() {
        super();
        this.ws = [];
    }
    async updateLeaderboard() {
        let jason = await db.all('SELECT * FROM stats ORDER BY CAST(ko AS REAL) DESC LIMIT 1000');
        
        let scoresOld = this.scores || [];

        let scores = {};

        for (let e of jason) {
            scores[e.ip] = scores[e.ip] || 0;
            scores[e.ip] += e.ko ** 2;
        }

        for (let score in scores) {
            let s = scores[score];
            scores[score] = Math.round(Math.sqrt(s));
        }

        let s = Object.entries(scores).sort((a, b) => b[1] - a[1]).filter(a => a[1] > 5);

        for (let x in s) {
            let jk = jason.filter(y => y.ip == s[x][0]).map(y => y.ko).sort((a,b) => b - a);

            s[x].push(jk.join(', '));
        }

        this.scores = s;

        s = [...s];
        s.length = Math.min(s.length,10);

        let message = [];

        if (scoresOld.length == 0) return;

        for (let i = 0; i < this.scores.length || i < scoresOld.length; i++) {
            let a = this.scores[i] ? this.scores[i][0] : '!nobody';
            let b = this.scores[i + 1] ? this.scores[i + 1][0] : '!nobody';

            let c = scoresOld.findIndex(x => x[0] == a);
            let d = scoresOld.findIndex(x => x[0] == b);

            if (a == '!nobody' || b == '!nobody' || d - c == 1 || (c != -1 && this.scores[i][1] == scoresOld[c][1])) continue;

            message.push(`${a} beat ${b} on the leaderboard!`);
        }

        if (message.length == 0) return;

        message = message.join('\n');
        console.log(message);

        if (!process.env.HOOK) return;

        let h = await fetch(
            process.env.HOOK,
            {
                method: 'post',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    username: 'UniButton',
                    content: message,
                    embeds: [
                        {
                            color: 11730954,
                            title: 'Leaderboard',
                            description: s.map((x, i) => `**#${i + 1}**: ${x[0]} [${x[1]}]`).join('\n'),
                        },
                    ],
                    files: []
                }),
            }
        );
    }
    async sync(full = false) {
        let onScreen = this.entities.filter(x => x && x.health > 0).length;

        onScreen += 4;

        this.width = (this.width * 0.99) + (Math.sqrt(onScreen) * 16);
        this.height = (this.height * 0.99) + (Math.sqrt(onScreen) * 16);

        let { entities } = this;
        let entList = [];
        for (let entity of entities) {
            entity.camera = entity.camera || false;

            let props = entity.serverProps;
            let basic = props.map(prop => entity[prop]);
            for (let posn in basic) {
                let posp = basic[posn];
                if (!posp) continue;
                if (posp.x && posp.y) {
                    posp.x = sround(posp.x);
                    posp.y = sround(posp.y);
                }
                if (typeof posp == 'number') {
                    basic[posn] = sround(posp);
                }
            }
            let str = JSON.stringify(basic,null);

            entList.push({ entity, str, basic });
        }

        if (entList.length == 0) return;

        for (let client of this.ws) {
            let wsEnt = client.ent;

            if (!wsEnt) continue;

            if (full && wsEnt.health < 1 && !wsEnt.picked) {
                wsEnt.picked = true;

                let hash = client.username;

                if (!hash) {
                    hash = createHash('sha256');
                    hash.update((+new Date) + '');
                    //hash.update(client.ip || '');
                    hash = hash.digest('hex');
                }

                await db.run('INSERT INTO stats (username, ip, ko) VALUES (?,?,?)', [
                    wsEnt.you,
                    hash,
                    wsEnt.headCount
                ]);

                this.updateLeaderboard();
            }

            if (!client.active) continue;

            wsEnt.isYou = true;

            let filtered;

            if (!full) {
                filtered = entList.filter(({ entity: ent }) => {
                    return Math.sqrt((ent.pos.x - wsEnt.pos.x) ** 2 + (ent.pos.y - wsEnt.pos.y) ** 2) < 777
                }
                );
            } else {
                filtered = entList;
            }

            let na = [];
            for (let i in filtered) {
                let fi = filtered[i]

                na.push(fi.str)
                if (fi.entity.isYou) {
                    let narr = [...fi.basic];
                    narr[fi.entity.serverProps.indexOf('isYou')] = 'true';
                    na[na.length - 1] = JSON.stringify(narr);
                }
            }

            na.push(JSON.stringify([this.width, this.height]))

            client.send(`[${na.join(',')}]`);

            wsEnt.isYou = false;
        }

        for (let entity of entities) {
            entity.r = 1;
        }
    }
    init() {
        super.init();

        this.updateLeaderboard();
        let that = this;
        that.entities = [];

        for (let i = 0; i < 20; i++) {
            that.entities.push(new NPC(false, that))
        }

        for (let i = 0; i < 18; i++) {
            that.entities.push(new Shooter(false, that))
        }

        setInterval(function () { that.sync(false) }, 1000 / 10);
        setInterval(function () { that.sync(true) }, 1000);
        setInterval(function () { that.clean() }, 1000);
    }

    clean() {
        for (let ent in this.entities) {
            let x = this.entities[ent];
            if (x.health > 0 || x.type != 'Player') return;
            this.entites[ent] = false;
        }
    }

    async authUser(ws, data) {
        ws.token = data;

        let f = new FormData();
        f.append('token', ws.token);

        let j = await fetch("https://bg.dervland.net/api/form/auth_api/",
            {
                "method": "post",
                "body": f
            }
        );

        j = await j.text();

        try {
            j = JSON.parse(j);
        } catch (err) {
            j = {};
        }

        if (j.username == '!nobody' || !j.username) return;

        if (this.ws.findIndex(x => x.username == j.username && x.ent.health > 0) != -1) {
            console.log(`Player ${ws.ent.you} uses username ${j.username} illegally`)
            return;
        }
        ws.ent.username = ws.username = j.username;

        console.log(`Player ${ws.ent.you} uses username ${ws.username}`)
    }
}

export default Game;