import { CommonMancalaGame, MancalaGame } from "mancala.js";
import { RTMT } from "../rtmt/rtmt";
import { GameStore } from "./gamestore/GameStore";
import { generateKey } from "../util/key_factory";
import {
    channel_cancel_new_game,
    channel_game_move,
    channel_leave_game,
    channel_listen_game_events,
    channel_new_game,
    channel_on_game_crashed,
    channel_on_game_start,
    channel_on_game_update,
    channel_on_game_user_leave,
    channel_on_user_connection_change,
    channel_unlisten_game_events
} from "@mancala/core";
import { GameMove } from "@mancala/core";
import { GameCrashManager } from "./GameCrashManager";
import { MatchMaker } from "../matchmaker/MatchMaker";
import { GameUsersConnectionInfo } from "@mancala/core";
import { BackendGame } from "../models/Game";

export class GameManager {
    gameStore: GameStore;
    rtmt: RTMT;
    matchMaker: MatchMaker;

    constructor(params: { gameStore: GameStore, rtmt: RTMT, matchMaker: MatchMaker }) {
        this.gameStore = params.gameStore;
        this.rtmt = params.rtmt;
        this.matchMaker = params.matchMaker;
        this.initialize();
    }

    private initialize() {
        this.listenOnPlayersPaired();
        this.listenRtmtMessages();
        this.listenUserConnectionChange();
    }

    private listenRtmtMessages() {
        this.rtmt.listenMessage(channel_new_game, (userKey: string, message: Object) => {
            this.matchMaker.join(userKey);
        });

        this.rtmt.listenMessage(channel_cancel_new_game, (userKey: string, message: Object) => {
            this.matchMaker.cancel(userKey);
        });

        this.rtmt.listenMessage(channel_game_move, (userKey: string, message: Object) => {
            this.onGameMove(userKey, message as GameMove);
        });

        this.rtmt.listenMessage(channel_leave_game, (userKey: string, message: Object) => {
            this.onPlayerLeave(userKey)
        });

        this.rtmt.listenMessage(channel_listen_game_events, (userKey: string, message: Object) => {
            this.addSpectator(userKey, message as string);
        });

        this.rtmt.listenMessage(channel_unlisten_game_events, (userKey: string, message: Object) => {
            this.removeSpectator(userKey, message as string);
        });
    }

    private onGameMove(userKey: string, gameMove: GameMove) {
        const game = this.gameStore.getGameByUser(userKey);
        if (game) {
            try {
                game.mancalaGame.moveByPlayerPit(userKey, gameMove.index);
                game.gameUsersConnectionInfo = this.getGameUsersConnectionInfo(game);
                this.sendMessageToPlayersAndSpectators(game, channel_on_game_update, game);
                if (game.mancalaGame.state == "ended") {
                    this.onGameEnd(game);
                }
            } catch (err: any) {
                this.onGameError(game, err)
            }
        } else {
            console.log("Game not found!");
        }
    }

    private onGameError(game: BackendGame, error: any) {
        console.error(error);
        const crashFileName = GameCrashManager.logGameCrash(error, game);
        console.info(`Game crash saved to file : ${crashFileName}`);
        this.sendMessageToPlayersAndSpectators(game, channel_on_game_crashed, error);
    }

    private onPlayerLeave(userKey: string) {
        const game = this.gameStore.getGameByUser(userKey);
        if (game) {
            this.deleteGame(game);
            this.sendMessageToPlayersAndSpectators(game, channel_on_game_user_leave, userKey)
        }
    }

    private addSpectator(userId: string, gameId: string) {
        const game = this.gameStore.get(gameId);
        if (game) {
            const isUserPlayer = this.checkUserIdisPlayer(game, userId);
            const isAlreadySpectator = game.spectatorIds.find((value) => value === userId);
            if (!isAlreadySpectator && !isUserPlayer) {
                game.spectatorIds.push(userId);
            }
        }
    }

    private removeSpectator(userId: string, gameId: string) {
        const game = this.gameStore.get(gameId);
        if (game) {
            const userIdIndex = game.spectatorIds.findIndex((value) => value === userId);
            if (userIdIndex >= 0) {
                game.spectatorIds.splice(userIdIndex, 1);
            }
        }
    }

    private checkUserIdisPlayer(game: BackendGame, userId: string): boolean {
        return game.mancalaGame.player1Id === userId || game.mancalaGame.player2Id === userId;
    }

    private sendMessageToPlayersAndSpectators(game: BackendGame, channel: string, message: Object) {
        const sendMessage = (userId: string) => this.rtmt.sendMessage(userId, channel, message);
        sendMessage(game.mancalaGame.player1Id);
        sendMessage(game.mancalaGame.player2Id);
        game.spectatorIds.forEach((spectatorId) => sendMessage(spectatorId));
    }

    private onGameEnd(game: BackendGame) {
        this.deleteGame(game);
        game.spectatorIds = [];
    }

    private listenOnPlayersPaired() {
        this.matchMaker.listenOnPlayersPaired((player1Id: string, player2Id: string) => {
            const game = this.createMancalaGame(player1Id, player2Id);
            this.startGame(game);
        });
    }

    public fireOnPlayerConnected(playerId: string) { }

    public createMancalaGame(userKey1: string, userKey2: string): BackendGame {
        const mancalaGame = new CommonMancalaGame(generateKey(), userKey1, userKey2);
        const random = Math.random();
        mancalaGame.turnPlayerId = random > 0.5 ? userKey1 : userKey2;

        const game: BackendGame = {
            id: mancalaGame.id,
            mancalaGame,
            gameUsersConnectionInfo: this.getGameUsersConnectionInfoFromUsers(mancalaGame.player1Id, mancalaGame.player2Id),
            spectatorIds: []
        };

        this.gameStore.set(game.id, game);
        this.gameStore.setGameByUser(userKey1, game);
        this.gameStore.setGameByUser(userKey2, game);
        return game;
    }

    public getGameUsersConnectionInfo(game: BackendGame): GameUsersConnectionInfo {
        return this.getGameUsersConnectionInfoFromUsers(game.mancalaGame.player1Id, game.mancalaGame.player2Id);
    }

    public getGameUsersConnectionInfoFromUsers(user1Id: string, user2Id: string): GameUsersConnectionInfo {
        const isPlayer1Online = this.rtmt.isClientOnline(user1Id);
        const isPlayer2Online = this.rtmt.isClientOnline(user2Id);
        return {
            user1ConnectionInfo: { userId: user1Id, isOnline: isPlayer1Online },
            user2ConnectionInfo: { userId: user2Id, isOnline: isPlayer2Online }
        };
    }

    public startGame(game: BackendGame) {
        this.sendMessageToPlayersAndSpectators(game, channel_on_game_start, game);
        this.sendUserConnectionInfo(game);
        this.sendUserConnectionInfo(game);
    }

    public sendUserConnectionInfo(game: BackendGame) {
        const gameUsersConnectionInfo = this.getGameUsersConnectionInfo(game);
        this.sendMessageToPlayersAndSpectators(game, channel_on_user_connection_change, gameUsersConnectionInfo);
    }

    public deleteGame(game: BackendGame) {
        if (game) {
            this.gameStore.removeGameByPlayer(game.mancalaGame.player1Id);
            this.gameStore.removeGameByPlayer(game.mancalaGame.player2Id);
        }
    }

    private listenUserConnectionChange() {
        this.rtmt.listenOnClientConnectionChange((clientId: string, isOnline: boolean) => {
            const game = this.gameStore.getGameByUser(clientId);
            if (game) this.sendUserConnectionInfo(game);
            if (!isOnline) {
                const speactatingGameIds = this.gameStore.getSpeactatingGameIdsByUser(clientId);
                speactatingGameIds.forEach((gameId) => this.removeSpectator(clientId, gameId));
            } else {
                game && this.rtmt.sendMessage(clientId, channel_on_game_update, game);
            }
        });
    }
}