// See https://aka.ms/new-console-template for more information

using Models;
using Models.Events;
using Models.Exceptions;
using Models.Game;

namespace ConsoleApp;

class Program
{
    /// <summary>
    /// Main function of the console app
    /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args)
    {
        Console.WriteLine("Enter your pseudo:");
        string? pseudo = Console.ReadLine();
        if (pseudo != null)
        {
            Player player = new Player(pseudo);

            Map map = new Map("background");
            Game game = new Game(player, map);

            // Abonnement aux événements
            game.GameStarted += OnGameStarted!;
            game.GameEnded += OnGameEnded!;
            game.BoardUpdated += OnBoardUpdated!;
            game.DiceRolled += OnDiceRolled!;
            game.OperationChosen += OnOperationChosen!;
            game.CellChosen += OnCellChosen!;

            // Initialisation
            game.InitializeGame();
        }
    }

    /// <summary>
    /// Handles the event when the game has started.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void OnGameStarted(object sender, GameStartedEventArgs e)
    {
        Console.WriteLine($"The game has started! Player: {e.CurrentPlayer.Pseudo}");
    }

    /// <summary>
    /// Handles the event when the game has ended.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void OnGameEnded(object sender, GameEndedEventArgs e)
    {
        Console.WriteLine($"The game has ended! Player: {e.CurrentPlayer.Pseudo}");
        Console.WriteLine($"Points: {e.Point}");
    }

    /// <summary>
    /// Handles the event when the board is updated.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void OnBoardUpdated(object sender, EventArgs e)
    {
        DisplayBoard(((Game)sender).UsedMap);
        DisplayOperationTable(((Game)sender).UsedMap.OperationGrid);
    }

    /// <summary>
    /// Handles the event when the dice are rolled.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void OnDiceRolled(object sender, DiceRolledEventArgs e)
    {
        Console.WriteLine($"Dice 1: {e.Dice1Value} | Dice 2: {e.Dice2Value}");
        Operation playerOperation = GetPlayerOperation(sender);
        ((Game)sender).HandlePlayerOperation(playerOperation);
    }

    /// <summary>
    /// Handles the event when an operation is chosen by the player.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void OnOperationChosen(object sender, OperationChosenEventArgs e)
    {
        Console.WriteLine($"Operation: {e.Operation}, Result: {e.Result}");
        DisplayOperationTable(((Game)sender).UsedMap.OperationGrid);
        Cell playerChoice = GetPlayerChoice();
        bool test = ((Game)sender).HandlePlayerChoice(playerChoice, e.Result);
        if(!test)
        {
            Console.WriteLine("Invalid cell. Please choose again.");
            Console.WriteLine();
            Console.WriteLine();
            DisplayBoard(((Game)sender).UsedMap);
            OnOperationChosen(sender, e);
        }



        /*
        try
        {
            ((Game)sender).HandlePlayerChoice(playerChoice, e.Result);
        }
        catch (InvalidCellCoordinatesException err)
        {
            Console.WriteLine(err.Message);
            playerChoice = GetPlayerChoice();
            ((Game)sender).HandlePlayerChoice(playerChoice, e.Result);
        }
        catch (InvalidCellException err)
        {
            Console.WriteLine(err.Message);
            playerChoice = GetPlayerChoice();
            ((Game)sender).HandlePlayerChoice(playerChoice, e.Result);
        }
        catch (InvalidPlaceResultException err)
        {
            Console.WriteLine(err.Message);
            playerChoice = GetPlayerChoice();
            ((Game)sender).HandlePlayerChoice(playerChoice, e.Result);
        }
        */
    }

    /// <summary>
    /// Handles the event when a cell is chosen by the player.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void OnCellChosen(object sender, CellChosenEventArgs e)
    {
        Console.WriteLine($"Cell chosen: ({e.Cell.X}, {e.Cell.Y}) with result: {e.Result}");
        DisplayBoard(((Game)sender).UsedMap);
    }

    /// <summary>
    /// Displays the game board.
    /// </summary>
    /// <param name="map">Used map to display</param>
    static void DisplayBoard(Map map)
    {
        int cpt = 0;
        for (int i = 0; i < map.Boards.Count; i++)
        {
            if (cpt % 6 == 0)
            {
                Console.Write("| ");
            }

            if (map.Boards[i].Value.HasValue)
            {
                Console.Write(map.Boards[i].Value?.ToString().PadLeft(2));
            }
            else
            {
                Console.Write(" O");
            }

            cpt++;
            if (cpt % 6 == 0)
            {
                Console.Write(" |");
                Console.WriteLine();
            }
        }
    }

    /// <summary>
    /// Displays the operation table.
    /// </summary>
    /// <param name="operationTable">The operation table to display.</param>
    static void DisplayOperationTable(List<OperationCell> operationTable)
    {
        Console.WriteLine("Operation Table:");
        string[] operations = { "Addition (+)", "Subtraction (-)", "Multiplication (*)", "Lower Dice (less)", "Higher Dice (high)" };

        for (int i = 0; i < operations.Length; i++)
        {
            Console.Write(operations[i].PadRight(18));

            for (int j = 0; j < 4; j++)
            {
                int index = i * 4 + j;
                if (index < operationTable.Count)
                {
                    string status = operationTable[index].IsChecked ? "X" : " ";
                    Console.Write($" | {status}");
                }
            }
            Console.WriteLine();
        }
    }

    /// <summary>
    /// Gets the cell chosen by the player.
    /// </summary>
    /// <returns>The cell chosen by the player.</returns>
    static Cell GetPlayerChoice()
    {
        int row, column;
        while (true)
        {
            Console.WriteLine("Enter the position of the cell you want to play");
            Console.WriteLine("Enter the row number (0-5)");
            if (!int.TryParse(Console.ReadLine(), out row) || row < 0 || row >= 6)
            {
                Console.WriteLine("Invalid row number. Please enter a number between 0 and 5.");
                continue;
            }

            Console.WriteLine("Enter the column number (0-5)");
            if (!int.TryParse(Console.ReadLine(), out column) || column < 0 || column >= 6)
            {
                Console.WriteLine("Invalid column number. Please enter a number between 0 and 5.");
                continue;
            }

            return new Cell(row, column);
        }
    }

    /// <summary>
    /// Gets the operation chosen by the player.
    /// </summary>
    /// <returns></returns>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    static Operation GetPlayerOperation(object? sender)
    {
        DisplayOperationOptions();
        string? op = Console.ReadLine();
        while (op != "1" && op != "2" && op != "3" && op != "4" && op != "5")
        {
            Console.WriteLine("Invalid operation. Please choose again.");
            op = Console.ReadLine();
        }
        int test = Convert.ToInt32(op);
        
        while(((Game)sender).UsedMap.CheckOperationPossible(test-1))
        {
            Console.WriteLine("Invalid operation. Please choose again.");
            Console.WriteLine();
            op = Console.ReadLine();
            while (op != "1" && op != "2" && op != "3" && op != "4" && op != "5")
            {
                Console.WriteLine("Invalid operation. Please choose again.");
                op = Console.ReadLine();
            }
            test = Convert.ToInt32(op);
        }

        return op switch
        {
            "1" => Operation.ADDITION,
            "2" => Operation.SUBTRACTION,
            "3" => Operation.MULTIPLICATION,
            "4" => Operation.LOWER,
            "5" => Operation.HIGHER,
            _ => throw new ArgumentOutOfRangeException()
        };
    }

    /// <summary>
    /// Displays the operation options for the player to choose from.
    /// </summary>
    static void DisplayOperationOptions()
    {
        Console.WriteLine("Choose an operation:");
        Console.WriteLine("1: Addition (+)");
        Console.WriteLine("2: Subtraction (-)");
        Console.WriteLine("3: Multiplication (*)");
        Console.WriteLine("4: Lower Dice (less)");
        Console.WriteLine("5: Higher Dice (high)");
    }
}