<!--
SPDX-FileCopyrightText: syuilo and other misskey contributors
SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<MkSpacer :contentMax="600">
	<div :class="$style.root" class="_gaps">
		<header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ i18n.ts._reversi.black }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ i18n.ts._reversi.white }})</header>

		<div style="overflow: clip; line-height: 28px;">
			<div v-if="!iAmPlayer && !game.isEnded && turnUser" class="turn">
				<Mfm :key="'turn:' + turnUser.id" :text="i18n.tsx._reversi.turnOf({ name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
				<MkEllipsis/>
			</div>
			<div v-if="(logPos !== logs.length) && turnUser" class="turn">
				<Mfm :key="'past-turn-of:' + turnUser.id" :text="i18n.tsx._reversi.pastTurnOf({ name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
			</div>
			<div v-if="iAmPlayer && !game.isEnded && !isMyTurn" class="turn1">{{ i18n.ts._reversi.opponentTurn }}<MkEllipsis/></div>
			<div v-if="iAmPlayer && !game.isEnded && isMyTurn" class="turn2" style="animation: tada 1s linear infinite both;">{{ i18n.ts._reversi.myTurn }}</div>
			<div v-if="game.isEnded && logPos == logs.length" class="result">
				<template v-if="game.winner">
					<Mfm :key="'won'" :text="i18n.tsx._reversi.won({ name: game.winner.name ?? game.winner.username })" :plain="true" :customEmojis="game.winner.emojis"/>
					<span v-if="game.surrendered != null"> ({{ i18n.ts._reversi.surrendered }})</span>
				</template>
				<template v-else>{{ i18n.ts._reversi.drawn }}</template>
			</div>
		</div>

		<div :class="$style.board">
			<div v-if="showBoardLabels" :class="$style.labelsX">
				<span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
			</div>
			<div style="display: flex;">
				<div v-if="showBoardLabels" :class="$style.labelsY">
					<div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
				</div>
				<div :class="$style.boardCells" :style="cellsStyle">
					<div
						v-for="(stone, i) in engine.board"
						v-tooltip="`${String.fromCharCode(65 + engine.posToXy(i)[0])}${engine.posToXy(i)[1] + 1}`"
						:class="[$style.boardCell, {
							[$style.boardCell_empty]: stone == null,
							[$style.boardCell_none]: engine.map[i] === 'null',
							[$style.boardCell_isEnded]: game.isEnded,
							[$style.boardCell_myTurn]: !game.isEnded && isMyTurn,
							[$style.boardCell_can]: turnUser ? engine.canPut(turnUser.id === blackUser.id, i) : null,
							[$style.boardCell_prev]: engine.prevPos === i
						}]"
						@click="putStone(i)"
					>
						<img v-if="stone === true" style="pointer-events: none; user-select: none; display: block; width: 100%; height: 100%;" :src="blackUser.avatarUrl">
						<img v-if="stone === false" style="pointer-events: none; user-select: none; display: block; width: 100%; height: 100%;" :src="whiteUser.avatarUrl">
					</div>
				</div>
				<div v-if="showBoardLabels" :class="$style.labelsY">
					<div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
				</div>
			</div>
			<div v-if="showBoardLabels" :class="$style.labelsX">
				<span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
			</div>
		</div>

		<div class="status"><b>{{ i18n.tsx._reversi.turnCount({ count: logPos }) }}</b> {{ i18n.ts._reversi.black }}:{{ engine.blackCount }} {{ i18n.ts._reversi.white }}:{{ engine.whiteCount }} {{ i18n.ts._reversi.total }}:{{ engine.blackCount + engine.whiteCount }}</div>

		<div v-if="!game.isEnded && iAmPlayer" class="_buttonsCenter">
			<MkButton danger @click="surrender">{{ i18n.ts._reversi.surrender }}</MkButton>
		</div>

		<div v-if="game.isEnded" class="_panel _gaps_s" style="padding: 16px;">
			<div>{{ logPos }} / {{ logs.length }}</div>
			<div v-if="!autoplaying" class="_buttonsCenter">
				<MkButton :disabled="logPos === 0" @click="logPos = 0"><i class="ti ti-chevrons-left"></i></MkButton>
				<MkButton :disabled="logPos === 0" @click="logPos--"><i class="ti ti-chevron-left"></i></MkButton>
				<MkButton :disabled="logPos === logs.length" @click="logPos++"><i class="ti ti-chevron-right"></i></MkButton>
				<MkButton :disabled="logPos === logs.length" @click="logPos = logs.length"><i class="ti ti-chevrons-right"></i></MkButton>
			</div>
			<MkButton style="margin: auto;" :disabled="autoplaying" @click="autoplay()"><i class="ti ti-player-play"></i></MkButton>
		</div>

		<div>
			<p v-if="game.isLlotheo">{{ i18n.ts._reversi.isLlotheo }}</p>
			<p v-if="game.loopedBoard">{{ i18n.ts._reversi.loopedMap }}</p>
			<p v-if="game.canPutEverywhere">{{ i18n.ts._reversi.canPutEverywhere }}</p>
		</div>

		<MkA v-if="game.isEnded" :to="`/reversi`">
			<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; width: 200px; margin: auto;"/>
		</MkA>
	</div>
</MkSpacer>
</template>

<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue';
import * as CRC32 from 'crc-32';
import * as Misskey from 'misskey-js';
import * as Reversi from 'misskey-reversi';
import MkButton from '@/components/MkButton.vue';
import { deepClone } from '@/scripts/clone.js';
import { useInterval } from '@/scripts/use-interval.js';
import { signinRequired } from '@/account.js';
import { i18n } from '@/i18n.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { userPage } from '@/filters/user.js';

const $i = signinRequired();

const props = defineProps<{
	game: Misskey.entities.ReversiGameDetailed;
	connection: Misskey.ChannelConnection;
}>();

const showBoardLabels = true;
const autoplaying = ref<boolean>(false);
const game = ref<Misskey.entities.ReversiGameDetailed>(deepClone(props.game));
const logs = ref<Misskey.entities.ReversiLog[]>(game.value.logs);
const logPos = ref<number>(logs.value.length);
const engine = shallowRef<Reversi.Game>(new Reversi.Game(game.value.map, {
	isLlotheo: game.value.isLlotheo,
	canPutEverywhere: game.value.canPutEverywhere,
	loopedBoard: game.value.loopedBoard,
}));

for (const log of game.value.logs) {
	engine.value.put(log.color, log.pos);
}

const iAmPlayer = computed(() => {
	return game.value.user1Id === $i.id || game.value.user2Id === $i.id;
});

const myColor = computed(() => {
	if (!iAmPlayer.value) return null;
	if (game.value.user1Id === $i.id && game.value.black === 1) return true;
	if (game.value.user2Id === $i.id && game.value.black === 2) return true;
	return false;
});

const opColor = computed(() => {
	if (!iAmPlayer.value) return null;
	return !myColor.value;
});

const blackUser = computed(() => {
	return game.value.black === 1 ? game.value.user1 : game.value.user2;
});

const whiteUser = computed(() => {
	return game.value.black === 1 ? game.value.user2 : game.value.user1;
});

const turnUser = computed(() => {
	if (engine.value.turn === true) {
		return game.value.black === 1 ? game.value.user1 : game.value.user2;
	} else if (engine.value.turn === false) {
		return game.value.black === 1 ? game.value.user2 : game.value.user1;
	} else {
		return null;
	}
});

const isMyTurn = computed(() => {
	if (!iAmPlayer.value) return false;
	const u = turnUser.value;
	if (u == null) return false;
	return u.id === $i.id;
});

const cellsStyle = computed(() => {
	return {
		'grid-template-rows': `repeat(${game.value.map.length}, 1fr)`,
		'grid-template-columns': `repeat(${game.value.map[0].length}, 1fr)`,
	};
});

watch(logPos, (v) => {
	if (!game.value.isEnded) return;
	const _o = new Reversi.Game(game.value.map, {
		isLlotheo: game.value.isLlotheo,
		canPutEverywhere: game.value.canPutEverywhere,
		loopedBoard: game.value.loopedBoard,
	});
	for (const log of logs.value.slice(0, v)) {
		_o.put(log.color, log.pos);
	}
	engine.value = _o;
});

if (game.value.isStarted && !game.value.isEnded) {
	useInterval(() => {
		if (game.value.isEnded) return;
		const crc32 = CRC32.str(logs.value.map(x => x.pos.toString()).join(''));
		props.connection.send('syncState', {
			crc32: crc32,
		});
	}, 5000, { immediate: false, afterMounted: true });
}

function putStone(pos) {
	if (game.value.isEnded) return;
	if (!iAmPlayer.value) return;
	if (!isMyTurn.value) return;
	if (!engine.value.canPut(myColor.value!, pos)) return;

	engine.value.put(myColor.value!, pos);
	triggerRef(engine);

	// サウンドを再生する
	//sound.play(myColor.value ? 'reversiPutBlack' : 'reversiPutWhite');

	props.connection.send('putStone', {
		pos: pos,
	});

	checkEnd();
}

function onPutStone(x) {
	logs.value.push(x);
	logPos.value++;
	engine.value.put(x.color, x.pos);
	triggerRef(engine);
	checkEnd();

	// サウンドを再生する
	if (x.color !== myColor.value) {
		//sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite');
	}
}

function onEnded(x) {
	game.value = deepClone(x.game);
}

function checkEnd() {
	game.value.isEnded = engine.value.isEnded;
	if (game.value.isEnded) {
		if (engine.value.winner === true) {
			game.value.winnerId = game.value.black === 1 ? game.value.user1Id : game.value.user2Id;
			game.value.winner = game.value.black === 1 ? game.value.user1 : game.value.user2;
		} else if (engine.value.winner === false) {
			game.value.winnerId = game.value.black === 1 ? game.value.user2Id : game.value.user1Id;
			game.value.winner = game.value.black === 1 ? game.value.user2 : game.value.user1;
		} else {
			game.value.winnerId = null;
			game.value.winner = null;
		}
	}
}

function onRescue(_game) {
	game.value = deepClone(_game);

	engine.value = new Reversi.Game(game.value.map, {
		isLlotheo: game.value.isLlotheo,
		canPutEverywhere: game.value.canPutEverywhere,
		loopedBoard: game.value.loopedBoard,
	});

	for (const log of game.value.logs) {
		engine.value.put(log.color, log.pos);
	}

	triggerRef(engine);

	logs.value = game.value.logs;
	logPos.value = logs.value.length;

	checkEnd();
}

function surrender() {
	misskeyApi('reversi/surrender', {
		gameId: game.value.id,
	});
}

function autoplay() {
	autoplaying.value = true;
	logPos.value = 0;

	window.setTimeout(() => {
		logPos.value = 1;

		let i = 1;
		let previousLog = game.value.logs[0];
		const tick = () => {
			const log = game.value.logs[i];
			const time = new Date(log.at).getTime() - new Date(previousLog.at).getTime();
			setTimeout(() => {
				i++;
				logPos.value++;
				previousLog = log;

				if (i < game.value.logs.length) {
					tick();
				} else {
					autoplaying.value = false;
				}
			}, time);
		};

		tick();
	}, 1000);
}

onMounted(() => {
	props.connection.on('putStone', onPutStone);
	props.connection.on('rescue', onRescue);
	props.connection.on('ended', onEnded);
});

onUnmounted(() => {
	props.connection.off('putStone', onPutStone);
	props.connection.off('rescue', onRescue);
	props.connection.off('ended', onEnded);
});
</script>

<style lang="scss" module>
@use "sass:math";

$label-size: 16px;
$gap: 4px;

.root {
	text-align: center;
}

.board {
	width: calc(100% - 16px);
	max-width: 500px;
	margin: 0 auto;
}

.labelsX {
	height: $label-size;
	padding: 0 $label-size;
	display: flex;
}

.labelsXLabel {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 0.8em;

	&:first-child {
		margin-left: -(math.div($gap, 2));
	}

	&:last-child {
		margin-right: -(math.div($gap, 2));
	}
}

.labelsY {
	width: $label-size;
	display: flex;
	flex-direction: column;
}

.labelsYLabel {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 12px;

	&:first-child {
		margin-top: -(math.div($gap, 2));
	}

	&:last-child {
		margin-bottom: -(math.div($gap, 2));
	}
}

.boardCells {
	flex: 1;
	display: grid;
	grid-gap: $gap;
}

.boardCell {
	background: transparent;
	border-radius: 6px;
	overflow: clip;

	&.boardCell_empty {
		border: solid 2px var(--divider);
	}

	&.boardCell_empty.boardCell_can {
		border-color: var(--accent);
		opacity: 0.5;
	}

	&.boardCell_empty.boardCell_myTurn {
		border-color: var(--divider);
		opacity: 1;

		&.boardCell_can {
			border-color: var(--accent);
			cursor: pointer;

			&:hover {
				background: var(--accent);
			}
		}
	}

	&.boardCell_prev {
		box-shadow: 0 0 0 4px var(--accent);
	}

	&.boardCell_isEnded {
		border-color: var(--divider);
	}

	&.boardCell_none {
		border-color: transparent !important;
	}
}
</style>