// Include necessary C++ standard libraries for input/output, file handling, vectors, and randomization.
// Include the input/output stream library, which provides functionality for reading from and writing to the console.
#include <iostream> // For input/output operations
// Include the file stream library, allowing for file input and output operations.
#include <fstream> // For file input/output operations
// Include the vector library, which provides a dynamic array implementation (vector) in C++.
#include <vector> // For dynamic arrays (vectors)
// Include the cstdlib library, which provides functions involving memory allocation, random numbers, and other general utilities.
#include <cstdlib> // For general utilities
// Include the ctime library, which provides functions related to time and date manipulation.
#include <ctime> // For time and date manipulation

// Use the standard C++ namespace for easier access to standard library elements.
using namespace std;

// Define constants representing different types of terrain on the game map.
const char WALL = '#';   // Character representing a wall on the map.
const char GRASS = '-';  // Character representing grass terrain on the map.
const char PLAIN = ' ';  // Character representing plain ground on the map.
const char ENEMY = 'X';  // Character representing an enemy on the map.
const char PLAYER = 'O'; // Character representing the player on the map.
const char HIDDEN = 'H'; // Character representing a hidden (safe) tile on the map.

struct Point
{
    int x; // X-coordinate of the point.
    int y; // Y-coordinate of the point.
};

class Game
{
private:
    // Map representation.
    char **map;
    // Size of the map (square).
    int size;
    // Player's position.
    Point player;
    // Array of enemy positions.
    Point enemies[3];
    // Hidden tile position.
    Point hidden;

public:
    /**
     * @brief Constructor to initialize the game with a map loaded from a file.
     * @param filename The name of the file containing the map layout.
     */
    Game(const char *filename)
    {
        loadMap(filename);
        // Seed random number generator for enemy movement.
        srand(time(0));
    }

    /**
     * @brief Destructor to free dynamically allocated memory for the map.
     */
    ~Game()
    {
        for (int i = 0; i < size; ++i)
            delete[] map[i];
        delete[] map;
    }

    // Public accessor methods for enemy and player positions.
    // These methods return a copy of the point, not a reference.

    /**
     * @brief Returns the position of the enemy at the given index (0-2).
     * @param index The index of the enemy (0, 1, or 2).
     * @return The position (x, y) of the enemy at the specified index.
     */
    Point getEnemyPosition(int index) const
    {
        return enemies[index];
    }

    /**
     * @brief Returns the position of the player.
     * @return The position (x, y) of the player on the map.

     */
    Point getPlayerPosition() const
    {
        return player;
    }

    /**
     * @brief Loads the game map from a file.
     *
     * This method opens the specified file and reads the map data. It then initializes the internal map representation,
     * player and enemy positions, and hidden tile position based on the information in the file.
     *
     * @param filename The path to the map file.
     *
     * @throws std::runtime_error If the file cannot be opened.
     */
    void loadMap(const char *filename)
    {
        ifstream file(filename);
        if (!file)
        {
            cerr << "Error opening file: " << filename << endl;
            exit(EXIT_FAILURE);
        }

        file >> size; // Read map size

        map = new char *[size]; // Allocate memory for map
        for (int i = 0; i < size; ++i)
        {
            map[i] = new char[size]; // Allocate memory for each map row
        }

        for (int i = 0; i < size; ++i)
        {
            for (int j = 0; j < size; ++j)
            {
                file >> map[i][j]; // Read each map tile
                if (map[i][j] == PLAYER)
                { // Update player position
                    player.x = i;
                    player.y = j;
                }
                else if (map[i][j] == ENEMY)
                { // Update enemy positions
                    for (int k = 0; k < 3; ++k)
                    {
                        enemies[k].x = i;
                        enemies[k].y = j;
                    }
                }
                else if (map[i][j] == HIDDEN)
                { // Update hidden tile position
                    hidden.x = i;
                    hidden.y = j;
                }
            }
        }

        file.close();
    }

    /**
     * @brief Prints the current state of the game map to the console.
     */
    void printMap()
    {
        for (int i = 0; i < size; ++i)
        {
            for (int j = 0; j < size; ++j)
            {
                cout << map[i][j] << ' '; // Print each map tile with a space
            }
            cout << endl; // Print a newline after each row
        }
    }
    // Function to move the player on the game map based on the specified direction.
    void movePlayer(char direction)
    {
        // Copy the current player's position to new variables.
        int newX = player.x;
        int newY = player.y;

        // Update the new position based on the specified direction.
        switch (direction)
        {
        case 'w':
            newX--; // Move up
            break;
        case 's':
            newX++; // Move down
            break;
        case 'a':
            newY--; // Move left
            break;
        case 'd':
            newY++; // Move right
            break;
        default:
            return; // If an invalid direction is provided, return without making any changes.
        }

        // Check if the new position is a valid move on the map.
        if (isValidMove(newX, newY))
        {
            // Update the map to reflect the player's new position.
            map[player.x][player.y] = PLAIN;
            player.x = newX;
            player.y = newY;
            map[player.x][player.y] = PLAYER;
        }
    }

    /**
     * @brief Moves enemies on the map based on random directions.
     */
    void moveEnemies()
    {
        // Iterate through each enemy on the map.
        for (int i = 0; i < 3; ++i)
        {
            // Generate a random direction for the enemy to move (0: up, 1: down, 2: left, 3: right).
            int direction = rand() % 4;

            // Copy the current enemy's position to new variables.
            int newX = enemies[i].x;
            int newY = enemies[i].y;

            // Update the new position based on the randomly generated direction.
            switch (direction)
            {
            case 0:
                newX--; // Move up
                break;
            case 1:
                newX++; // Move down
                break;
            case 2:
                newY--; // Move left
                break;
            case 3:
                newY++; // Move right
                break;
            }

            // Check if the new position is a valid move on the map and not equal to the hidden tile.
            if (isValidMove(newX, newY) && !(newX == hidden.x && newY == hidden.y))
            {
                // Update the map to reflect the enemy's new position.
                map[enemies[i].x][enemies[i].y] = PLAIN;
                enemies[i].x = newX;
                enemies[i].y = newY;
                map[enemies[i].x][enemies[i].y] = ENEMY;
            }
        }
    }

    /**
     * @brief Checks if a move to the specified position is valid on the game map.
     * @param x The x-coordinate of the target position.
     * @param y The y-coordinate of the target position.
     * @return True if the move is valid, false otherwise.
     */
    bool isValidMove(int x, int y)
    {
        // Check if the specified position is within the boundaries of the map,
        // and the target cell is not a wall (obstacle).
        return x >= 0 && x < size && y >= 0 && y < size && map[x][y] != WALL;
    }

    /**
     * @brief Displays a message to the console and handles player relocation to the hidden tile.
     * @param message The message to be displayed.
     */
    void displayMessage(const char *message)
    {
        // Output the provided message to the console.
        cout << message << endl;

        // Update the map to reflect the player's relocation to the hidden tile.
        map[player.x][player.y] = HIDDEN; // Player sent to hidden tile
        player.x = hidden.x;              // Update player's x-coordinate
        player.y = hidden.y;              // Update player's y-coordinate
        map[player.x][player.y] = PLAYER; // Place the player on the hidden tile.
    }
};
/**
 * @brief The main function that orchestrates the execution of the game loop.
 *
 * The main function initializes the Game object, displays the map and game controls,
 * and processes user input in a loop until the user decides to quit. It handles player
 * and enemy movements, as well as checks for interactions such as an enemy touching the player.
 *
 * @return Integer representing the exit status of the program (0 for success).
 */
int main()
{
    // Create a Game object, initializing it with the map from the "map.txt" file.
    Game game("map.txt");

    // Variable to store user input.
    char userInput;

    // Game loop
    do
    {
        // Display the current state of the game map.
        game.printMap();

        // Prompt the user for input and display available controls.
        cout << "Controls: 'q' to quit, 'w', 's', 'a', 'd' to move, space to skip turn: ";
        cin >> userInput;

        // Process user input
        if (userInput == 'q')
        {
            break; // Exit the loop if the user decides to quit.
        }
        else if (userInput == ' ')
        {
            game.moveEnemies(); // Skip a turn by moving the enemies.
        }
        else
        {
            // Move the player and the enemies for the current turn.
            game.movePlayer(userInput);
            game.moveEnemies();

            // Check if an enemy touched the player during the current turn.
            for (int i = 0; i < 3; ++i)
            {
                Point enemyPosition = game.getEnemyPosition(i);
                Point playerPosition = game.getPlayerPosition();
                if (playerPosition.x == enemyPosition.x && playerPosition.y == enemyPosition.y)
                {
                    // Display a message if an enemy touched the player, and send the player back to the hidden tile.
                    game.displayMessage("Enemy touched the player! Player sent back to hidden tile.");
                }
            }
        }
    } while (true);

    // Return 0 to indicate successful program execution.
    return 0;
}