#include "Game.h"

#include <iostream>
#include <thread>
#include <chrono>

#include "util.h"


void Game::init()
{
    //load assets
    if (!font.openFromFile("assets/fonts/ChakraPetch-Regular.ttf"))
    {
        std::cerr << "Error: cannot open 'assets/fonts/ChakraPetch-Regular.ttf'\n";
    }

    if (!bgMusicBuffer.loadFromFile("assets/sounds/bgMusic.wav"))
    {
        std::cerr << "Error: cannot open 'assets/sounds/bgMusic.wav'\n";
    }

    if (!winSoundBuffer.loadFromFile("assets/sounds/win.ogg"))
    {
        std::cerr << "Error: cannot open 'assets/sounds/win.ogg'\n";
    }

    if (!failSoundBuffer.loadFromFile("assets/sounds/lose.ogg"))
    {
        std::cerr << "Error: cannot open 'assets/sounds/lose.ogg'\n";
    }

    if (!hitSoundBuffer.loadFromFile("assets/sounds/hit.wav"))
    {
        std::cerr << "Error: cannot open 'assets/sounds/hit.wav'\n";
    }

    if (!scoreSoundBuffer.loadFromFile("assets/sounds/score.wav"))
    {
        std::cerr << "Error: cannot open 'assets/sounds/score.wav'\n";
    }

    player.pos = {100, 100};
    player.body.setSize({10, 70});
    player.body.setPosition(player.pos);
    player.body.setFillColor(sf::Color::White);

    cpu.pos = {(float)window.getSize().x - 100, 100};
    cpu.body.setSize({10,70});
    cpu.body.setPosition(cpu.pos);
    cpu.body.setFillColor(sf::Color::White);

    ball.pos = {(float)window.getSize().x / 2.f, (float)window.getSize().y / 2.f};
    ball.body.setPosition(ball.pos);
    ball.body.setFillColor(sf::Color::White);
    ball.body.setRadius(5.f);

    gameBoundary.setOutlineThickness(5);
    gameBoundary.setOutlineColor(sf::Color::White);
    gameBoundary.setFillColor(sf::Color::Transparent);
    gameBoundary.setPosition({0,100});
    gameBoundary.setSize({1280.f, 620.f});

    midline.setFillColor(sf::Color::White);
    midline.setSize({2, 40});
    midline.setPosition({(float)window.getSize().x/2.f - 1.f, 100.f });


    time.setString("Time: 00:00");
    time.setPosition({560, 25});
    time.setFillColor(sf::Color::White);

    bgMusic.setVolume(15.f);
    failSound.setVolume(15.f);
    winSound.setVolume(15.f);
    hitSound.setVolume(15.f);
    scoreSound.setVolume(15.f);

    bgMusic.play();
}

void Game::run()
{
    TimePoint lastFrameStartPoint = std::chrono::steady_clock::now();

    while (window.isOpen())
    {
        TimePoint currentFrameStartPoint = std::chrono::steady_clock::now();

        TimeDuration duration = currentFrameStartPoint - lastFrameStartPoint;
        const double frameTime = duration.count();

        playTime += duration;
        totalSeconds = std::chrono::duration_cast<std::chrono::seconds>(playTime).count();

        lastFrameStartPoint = currentFrameStartPoint;

        accumulator += frameTime;
        if (accumulator > 0.25) accumulator = 0.25;

        getInput();

        if (bgMusic.getStatus() == sf::Sound::Status::Playing)
        {
            while (accumulator >= physicsDeltaTime)
            {
                updateBall();

                updateCpu();

                updatePlayer();

                collision();

                updateTexts();

                accumulator -= physicsDeltaTime;
            }
        }

        soundSystem();

        render();

        TimePoint currentFrameEndPoint = std::chrono::steady_clock::now();
        TimeDuration currentFrameDuration = currentFrameEndPoint - currentFrameStartPoint;

        if (currentFrameDuration.count() < targetFrameTime)

        {
            TimeDuration sleepNeeded = TimeDuration(targetFrameTime) - currentFrameDuration;
            std::this_thread::sleep_for(std::chrono::duration_cast<std::chrono::milliseconds>(sleepNeeded));
        }
    }
}

void Game::getInput()
{
    while (const std::optional event = window.pollEvent())
    {
        if (event->is<sf::Event::Closed>())
        {
            window.close();
        }

        if (const auto* keyPressed = event->getIf<sf::Event::KeyPressed>())
        {
            switch (keyPressed->scancode)
            {
                case sf::Keyboard::Scan::Escape:
                    window.close();
                    break;

                case sf::Keyboard::Scan::Up:
                case sf::Keyboard::Scan::W:
                    player.up = true;
                    break;

                case sf::Keyboard::Scan::Down:
                case sf::Keyboard::Scan::S:
                    player.down = true;
                    break;

                case sf::Keyboard::Scan::R:
                    resetGame();
                    break;

                default:
                    break;
            }
        }

        if (const auto* keyReleased = event->getIf<sf::Event::KeyReleased>())
        {
            switch (keyReleased->scancode)
            {
                case sf::Keyboard::Scan::Up:
                case sf::Keyboard::Scan::W:
                    player.up = false;
                    break;

                case sf::Keyboard::Scan::Down:
                case sf::Keyboard::Scan::S:
                    player.down = false;
                    break;

                default:
                    break;
            }
        }
    }
}

void Game::render()
{
    window.clear();
    window.draw(gameBoundary);

    window.draw(midline);

    while (midline.getPosition().y <= window.getSize().y)
    {
        midline.move({0, 60});
        window.draw(midline);
    }

    midline.setPosition({midline.getPosition().x, 100.f});

    player.body.setPosition(player.pos);
    window.draw(player.body);

    cpu.body.setPosition(cpu.pos);
    window.draw(cpu.body);

    window.draw(playerScore);
    window.draw(cpuScore);
    window.draw(time);

    ball.body.setPosition(ball.pos);
    window.draw(ball.body);

    window.display();
}

void Game::updateCpu()
{
    if (totalSeconds > 0 and totalSeconds % 10 == 0)
    {
        cpu.speed += .5f;
    }

    if (ball.pos.y > cpu.pos.y)
    {
        cpu.velocity = cpu.speed;
    }

    if (ball.pos.y < cpu.pos.y)
    {
        cpu.velocity = cpu.speed * -1.f;
    }

    if (ball.pos.y == cpu.pos.y)
    {
        cpu.velocity = 0;
    }

    cpu.prevPos = cpu.pos;
    cpu.pos.y += cpu.velocity;
}
void Game::updatePlayer()
{
    const int direction = player.down - player.up;

    player.prevPos = player.pos;
    player.pos.y += player.velocity * (float)direction;
}
void Game::updateBall()
{
    ball.prevPos = ball.pos;
    ball.pos += ball.velocity;
}
void Game::collision() {}

void Game::updateTexts()
{
    const auto minutes = totalSeconds / 60;
    const auto seconds = totalSeconds % 60;

    const std::string secondsStr = (seconds < 10) ? "0" + std::to_string(seconds) : std::to_string(seconds);
    const std::string minutesStr = (minutes < 10) ? "0" + std::to_string(minutes) : std::to_string(minutes);

    time.setString("Time: " + minutesStr + ":" + secondsStr);
}

void Game::soundSystem() {}
void Game::resetGame() {}

