import { ContKind, Haku } from "rkgk/haku.js";
import { renderToChunksInArea, dotterRenderArea } from "rkgk/painter.js";

export class User {
    nickname = "";
    brush = "";
    reticle = null;

    isBrushOk = false;
    simulation = null;

    constructor(wallInfo, nickname) {
        this.nickname = nickname;
        this.haku = new Haku(wallInfo.hakuLimits);
    }

    destroy() {
        this.haku.destroy();
    }

    setBrush(brush) {
        console.groupCollapsed("setBrush", this.nickname);
        let compileResult = this.haku.setBrush(brush);
        console.log("compiling brush complete", compileResult);
        console.groupEnd();

        this.isBrushOk = compileResult.status == "ok";

        return compileResult;
    }

    renderBrushToChunks(wall, x, y) {
        console.groupCollapsed("renderBrushToChunks", this.nickname);
        let result = this.painter.renderBrushToWall(this.haku, x, y, wall);
        console.log("rendering brush to chunks complete");
        console.groupEnd();

        return result;
    }

    simulate(wall, interactions) {
        console.group("simulate");
        for (let interaction of interactions) {
            if (interaction.kind == "setBrush") {
                this.simulation = null;
                this.setBrush(interaction.brush);
            }

            if (this.isBrushOk) {
                if (this.simulation == null) {
                    console.log("no simulation -- beginning brush");
                    this.simulation = { renderArea: { left: 0, top: 0, right: 0, bottom: 0 } };
                    this.haku.beginBrush();
                }

                if (interaction.kind == "dotter" && this.#expectContKind(ContKind.Dotter)) {
                    let dotter = {
                        fromX: interaction.from.x,
                        fromY: interaction.from.y,
                        toX: interaction.to.x,
                        toY: interaction.to.y,
                        num: interaction.num,
                    };
                    this.haku.contDotter(dotter);
                    this.simulation.renderArea = dotterRenderArea(wall, dotter);
                }

                if (interaction.kind == "scribble" && this.#expectContKind(ContKind.Scribble)) {
                    renderToChunksInArea(
                        wall,
                        this.simulation.renderArea,
                        (pixmap, translationX, translationY) => {
                            return this.haku.contScribble(pixmap, translationX, translationY);
                        },
                    );
                    console.log("ended simulation");
                    this.simulation = null;
                }
            }
        }
        console.groupEnd();
    }

    #expectContKind(kind) {
        if (this.haku.expectedContKind() == kind) {
            return true;
        } else {
            console.error(`expected cont kind: ${kind}`);
            return false;
        }
    }

    getStats(wallInfo) {
        return {
            astSize: this.haku.astSize,
            astSizeMax: wallInfo.hakuLimits.ast_capacity,
            numRefs: this.haku.numRefs,
            numRefsMax: wallInfo.hakuLimits.ref_capacity,
            fuel: wallInfo.hakuLimits.fuel - this.haku.remainingFuel,
            fuelMax: wallInfo.hakuLimits.fuel,
            memory: wallInfo.hakuLimits.memory - this.haku.remainingMemory,
            memoryMax: wallInfo.hakuLimits.memory,
        };
    }
}

export class OnlineUsers extends EventTarget {
    #wallInfo;
    #users = new Map();

    constructor(wallInfo) {
        super();

        this.#wallInfo = wallInfo;
    }

    addUser(sessionId, { nickname, brush }) {
        if (!this.#users.has(sessionId)) {
            console.info("user added", sessionId, nickname);

            let user = new User(this.#wallInfo, nickname);
            user.setBrush(brush);
            this.#users.set(sessionId, user);
            return user;
        } else {
            console.info("user already exists", sessionId, nickname);
            return this.#users.get(sessionId);
        }
    }

    getUser(sessionId) {
        return this.#users.get(sessionId);
    }

    removeUser(sessionId) {
        if (this.#users.has(sessionId)) {
            let user = this.#users.get(sessionId);
            user.destroy();
            console.info("user removed", sessionId, user.nickname);
            // TODO: Cleanup reticles
            this.#users.delete(sessionId);
        }
    }
}