<script lang="ts">
	import {
		Button,
		Column,
		DataTable,
		Grid,
		ListItem,
		RadioButton,
		RadioButtonGroup,
		Row,
		Toggle,
		UnorderedList
	} from "carbon-components-svelte";
	import wasmInit, { count_empty, find_winner, get_score, predict } from "@game-algorithms/o-x-rust";
	import OXBoard from "$lib/components/o-x/OXBoard.svelte";
	import OXOIcon from "$lib/components/o-x/OXOIcon.svelte";
	import OXXIcon from "$lib/components/o-x/OXXIcon.svelte";
	import { onMount } from "svelte";

	let board = Array(9).fill(0);

	let turn = -1;
	let playAs = "X";
	let algorithm = "mm+d";
	let human = 0;
	let bot = 0;
	let loaded = false;
	let showPreviousScores = false;
	let showCurrentScores = false;
	let showScoresFor = "human";
	let status = "Press 'Start' to play";
	// step: cell: [human, bot]
	let scores: [number | null, number | null][][] = [];

	onMount(async () => {
		await wasmInit();
		loaded = true;
	});

	/**
	 * Complete a turn logic
	 */
	function completeTurn() {
		const convertedBoard = Uint8Array.from(board);
		scores = [...scores, getBoardScore(board, human, bot, algorithm)];
		const winner = find_winner(convertedBoard);
		if (winner !== 0 || count_empty(convertedBoard) === 0) {
			turn = -1;
			switch (winner) {
			case 0:
				status = "You drew!";
				break;
			case human:
				status = "You won!";
				break;
			case bot:
				status = `The ${algorithm} algorithm defeated you!`;
				break;
			default:
				status = "How did we get here?";
				break;
			}
			alert(status);
			return;
		}
		turn %= 2;
		turn += 1;
		if (turn === human) {
			status = "Your turn!";
		} else {
			status = "Algorithm's turn!";
			const p = predict(bot, human, bot === 1, convertedBoard, algorithm);
			board[p] = bot;
			completeTurn();
		}
	}

	/**
	 * Get the scores for all possible moves on the board for each player
	 *
	 * @param board The board
	 * @param human The human player ID
	 * @param bot The bot player ID
	 * @param algorithm The algorithm to use for scoring
	 * @returns The scores for the moves for each cell and player
	 */
	function getBoardScore(
		board: number[],
		human: number,
		bot: number,
		algorithm: string
	): [number | null, number | null][] {
		return board.map((v, i) => {
			if (v === 0) {
				const humanCopy = board.slice();
				humanCopy[i] = human;
				const hUint8Array = Uint8Array.from(humanCopy);
				const botCopy = board.slice();
				botCopy[i] = bot;
				const bUint8Array = Uint8Array.from(humanCopy);
				return [
					get_score(human, bot, human === 1, hUint8Array, algorithm),
					get_score(bot, human, bot === 1, bUint8Array, algorithm)
				];
			} else {
				return [null, null];
			}
		});
	}

	/**
	 * Helper function to add up to <dp> decimal places to a number
	 *
	 * @param n Number
	 * @param dp Maximum decimal places
	 * @returns String formated number
	 */
	function toFixedOrLess(n: number, dp: number) {
		const f = n.toFixed(dp);
		const [u, d] = f.split(".");
		const minD = d.slice(0, d.indexOf("0"));
		if (minD.length === 0) {
			return u;
		}
		return u + "." + minD;
	}

	/**
	 * Generate a table row from an array of scores
	 *
	 * @param player Player to get scores for
	 * @param moveOverride Override the move key value
	 * @returns The generated table row
	 */
	function scoreToRows(player: string, moveOverride?: string) {
		return (score: [number | null, number | null][], i: number) => {
			const rowScores: Record<string, string> = {};
			for (let cell = 0;cell < score.length;cell++) {
				const s = score[cell][player === "human" ? 0 : 1];
				rowScores[cell.toString()] = s === null ? "-" : toFixedOrLess(s, 3);
			}
			return {
				id: i,
				move: moveOverride || i + 1,
				...rowScores
			};
		};
	}
</script>

<svelte:head>
	<title>Noughts & Crosses</title>
</svelte:head>

<Grid>
	<Row>
		<Column>
			<h1>Noughts and Crosses</h1>
			<p>
				This is a noughts and crosses bot using various algorithms.
			</p>
			<UnorderedList>
				<ListItem>sa: score adding without optimizations</ListItem>
				<ListItem>sa+rd: score adding with ratio optimization including draw (score between win and loose)</ListItem>
				<ListItem>sa+r-d: score adding with ratio optimization not including draw (draw = loss)</ListItem>
				<ListItem>mm: minimax</ListItem>
				<ListItem>mm+d: minimax with depth</ListItem>
			</UnorderedList>
			<div class="selection">
				<RadioButtonGroup disabled={turn !== -1} legendText="Algorithm" bind:selected={algorithm}>
					<RadioButton labelText="sa" value="sa" />
					<RadioButton labelText="sa+rd" value="sa+rd" />
					<RadioButton labelText="sa+r-d" value="sa+r-d" />
					<RadioButton labelText="mm" value="mm" />
					<RadioButton labelText="mm+d" value="mm+d" />
				</RadioButtonGroup>
			</div>
			<div class="selection">
				<RadioButtonGroup disabled={turn !== -1} legendText="Play as" bind:selected={playAs}>
					<RadioButton labelText="Noughts" value="O" />
					<RadioButton labelText="Crosses" value="X" />
				</RadioButtonGroup>
			</div>
			<Button disabled={!loaded || turn !== -1} on:click={() => {
				board = Array(9).fill(0);
				scores = [];
				turn = 2;
				human = Number(playAs === "O") + 1;
				bot = human % 2 + 1;
				completeTurn();
			}}>Start</Button>
		</Column>
	</Row>
	<hr/>
	<Row>
		<Column>
			<h2>Board</h2>
			<p>{status}</p>
			{#if loaded}
			<OXBoard
				disabled={turn !== human}
				board={board}
				iconMap={{
					1: OXXIcon,
					2: OXOIcon
				}}
				on:click={event => {
					board[event.detail.idx] = human;
					completeTurn();
				}}
			/>
			{:else}
			<h3>Loading...</h3>
			{/if}
		</Column>
	</Row>
	<hr/>
	<Row>
		<Column>
			<h2>Scores</h2>
			<p>
				View scores for each potential move here.
				Cells are numbered from 1 to 9 going left-to-right, top-to-bottom.
				The table headers show the cell numbers.
			</p>
			<Toggle labelText="Show previous scores" bind:toggled={showPreviousScores}/>
			<Toggle labelText="Show current scores" bind:toggled={showCurrentScores}/>
			<div class="selection">
				<RadioButtonGroup legendText="Shows scores for" bind:selected={showScoresFor}>
					<RadioButton labelText="You" value="human" />
					<RadioButton labelText="The algorithm" value="bot" />
				</RadioButtonGroup>
			</div>
			{#if showPreviousScores || showCurrentScores}
			<DataTable headers={[
					{
						key: "move",
						value: "Move"
					},
					...board.map((_, c) => {
						return {
							key: c.toString(),
							value: (c + 1).toString()
						};
					})
				]}
				rows={[
					...(showPreviousScores ?
						scores.slice(0, -1).map(scoreToRows(showScoresFor)) : []
					),
					...(showCurrentScores && scores.length !== 0 ?
						[scoreToRows(showScoresFor, "Now")(scores.slice(-1)[0], scores.length)] : []
					)
				]}
			/>
			{/if}
		</Column>
	</Row>
</Grid>

<style>
	h1, h2, h3 {
		margin-bottom: var(--cds-layout-01);
	}
	p, .selection {
		margin-top: var(--cds-layout-01);
		margin-bottom: var(--cds-layout-01);
	}
</style>
