import { Sockpuppet } from "puppet/client";
import { acceptEULA, checkEULA } from "../util/EULA.ts";
import { Loader } from "../types/mcgrizzconf.ts";
import { updateConfFile } from "../util/confFile.ts";
import { IS_BROWSER } from "$fresh/runtime.ts";

type MCServerEvent = 'message';
type MCServerEventCallback = (msg: string) => void;

class ServerState {
  private _status: "running" | "stopped" = "stopped";
  public get status() {
    return this._status;
  }

  private command!: Deno.Command;
  private process!: Deno.ChildProcess;

  private _eulaAccepted: boolean;

  private sockpuppet!: Sockpuppet;
  private _channelId = "blanaba";
  public get channelId() {
    return this._channelId;
  }
  public get eulaAccepted() {
    return this._eulaAccepted;
  }
  private stdin!: WritableStreamDefaultWriter; 

  private _serverType: Loader = 'unset';
  public get serverType(): Loader {
    return this.serverType;
  }
  public set serverType(loader: Loader) {
    updateConfFile({loader});
    this._serverType = loader;
  }

  constructor() {
    this._eulaAccepted = checkEULA();
    this.sockpuppet = new Sockpuppet(
      "ws://sockpuppet.cyborggrizzly.com",
      () => {
        this.sockpuppet.joinChannel(this.channelId, (msg) => {
          this.sendStdIn(msg);
        });
      },
    );
  }

  public get channel() {
    return this.sockpuppet.getChannel(this.channelId);
  }

  public async sendStdIn(message: string) {
    if (IS_BROWSER || !this.stdin) return;

    const msg = new TextEncoder().encode(message + "\n");

    await this.stdin.write(msg);
  }


  // "instance" should be moved to a private member once multi-instance support is implemented
  public startMCServer(instance = "server") {
    this.command = new Deno.Command("java", {
      args: [
        "-Xmx2G",
        "-jar",
        "./server.jar",
        "nogui",
      ],
      cwd: "./" + instance,
      stdin: "piped",
      stdout: "piped",
    });

    this.process = this.command.spawn();

    const {readable, writable} = new TransformStream();
    readable.pipeTo(this.process.stdin);
    this.stdin = writable.getWriter();

    this._status = "running";
    this.startStream();
  }

  private async startStream() {
    const stream = this.process.stdout.getReader();
    const decoder = new TextDecoder();

    while (true) {
      const { done, value } = await stream.read();
      if (value) {
        const line = decoder.decode(value);
        this.channel?.send(line);
        const stdoutMsg = new CustomEvent('stdoutmsg', {
          detail: line
        })
        globalThis.dispatchEvent(stdoutMsg);
      }
      if (done) break;
    }
  }

  public gracefullyStopMCServer() {
    this._status = "stopped";
    this.sendStdIn("stop");
  }

  public forceStopMCServer() {
    this._status = "stopped";
    this.process.kill();
  }

  public async restartMCServer() {
    if (this.status === "running") {
      await this.sendStdIn("stop");
      // while (true) {
      await this.process.status;
      // }
    }
    this.startMCServer();
  }

  public acceptEULA() {
    this._eulaAccepted = true;
    acceptEULA();
  }
}

export const SERVER_STATE = new ServerState();
