#include "debuggers.h"
#include <algorithm>
#include <cmath>
#include <ctime>
#include <iostream>
#include <random>

namespace pabloader {
std::string& rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r ")
{
    str.erase(str.find_last_not_of(chars) + 1);
    return str;
}

template <>
float Debuggers::GetRandom<float>(float from, float to)
{
    if (from > to) {
        std::swap(from, to);
    }
    std::uniform_real_distribution<float> dist(from, to);
    return dist(generator);
}

template <>
int Debuggers::GetRandom<int>(int from, int to)
{
    if (from > to) {
        std::swap(from, to);
    }
    std::uniform_int_distribution<int> dist(from, to - 1);
    return dist(generator);
}

bool Debuggers::OnUserCreate()
{
    std::srand(std::time(0));

    if (!olc::SOUND::InitialiseAudio()) {
        std::cerr << "Cannot init audio" << std::endl;
        return false;
    }

    if (!pack.LoadPack("debuggers.pgp")) {
        std::cerr << "Cannot load resource pack debuggers.pgp" << std::endl;
        return false;
    }

    // Load Player

    std::string fileName = "res/player.pgs";
    if (!playerSprite.LoadFromPGESprFile(fileName, &pack)) {
        std::cerr << "[Load player] File " << fileName << " is not found" << std::endl;
        return false;
    }

    fileName = "res/fall.wav";
    playerFallSample = olc::SOUND::LoadAudioSample(fileName, &pack);
    if (playerFallSample < 0) {
        std::cerr << "[Load fall] File " << fileName << " is not found" << std::endl;
        return false;
    }

    // Load enemies

    fileName = "res/enemies.pgs";
    if (!enemiesSprite.LoadFromPGESprFile(fileName, &pack)) {
        std::cerr << "[Load enemies] File " << fileName << " is not found" << std::endl;
        return false;
    }

    fileName = "res/bug_fall.wav";
    bugFallSample = olc::SOUND::LoadAudioSample(fileName, &pack);
    if (bugFallSample < 0) {
        std::cerr << "[Load bug fall] File " << fileName << " is not found" << std::endl;
        return false;
    }

    fileName = "res/bug_catch.wav";
    bugCatchSample = olc::SOUND::LoadAudioSample(fileName, &pack);
    if (bugCatchSample < 0) {
        std::cerr << "[Load bug catch] File " << fileName << " is not found" << std::endl;
        return false;
    }

    // Load bonuses

    fileName = "res/bonuses.pgs";
    if (!bonusesSprite.LoadFromPGESprFile(fileName, &pack)) {
        std::cerr << "[Load bonuses] File " << fileName << " is not found" << std::endl;
        return false;
    }

    // Load background

    fileName = "res/bg.pgs";
    if (!backgroundSprite.LoadFromPGESprFile(fileName, &pack)) {
        std::cerr << "[Load background] File " << fileName << " is not found" << std::endl;
        return false;
    }

    fileName = "src/programmer.cpp";
    auto streamBuffer = pack.GetStreamBuffer(fileName);
    if (streamBuffer.data == nullptr) {
        std::cerr << "[Load source] File " << fileName << " is not found" << std::endl;
        return false;
    }
    std::istream is(&streamBuffer);
    std::string line;
    while (std::getline(is, line)) {
        if (!rtrim(line).empty())
            originalSourceCode.push_back(line);
    }

    fileName = "res/hover.wav";
    hoverSample = olc::SOUND::LoadAudioSample(fileName, &pack);
    if (hoverSample < 0) {
        std::cerr << "[Load hover] File " << fileName << " is not found" << std::endl;
        return false;
    }

    fileName = "res/click.wav";
    clickSample = olc::SOUND::LoadAudioSample(fileName, &pack);
    if (clickSample < 0) {
        std::cerr << "[Load click] File " << fileName << " is not found" << std::endl;
        return false;
    }

    fileName = "res/music.wav";
    bgMusic = olc::SOUND::LoadAudioSample(fileName, &pack);
    if (bgMusic < 0) {
        std::cerr << "[Load music] File " << fileName << " is not found" << std::endl;
        return false;
    }

    totalLines = originalSourceCode.size();

    // Free Memory
    pack.ClearPack();

    // Creating entities

    ResetGame();
    SwitchState(STATE_MAIN_MENU);
    olc::SOUND::PlaySample(bgMusic, true);

    return true;
}

bool Debuggers::OnUserUpdate(float fElapsedTime)
{
#ifdef _DEBUG
    if (debug) {
        if (GetKey(olc::B).bPressed) {
            SpawnBug();
        }
        if (GetKey(olc::N).bPressed) {
            SpawnBonus();
        }
        if (GetKey(olc::P).bPressed) {
            SpawnProgrammer();
        }
    }
    if (GetKey(olc::K0).bReleased) {
        debug = !debug;
    }
#endif
    bool result = false;
    switch (state) {
    case STATE_GAME:
    case STATE_MAIN_MENU:
        result = GamePlay(fElapsedTime);
        break;
    case STATE_PAUSE_MENU:
        result = PauseMenu(fElapsedTime);
        break;
    case STATE_HELP:
        result = HelpMenu(fElapsedTime);
        break;
    case STATE_GAME_OVER:
        result = GameOver(fElapsedTime);
        break;
    default:
        break;
    }

    for (auto& button : buttons) {
        button->Draw();
        if (button->Update()) {
            break;
        }
    }
    return result;
}

void Debuggers::DrawMenu()
{
    DrawLogo();

    std::string resumeText = state == STATE_PAUSE_MENU ? "Continue" : "Start";
    if (buttons.size() > 0) {
        buttons.front()->SetText(resumeText);
    }

    if (GetKey(olc::ESCAPE).bPressed && state == STATE_PAUSE_MENU) {
        SwitchState(STATE_GAME);
    }
}

void Debuggers::DrawSourceCode()
{
    int y = GameScreenHeight();
    FillRect(0, y - 1, ScreenWidth(), ScreenHeight() - y + 1, olc::VERY_DARK_GREY);

    for (auto& line : sourceCode) {
        DrawString(1, y, line);
        y += 10;
        if (y >= ScreenHeight())
            break;
    }
}

void Debuggers::DrawLogo()
{
    SetPixelMode(olc::Pixel::ALPHA);
    FillRect(0, 0, ScreenWidth(), ScreenHeight(), { 0, 0, 0, 128 });
    SetPixelMode(olc::Pixel::NORMAL);

    DrawString(ScreenWidth() / 2 - 144, 32, "DEBUGGERS", olc::YELLOW, 4);
    DrawString(ScreenWidth() - 128, 64, "by Pabloader");
    DrawString(ScreenWidth() - 17 * 8 - 2, ScreenHeight() - 10, "OLC Code Jam 2019");
}

void Debuggers::SwitchState(GameState newState)
{
    prevState = state;
    ClearButtons();
    switch (newState) {
    case STATE_GAME:
        if (state == STATE_MAIN_MENU || state == STATE_GAME_OVER) {
            ResetGame();
            SpawnPlayer();
        }
        break;
    case STATE_MAIN_MENU:
        ResetGame();
        SpawnProgrammer();
        // fallthrough
    case STATE_PAUSE_MENU:
        buttons.push_back(new Button(this, ScreenWidth() / 2 - 64, 100, 128, 16, "Start", STATE_GAME));
        buttons.push_back(new Button(this, ScreenWidth() / 2 - 64, 132, 128, 16, "Help", STATE_HELP));
        buttons.push_back(new Button(this, ScreenWidth() / 2 - 64, 164, 128, 16, "Quit", STATE_QUIT));
        break;
    case STATE_HELP:
        buttons.push_back(new Button(this, 16, 64, 64, 16, "Back", prevState));
        break;
    default:
        break;
    }

    state = newState;
}

void Debuggers::ResetGame()
{
    ClearButtons();
    for (auto& i : programmers) {
        delete i;
    }
    programmers.clear();

    for (auto& i : bugs) {
        i->Kill();
    }

    for (auto& i : bonuses) {
        i->Kill();
    }

    bonusesCatched.clear();
    bugsCatched = 0;
    bugsCatchedByPlayer = 0;
    bugsMissed = 0;
    sourceCode = originalSourceCode;
}

void Debuggers::ClearButtons()
{
    for (auto& i : buttons) {
        delete i;
    }
    buttons.clear();
}

void Debuggers::SpawnBug()
{
    auto bug = CreateBug();
    bug->ResetPosition();
}

void Debuggers::SpawnBonus()
{
    auto bonus = CreateBonus();
    bonus->ResetPosition();
}

void Debuggers::SpawnPlayer()
{
    auto programmer = new Player(this);
    programmers.push_front(programmer);
}

void Debuggers::SpawnProgrammer()
{
    auto programmer = new Programmer(this);
    programmers.push_back(programmer);
}

Bug* Debuggers::CreateBug()
{
    for (auto bug : bugs) {
        if (!bug->IsActive() || !bug->IsAlive())
            return bug;
    }
    auto bug = new Bug(this);
    bugs.push_back(bug);
    return bug;
}

Bonus* Debuggers::CreateBonus()
{
    for (auto bonus : bonuses) {
        if (!bonus->IsActive())
            return bonus;
    }
    auto bonus = new Bonus(this);
    bonuses.push_back(bonus);
    return bonus;
}

bool Debuggers::OnUserDestroy()
{
    olc::SOUND::DestroyAudio();
    return true;
}
}