import { assertExists } from "https://deno.land/std@0.224.0/assert/assert_exists.ts";

import { Guessing } from "./guesser.ts";
import { Gaming, GuessResult } from "./game/game.ts";
import { last, wait } from "./utils.ts";

export class Runner<Ga extends Gaming, Gu extends Guessing> {
	game;
	guesser;
	delay_ms;
	turns;
	logging;

	constructor(game: Ga, guesser: Gu, logging: LoggingStrategy, delay_ms = 0) {
		this.game = game;
		this.guesser = guesser;
		this.delay_ms = delay_ms;
		this.turns = [] as Turn[];
		this.logging = logging;
	}

	async play_all() {
		this.logging.on_start(this.game.length());
		while (true) {
			const result = await this.guesser.guess(async (guess, known) => {
				const result = await this.game.guess(guess, known);
				this.turns.push({ guess, result });
				this.logging.on_guess(known, guess, result);
				return result;
			});
			if (result !== null) break;
			await wait(this.delay_ms);
		}
		this.logging.on_finish(this.turns);
	}
}

type Turn = { guess: string; result: GuessResult };

function format_result(result: GuessResult) {
	if (result.kind === "success") return "success";
	let line = "";
	for (const info of result.informations) {
		if (info.kind === "abscent") line += ".";
		if (info.kind === "somewhere") line += "*";
		if (info.kind === "there") line += "#";
	}
	return line;
}

interface LoggingStrategy {
	on_start(length: number): void;
	on_guess(known: string, guess: string, result: GuessResult): void;
	on_finish(turns: Turn[]): void;
}

export class VerboseLogging implements LoggingStrategy {
	on_start(length: number) {}

	on_guess(known: string, guess: string, result: GuessResult): void {
		console.log("   Knows:", known);
		console.log("Guessing:", guess);
		console.log("     Got:", format_result(result));
		console.log();
	}

	on_finish(turns: Turn[]): void {
		const last_ = last(turns);
		assertExists(last_);
		console.log("Stopped on", last_.guess, ".");
		console.log("With", last_.result.kind, ".");
		console.log("In", turns.length, "steps");
	}
}

export class TableLogging implements LoggingStrategy {
	on_start(length: number): void {
		const pad = (title: string) => title.padEnd(length);
		console.log(`${pad("known")}\t${pad("guess")}\t${pad("result")}`);
		console.log();
	}
	on_guess(known: string, guess: string, result: GuessResult): void {
		console.log(`${known}\t${guess}\t${format_result(result)}`);
	}
	on_finish(turns: Turn[]): void {
		const last_ = last(turns);
		assertExists(last_);
		console.log();
		console.log(last_.result.kind, "in", turns.length, "steps");
	}
}
