import { Application, Assets, Graphics } from 'pixi';
import { SFSEvent, SFSObject, EventParam, ExtensionRequest, SpectatorToPlayerRequest, AddBuddyRequest } from 'sfs3-api';
import GameHeader from './objects/game-header.js';
import PlayerTag from './objects/player-tag.js';
import GameBoard from './objects/game-board.js';

const State = Object.freeze({
  WAITING_FOR_PLAYERS: 0,
  RUNNING: 1,
  GAME_WON: 2,
  GAME_LOST: 3,
  GAME_TIE: 4,
  INTERRUPTED: 5
});

export default class TicTacToeGameManager
{
    constructor(sfs, htmlContainer)
    {
        this.sfs = sfs;
        this.htmlContainer = htmlContainer;

        this.padding = 20;
    }

    async init()
    {
        this.app = new Application();

        // Intialize the application.
        await this.app.init({
            backgroundAlpha: 0,
            resizeTo: this.htmlContainer
        });

        // Add the application's canvas to the passed DOM element
        this.htmlContainer.appendChild(this.app.canvas);

        // Load font before use
        await Assets.load({
            src: 'assets/fonts/EraserDust.ttf',
            data: {
                family: 'EraserDust', // optional
            }
        });

        // Create header
        await this.initHeader();

        // Create player tags
        await this.initPlayerTags();

        // Create board
        await this.initBoard();

        //---------------------------------------------------------

        // Add SmartFoxServer-related event listeners required by this game
        this.sfs.addEventListener(SFSEvent.EXTENSION_RESPONSE, this.onExtensionResponse, this);
        this.sfs.addEventListener(SFSEvent.USER_ENTER_ROOM, this.onUserEnterRoom, this);
        this.sfs.addEventListener(SFSEvent.USER_EXIT_ROOM, this.onUserExitRoom, this);
        this.sfs.addEventListener(SFSEvent.SPECTATOR_TO_PLAYER, this.onSpectatorToPlayer, this);

        // Display player tags
        this.updatePlayerTags();

        // Set initial state
        this.state = State.WAITING_FOR_PLAYERS;

        // Display game state
        this.updateGameState();

        // Tell Room Extension that user is ready
        this.sfs.send(new ExtensionRequest('ready', new SFSObject(), this.sfs.lastJoinedRoom));
    }

    destroy()
    {
        // Destroy game application
        this.app.destroy(true, true);

        // Remove SmartFoxServer-related event listeners added by this game
        this.sfs.removeEventListener(SFSEvent.EXTENSION_RESPONSE, this.onExtensionResponse);
        this.sfs.removeEventListener(SFSEvent.USER_ENTER_ROOM, this.onUserEnterRoom);
        this.sfs.removeEventListener(SFSEvent.USER_EXIT_ROOM, this.onUserExitRoom);
        this.sfs.removeEventListener(SFSEvent.SPECTATOR_TO_PLAYER, this.onSpectatorToPlayer);
    }

    //----------------------------------------------------------
    // Initialization methods
    //----------------------------------------------------------

    async initHeader()
    {
        // Create board
        this.header = new GameHeader();
        await this.header.init();

        // Add header to stage
        this.app.stage.addChild(this.header);
        this.header.y = this.padding;

        // Add listener to "Restart" and "Join" buttons
        this.header.getRestartButton().on('pointerdown', (e) => {
            this.onRestartClick();
        });

        this.header.getJoinButton().on('pointerdown', (e) => {
            this.onJoinClick();
        });
    }

    async initPlayerTags()
    {
        // Create player tags
        this.playerTags = [];
        await this.initPlayerTag(1, 0);
        await this.initPlayerTag(2, 1);
    }

    async initPlayerTag(playerId, anchorX)
    {
        this.playerTags[playerId] = new PlayerTag(playerId, anchorX);
        await this.playerTags[playerId].init();

        this.app.stage.addChild(this.playerTags[playerId]);

        this.playerTags[playerId].visible = false;

        // Add listener to "Add buddy" button
        this.playerTags[playerId].getAddButton().on('pointerdown', (e) => {
            this.onAddBuddyClick(this.playerTags[playerId].userName);
        });
    }

    hidePlayerTag(playerId)
    {
        this.playerTags[playerId].visible = false;
    }

    async initBoard()
    {
        const boundOnBoardSlotClick = this.onBoardSlotClick.bind(this);

        // Create board
        this.board = new GameBoard(boundOnBoardSlotClick);
        await this.board.init();

        // Add board to stage
        this.app.stage.addChild(this.board);

        // Set board size
        const headerHeight = this.header.height + (this.padding * 2);
        const tagsHeight = this.playerTags[1].maxHeight + (this.padding * 2);
        const maxHeight = this.app.screen.height - tagsHeight - headerHeight;
        const maxWidth = this.app.screen.width;
        const size = Math.min(maxWidth, maxHeight);
        this.board.setSize(size);

        // Center board
        this.setBoardPosition();
    }

    setBoardPosition()
    {
        const headerHeight = this.header.height + (this.padding * 2);
        const tagsHeight = Math.max(this.playerTags[1].currentHeight, this.playerTags[2].currentHeight) + (this.padding * 2);
        const maxHeight = this.app.screen.height - tagsHeight - headerHeight;

        this.board.x = this.app.screen.width / 2;
        this.board.y = headerHeight + (maxHeight / 2);
    }

    //----------------------------------------------------------
    // Game methods
    //----------------------------------------------------------

    /**
     * Display current game state.
     */
    updateGameState()
    {
        if (this.state == State.WAITING_FOR_PLAYERS)
        {
            if (this.sfs.mySelf.isPlayer)
                this.header.setStateText('Waiting for your opponent');
            else
            {
                if (this.sfs.lastJoinedRoom.playerList.length < 2)
                    this.header.setStateText('Waiting for players;\ndo you want to join?', false, true);
            }
        }

        else if (this.state == State.INTERRUPTED)
        {
            if (this.sfs.mySelf.isPlayer)
            {
                let text = 'Your opponent left the game';

                // If the current Room is private, no other player will ever join it
                // If spectators could be invited in a private game, we should also check that no one is in the Room
                if (this.sfs.lastJoinedRoom.isPasswordProtected)
                    text += ';\nyou should leave too';
                else
                    text += ';\nwaiting for a new player';

                this.header.setStateText(text);
            }
            else
                this.header.setStateText('Game interrupted due to missing\nplayer; do you want to join?', false, true);
        }

        else if (this.state == State.RUNNING)
        {
            if (this.sfs.mySelf.isPlayer)
                this.header.setStateText(this.sfs.mySelf.playerId == this.whoseTurn ? 'It\'s your turn' : 'It\'s your opponent\'s turn');
            else
                this.header.setStateText(`It's player ${this.whoseTurn} turn`);
        }

        else
        {
            let text = 'GAME OVER';
            if (this.sfs.mySelf.isPlayer)
            {
                let color;

                switch (this.state)
                {
                    case State.GAME_WON:
                        text += '\nYou are the winner!';
                        color = '#00ff00';
                        break;

                    case State.GAME_LOST:
                        text += '\nYou\'ve lost!';
                        color = '#c94232';
                        break;

                    case State.GAME_TIE:
                        text += '\nIt\'s a tie!';
                        color = '#ffff00';
                        break;
                }

                this.header.setStateText(text, true, false, color);
            }
            else
            {
                switch (this.state)
                {
                    case State.GAME_TIE:
                        text += '\nIt\'s a tie!';
                        break;

                    default:
                        text += `\nPlayer ${this.lastWinner} is the winner!`;
                        break;
                }

                this.header.setStateText(text);
            }
        }

        // Resize state container if needed
        this.header.scale = 1;
        if (this.header.width > this.app.screen.width + this.padding * 2)
            this.header.width = this.app.screen.width - this.padding * 2;

        // Center state container
        this.header.x = (this.app.screen.width - this.header.width) / 2;

        // Re-center game board
        this.setBoardPosition();
    }

    /**
     * Display current players tags.
     */
    updatePlayerTags()
    {
        for (let user of this.sfs.lastJoinedRoom.playerList)
        {
            const i = user.playerId;
            const tag = this.playerTags[i];

            if (tag != null)
            {
                tag.visible = true;
                tag.userName = user.name;
                tag.wins = tag.wins;
                tag.showAddButton(!user.isItMe && (this.sfs.buddyManager.getBuddyByName(user.name) == null || this.sfs.buddyManager.getBuddyByName(user.name).isTemp));

                // Set positioning
                if (i == 1)
                {
                    tag.x = this.padding;
                    tag.y = this.app.screen.height - tag.currentHeight - this.padding
                }
                else if (i == 2)
                {
                    tag.x = this.app.screen.width - this.padding;
                    tag.y = this.app.screen.height - tag.currentHeight - this.padding
                }
            }
        }
        
        // Re-center game board
        this.setBoardPosition();
    }
    
    /**
    * Start the game.
    */
    startGame(whoseTurn, p1Wins, p2Wins)
    {
        this.whoseTurn = whoseTurn;

        // Reset game board
        this.board.reset();

        // Set game state
        this.state = State.RUNNING;

        // Display game state
        this.updateGameState();

        // Display player wins
        this.playerTags[1].wins = p1Wins;
        this.playerTags[2].wins = p2Wins;

        // Enable game board
        if (this.sfs.mySelf.isPlayer)
            this.board.isEnabled = true;
    }

    /**
     * Stop the game.
     */
    stopGame()
    {
        // Set game state
        this.state = State.INTERRUPTED;

        // Display game state
        this.updateGameState();

        // Disable game board
        this.board.isEnabled = false;
    }

    /**
     * Spectator receives board update. If match isn't started yet,
     * a message is displayed and they can click the join button
     */
    setSpectatorBoard(whoseTurn, gameRunning, boardData, p1Wins, p2Wins, lastWinner)
    {
        this.whoseTurn = whoseTurn;

        // Set game state
        if (gameRunning)
            this.state = State.RUNNING;

        // Display game state
        this.updateGameState();

        // Display player wins
        this.playerTags[1].wins = p1Wins;
        this.playerTags[2].wins = p2Wins;

        // Update board
        // NOTE: the board data is a SFSArray (the board rows) containing three other SFSArrays (the columns)
        // As SFSArrays are 0-based, we have to convert to board coordinates
        for (let i = 0; i < 3; i++)
        {
            const values = boardData.getNumberArray(i);

            for (let j = 0; j < 3; j++)
            {
                const row = i + 1;
                const col = j + 1;

                this.board.setMark(row, col, values[j]);
            }
        }
console.log(this.lastWinner)
        if (this.lastWinner > -1)
            this.declareWinner(lastWinner);
    }

    /**
     * On move received, add mark to game board.
     */
    executeMove(playerId, row, col)
    {
        // Set new turn
        this.whoseTurn = (playerId == 1) ? 2 : 1;

        // Display game state
        this.updateGameState();

        // Show mark on game board
        this.board.setMark(row, col, playerId);
    }

    /**
     * Declare game winner.
     */
    declareWinner(winnerId)
    {
        this.lastWinner = winnerId;

        // Set game state
        if (winnerId > 0)
        {
            // Current user is the winner
            if (this.sfs.mySelf.playerId == winnerId)
                this.state = State.GAME_WON;

            // Opponent is the winner
            else
                this.state = State.GAME_LOST;

            // Update player wins
            this.playerTags[winnerId].wins++;
        }
        else
            this.state = State.GAME_TIE;

        // Display game state
        this.updateGameState();

        // Disable game board
        this.board.isEnabled = false;
    }

    //----------------------------------------------------------
    // UI event listeners
    //----------------------------------------------------------

    /**
     * On Add buddy button click, add oppontent to the user's Buddy List.
     */
    onAddBuddyClick(userName)
    {
        this.sfs.send(new AddBuddyRequest(userName));
    }

    /**
     * On Restart button click, start new game.
     */
    onRestartClick()
    {
        // Send request to Extension
		this.sfs.send(new ExtensionRequest('restart', null, this.sfs.lastJoinedRoom));
    }

    /**
     * On Join button click, turn current user from spectator to player.
     */
    onJoinClick()
    {
        // Send request to server
		this.sfs.send(new SpectatorToPlayerRequest());
    }

    /**
     * Listen to custom click event dispatched by GameBoard instance.
     */
    onBoardSlotClick(row, col)
    {
        // Only player in turn is allowed to interact with the board when the game is running
        if (this.state == State.RUNNING && this.sfs.mySelf.playerId == this.whoseTurn)
        {
            const move = new SFSObject();
            move.putInt('r', row);
            move.putInt('c', col);

            // Send move to Extension
            this.sfs.send(new ExtensionRequest('move', move, this.sfs.lastJoinedRoom));
        }
    }

    //----------------------------------------------------------
    // SmartFoxServer event listeners
    //----------------------------------------------------------

    onUserEnterRoom(evtParams)
    {
        this.updatePlayerTags();
    }

    onUserExitRoom(evtParams)
    {
        const user = evtParams[EventParam.User];

        if (!user.isItMe && user.isPlayer)
            this.hidePlayerTag(user.playerId)
    }

    onSpectatorToPlayer(evtParams)
    {
        this.updateGameState();
        this.updatePlayerTags();
    }

    onExtensionResponse(evtParams)
    {
        const cmd = evtParams[EventParam.Cmd];
        const params = evtParams[EventParam.ExtParams];

        switch (cmd)
        {
            case 'start':
                this.startGame(params.getNumber('t'), params.getNumber('p1w'), params.getNumber('p2w'));
                break;

            case 'state':
                this.setSpectatorBoard(params.getNumber('t'), params.getBool('run'), params.getSFSArray('board'), params.getNumber('p1w'), params.getNumber('p2w'), params.containsKey('w') ? params.getNumber('w') : -1);
                break;

            case 'stop':
                this.stopGame();
                break;

            case 'move':
                this.executeMove(params.getNumber('t'), params.getNumber('r'), params.getNumber('c'));
                break;

            case 'over':
                this.declareWinner(params.getNumber('w'));
                break;
        }
    }
}

