#include "game.h"

#define DELAY 30000

Game::Game()
    :max_x(1000), max_y(800), direction(0), window(sf::VideoMode(max_x, max_y), "ASCII RACER"),
      racecar(400.0f, 30.0f, 1.7f, sf::Color::Red), bot(400.0f, 30.0f, 1.7f, sf::Color::Green) { // maxSpeed, acceleration, and steerSpeed values
    if (!font.loadFromFile("cascaydia.otf")) {
        std::cerr << "Failed to load font" << std::endl;
        exit(1);
    }

    if (!backgroundMusic.openFromFile("background_music.wav")) {
        std::cerr << "Failed to load background music" << std::endl;
        exit(1);
    }

    // Set the volume (adjust as needed)
    backgroundMusic.setVolume(50);

    // Play the background music
    backgroundMusic.play();
    
    // Create a text object for displaying speed
    speedText.setFont(font);
    nitroText.setFont(font);

    // Create a map text object and set its properties
    mapText.setFont(font);
    mapText.setCharacterSize(24);
    mapText.setFillColor(sf::Color::White);
    mapText.setPosition(0, 0);
    mapText.setOutlineColor(sf::Color::Red);

    timerText.setFont(font);
    timerText.setCharacterSize(24);
    timerText.setFillColor(sf::Color::White);

    racecar.setPosition(1000, 80); // Set initial position for the racecar
    racecar.setRotation(180);

    bot.setPosition(1000, 80); // Set initial position for the bot
    bot.setRotation(180);

    // Initialize the times and quadrants data structure
    timesAndQuadrants.push_back(std::make_pair(1, "00:00:000"));
}

void Game::run() {
    sf::Clock clock;

    loadPlayerMovesFromFile("player_moves.txt");

    // Create a view that will follow the racecar
    view = sf::View(sf::FloatRect(0, 0, max_x, max_y));
    view.setCenter(racecar.getX(), racecar.getY()); // Center the view on the racecar
    window.setView(view);

    backgroundMusic.setLoop(true);

    int currentMove = 0;
    
    while (window.isOpen()) {
        float dt = clock.restart().asSeconds();

        if(lapPoints.size() < 3) {
            
        botMove(currentMove, dt);
        currentMove++;
        
        // Update game time
        sf::Time gameTime = gameTimeClock.getElapsedTime();
        int milis = static_cast<int>(gameTime.asMilliseconds());
        int seconds = static_cast<int>(gameTime.asSeconds());
        int minutes = seconds / 60;
        seconds %= 60;
        milis %= 1000;
        
        // Use std::setw and std::setfill to format numbers as two digits
        std::ostringstream timerStream;
        timerStream << std::setw(2) << std::setfill('0') << minutes << ":"
                    << std::setw(2) << std::setfill('0') << seconds << ":"
                    << std::setw(2) << std::setfill('0') << milis;
        
        std::string timerString = timerStream.str();
        timerText.setString(timerString);

        // Detect when the racecar changes quadrants and save the time
        int currentQuadrant = determineQuadrant(racecar.getX(), racecar.getY());
        if (currentQuadrant != timesAndQuadrants.back().first) {
            timesAndQuadrants.push_back(std::make_pair(currentQuadrant, timerString));
        }
        
        determineLap(static_cast<int>(gameTime.asMilliseconds()), timerString);
        }

        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
                backgroundMusic.stop();
            }
        }

        if(lapPoints.size() < 3) {
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::LShift)) {
                if(nitro > 0) {
                    nitro--;
                    float newX = racecar.getX() + ((racecar.getSpeed() + 1.1) * dt * cos(racecar.getRotation()));
                    float newY = racecar.getY() + ((racecar.getSpeed() + 1.1) * dt * sin(racecar.getRotation()));
                    //std::cout << "New: " << newX << "," << newY << "   Old: " << racecar.getX() << "," << racecar.getY() << std::endl;
                    if (!isTrackCollision(newX, newY)) {
                        racecar.boost(dt);
                        //std::cout << nitro << std::endl;
                        mapText.setOutlineThickness(0);
                    } else {
                        //racecar.stop();
                        racecar.boost(dt);
                        modifier += 0.01;
                        mapText.setOutlineThickness(6);
                    }
                }
                else {
                    if (racecar.getSpeed() < 0) {
                        racecar.brake(dt);
                    } else {
                        float newX = racecar.getX() + ((racecar.getSpeed() + 1.1) * dt * cos(racecar.getRotation()));
                        float newY = racecar.getY() + ((racecar.getSpeed() + 1.1) * dt * sin(racecar.getRotation()));
                        //std::cout << "New: " << newX << "," << newY << "   Old: " << racecar.getX() << "," << racecar.getY() << std::endl;
        
                        if (!isTrackCollision(newX, newY)) {
                            racecar.accelerate(dt);
                            mapText.setOutlineThickness(0);
                        } else {
                            //racecar.stop();
                            modifier += 0.01;
                            mapText.setOutlineThickness(6);
                        }
                    }
                }
            } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
                if (racecar.getSpeed() < 0) {
                    racecar.brake(dt);
                } else {
                    float newX = racecar.getX() + ((racecar.getSpeed() + 1.1) * dt * cos(racecar.getRotation()));
                    float newY = racecar.getY() + ((racecar.getSpeed() + 1.1) * dt * sin(racecar.getRotation()));
                    //std::cout << "New: " << newX << "," << newY << "   Old: " << racecar.getX() << "," << racecar.getY() << std::endl;
    
                    if (!isTrackCollision(newX, newY)) {
                        racecar.accelerate(dt);
                        mapText.setOutlineThickness(0);
                    } else {
                        //racecar.stop();
                        modifier += 0.01;
                        mapText.setOutlineThickness(6);
                    }
                }
            } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) {
                if (racecar.getSpeed() > 0) {
                    racecar.brake(dt);
                } else {
                    float newX = racecar.getX() + ((racecar.getSpeed() + 1.1) * dt * cos(racecar.getRotation()));
                    float newY = racecar.getY() + ((racecar.getSpeed() + 1.1) * dt * sin(racecar.getRotation()));
                    //std::cout << "New: " << newX << "," << newY << "   Old: " << racecar.getX() << "," << racecar.getY() << std::endl;
                    
                    if (!isTrackCollision(newX, newY)) {
                        racecar.reverse(dt);
                        mapText.setOutlineThickness(0);
                    } else {
                        //racecar.stop();
                        modifier += 0.01;
                        mapText.setOutlineThickness(6);
                    }
                }
            } else {
                racecar.decelerate(dt);
            }
    
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
                racecar.steer(dt, -1.0f); // Steer left
            } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
                racecar.steer(dt, 1.0f); // Steer right
            }
    
            racecar.update(dt);
            bot.update(dt);
    
            nitroText.setString("Nitro: " + std::to_string(nitro));
            nitroText.setCharacterSize(24);
            nitroText.setFillColor(sf::Color::White);
            nitroText.setPosition(racecar.getX() - 475, racecar.getY() + 300);
            
            speedText.setString(std::to_string(static_cast<int>(racecar.getSpeed())) + " km/h");
            speedText.setCharacterSize(24);
            speedText.setFillColor(sf::Color::Green);
            speedText.setPosition(racecar.getX() - 475, racecar.getY() + 350);
            if(racecar.getSpeed() < 0){
                speedText.setFillColor(sf::Color::Blue);
            }
            if(racecar.getSpeed() >= 250){
                speedText.setCharacterSize(27);
                speedText.setFillColor(sf::Color::Yellow);
            }
            if(racecar.getSpeed() >= 360){
                speedText.setCharacterSize(30);
                speedText.setFillColor(sf::Color::Red);
            }
            timerText.setPosition(racecar.getX() - 80, racecar.getY() - 350);
    
            // Update the view to follow the racecar
            view.setCenter(racecar.getX(), racecar.getY());
            window.setView(view);
        }
        
        window.clear();
        mapText.setString(asciiMap);
        window.draw(mapText);
        window.draw(racecar.getDrawable());
        window.draw(bot.getDrawable());
        window.draw(speedText);
        window.draw(nitroText);

        displaySavedTimes();

        if (lapPoints.size() == 3) {
            displayEndGame();
        }
        else {
            window.draw(timerText);
        }
        
        window.display();

        if (lapPoints.size() == 3) {
            while (window.isOpen()) {
                sf::Event event;
                while (window.pollEvent(event)) {
                    if (event.type == sf::Event::Closed) {
                        window.close();
                        backgroundMusic.stop();
                    }
                }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Enter))
                    break;
            }
            break;
        }

        sf::sleep(sf::microseconds(DELAY));
    }
}

bool Game::isTrackCollision(float x, float y) {
    if (x <= 0) {
        return true;
    } else if (y <= 0) {
        return true;
    } else {
        int currX = (x / 13.85) - 1;
        int currY = (y / 27.88) - 1;
        char currentChar;
        if (currY * 194 + currX < 6378)
            currentChar = asciiMap[currY * 194 + currX];
        else
            return true;
       //td::cout << currY * 194 + currX << std::endl;
       if (currentChar == '\\' || currentChar == '|' || currentChar == '/' || currentChar == '-' || currentChar == '_') {
           return true;
       }
    }
            
    return false; // No collision with track or out of bounds
}

void Game::determineLap(int millis, std::string timerString) {
    lapFinished = true;
    int requiredOrder[] = {1,2,1,2,3,4,1};
    if(timesAndQuadrants.size() >= 7) {
        for (int i = 0; i < 7; i++) {
            if (std::get<0>(timesAndQuadrants[i]) != requiredOrder[i])
                lapFinished = false; 
        }
    }
    else {
        lapFinished = false;
    }
       
    if (lapFinished) {
        int penality = (millis - lastRound) * modifier;
        if (penality > 100000 ) penality = 100000;
        unsigned int points = (100000 - penality);
        lapPoints.push_back(points);
        timesAndQuadrants.clear();
        timesAndQuadrants.push_back(std::make_pair(1, timerString));
        lastRound = millis;
        modifier = 1.0f;
    }
}

int Game::determineQuadrant(float x, float y) {
    if (y <= 460 && x <= 1150)
        return 1;
    if (y > 460 && x <= 1150)
        return 2;
    if (y > 460 && x > 1150)
        return 3;
    if (y <= 460 && x > 1150)
        return 4;

    return 0;
}

void Game::displaySavedTimes() {
    // Display the saved times on the left side of the screen
    int textY = 10; // Adjust the vertical position as needed
    for (const auto& lapTime : lapPoints) {
        std::string timeString = "Lap points: " + std::to_string(lapTime);
        sf::Text text;
        text.setFont(font);
        text.setCharacterSize(18);
        text.setFillColor(sf::Color::White);
        text.setPosition(racecar.getX() - 475, (racecar.getY() - 300) + textY);
        text.setString(timeString);
        window.draw(text);
        textY += 30; // Adjust the spacing as needed
    }
    for (const auto& timeAndQuadrant : timesAndQuadrants) {
        std::string timeString = "Quadrant " + std::to_string(timeAndQuadrant.first) + ": " + timeAndQuadrant.second;
        sf::Text text;
        text.setFont(font);
        text.setCharacterSize(18);
        text.setFillColor(sf::Color::White);
        text.setPosition(racecar.getX() - 475, (racecar.getY() - 300) + textY);
        text.setString(timeString);
        window.draw(text);
        textY += 30; // Adjust the spacing as needed
    }
}

void Game::displayEndGame() {
    // Load high scores from a file
    loadHighScores();
    
    // Get the best score from the current run
    std::sort(lapPoints.begin(), lapPoints.end());
    int bestScore = lapPoints.back();

    // If the new score is in the top ten, add it
    if (highScores.size() < 10 || bestScore > highScores.back()) {
        highScores.push_back(bestScore);
    }

    // Sort the high scores in descending order
    std::sort(highScores.rbegin(), highScores.rend());

    // Keep only the top ten scores
    if (highScores.size() > 10) {
        highScores.pop_back();
    }

    // Write the updated high scores back to the file
    writeHighScores();

    // Display the best score from the current run and the top ten high scores
    int textY = 10; // Adjust the vertical position as needed

    // Display the best score from the current run
    std::string bestScoreString = "Best Lap: " + std::to_string(bestScore);
    sf::Text bestScoreText;
    bestScoreText.setFont(font);
    bestScoreText.setCharacterSize(20);
    bestScoreText.setFillColor(sf::Color::White);
    bestScoreText.setPosition(racecar.getX(), (racecar.getY() - 300) + textY);
    bestScoreText.setString(bestScoreString);
    window.draw(bestScoreText);
    textY += 30; // Adjust the spacing as needed

    // Display the top ten high scores
    for (size_t i = 0; i < highScores.size(); ++i) {
        std::string scoreString = "High Score #" + std::to_string(i + 1) + ": " + std::to_string(highScores[i]);
        sf::Text scoreText;
        scoreText.setFont(font);
        scoreText.setCharacterSize(20);
        scoreText.setFillColor(sf::Color::White);
        scoreText.setPosition(racecar.getX(), (racecar.getY() - 300) + textY);
        scoreText.setString(scoreString);
        window.draw(scoreText);
        textY += 30; // Adjust the spacing as needed
    }
}

void Game::loadHighScores() {
    std::ifstream file(highScoresFile);
    if (file.is_open()) {
        int score;
        while (file >> score) {
            highScores.push_back(score);
        }
        file.close();
    }
}

// Function to write high scores to a file
void Game::writeHighScores() {
    std::ofstream file(highScoresFile);
    if (file.is_open()) {
        for (const int& score : highScores) {
            file << score << std::endl;
        }
        file.close();
    }
}

void Game::loadPlayerMovesFromFile(const std::string& filename) {
    std::ifstream file(filename);
    if (file.is_open()) {
        int move;
        while (file >> move) {
            lastPlayerMoves.push_back(move);
        }
        file.close();
    }
}

void Game::botMove(int i, float dt) {
    int move = lastPlayerMoves[i];
    switch (move) {
        case 0:
            bot.brake(dt);
            break;
        case 1:
            bot.accelerate(dt);
            break;
        case 2:
            bot.decelerate(dt);
            break;
        case 3:
            bot.decelerate(dt);
            bot.steer(dt, -1.0f);
            break;
        case 4:
            bot.decelerate(dt);
            bot.steer(dt, 1.0f);
            break;
        case 5:
            bot.accelerate(dt);
            bot.steer(dt, -1.0f);
            break;
        case 6:
            bot.accelerate(dt);
            bot.steer(dt, 1.0f);
            break;
    }
}