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

namespace pabloader {

bool Debuggers::GamePlay(float fElapsedTime)
{
    if (state == STATE_GAME) {
        if (!IsFocused() || GetKey(olc::ESCAPE).bPressed) {
            SwitchState(STATE_PAUSE_MENU);
            return true;
        }

        if (sourceCode.size() == 0) {
            SwitchState(STATE_GAME_OVER);
            return true;
        }
    }

    DrawSprite(0, 0, &backgroundSprite);

    uint32_t bugsSize = bugs.size();
    auto aliveBugs = std::count_if(bugs.begin(), bugs.end(), [](Bug* bug) { return bug->IsActive() && bug->IsAlive(); });

    Programmer* player = programmers.front();

    for (auto& bug : bugs) {
        if (bug->IsActive()) {
            bug->Update(fElapsedTime);
            bug->Draw();
            for (auto& programmer : programmers) {
                if (programmer->Collides(bug)) {
                    bugsCatched++;
                    if (programmer == player) {
                        bugsCatchedByPlayer++;
                    }
                    bug->Kill();
                    if (state == STATE_GAME)
                        olc::SOUND::PlaySample(bugCatchSample);
                    SpawnBug();
                }
            }
        } else if (bug->IsAlive()) {
            bugsMissed++;
            SpawnBug();
            if (state == STATE_GAME) {
                sourceCode.erase(sourceCode.begin());
                olc::SOUND::PlaySample(bugFallSample);
            }
        }
    }

    for (auto& bonus : bonuses) {
        if (bonus->IsActive()) {
            bonus->Update(fElapsedTime);
            bonus->Draw();
            if (player->Collides(bonus)) {
                auto type = bonus->GetType();
                if (type == BONUS_DOCUMENTATION) {
                    if (aliveBugs > 1) {
                        for (auto& bug : bugs) {
                            if (bug->IsAlive() && bug->IsActive()) {
                                bug->Kill();
                                break;
                            }
                        }
                    }
                } else {
                    bonusesCatched[type]++;
                }
                bonus->Kill();
                // TODO play pickup sound
            }
        } else {
            bonus->Kill();
        }
    }

    linesOfCode = (sourceCode.size() * 4 / totalLines) + 1;

    for (auto it = programmers.rbegin(); it != programmers.rend(); it++) {
        auto& programmer = *it;
        programmer->Think();
        programmer->Update(fElapsedTime);
        programmer->Draw();
    }

    if (aliveBugs == 0 && player->IsOnGround()) {
        SpawnBug();
    }

    auto programmersSize = programmers.size();
    if (bugsCatchedByPlayer >= 15 * programmersSize * programmersSize * programmersSize && player->GetSkin() < programmersSize) {
        player->UpgradeLevel();
        SpawnBug();
        SpawnProgrammer();
    }

    if (bugsCatched > 10 * bugsSize) {
        SpawnBug();
    }

    if (GetRandom<float>() < 0.01 * fElapsedTime) {
        SpawnBonus();
    }

#ifdef _DEBUG
    if (debug) {
        DrawString(1, 37, "Bg: " + std::to_string(bugs.size()) + " Ab: " + std::to_string(aliveBugs));
        DrawString(1, 46, "Ps: " + std::to_string(programmers.size()) + " Bn: " + std::to_string(bonuses.size()));
        DrawString(1, 55, "Cp: " + std::to_string(bugsCatchedByPlayer));
        DrawString(1, 64, "DBG: " + std::to_string(bonusesCatched[BONUS_DEBUGGER]));
        DrawString(1, 73, "UTS: " + std::to_string(bonusesCatched[BONUS_UNIT_TEST]));
    }
#endif
    if (state == STATE_GAME) {
        DrawString(1, 1, "Don't let the bugs destroy your code!", olc::YELLOW);
        DrawString(1, 10, "Bugs catched: " + std::to_string(bugsCatched));
        DrawString(1, 19, "Bugs missed: " + std::to_string(bugsMissed));
    }

    DrawSourceCode();

    if (state == STATE_MAIN_MENU) {
        DrawMenu();
    }

    return true;
}

bool Debuggers::PauseMenu(float fElapsedTime)
{
    DrawSprite(0, 0, &backgroundSprite);

    for (auto& bug : bugs) {
        if (bug->IsActive() && bug->IsAlive()) {
            bug->Draw();
        }
    }

    for (auto& bonus : bonuses) {
        if (bonus->IsActive()) {
            bonus->Draw();
        }
    }

    for (auto it = programmers.rbegin(); it != programmers.rend(); it++) {
        auto& programmer = *it;
        programmer->Draw();
    }

    DrawSourceCode();
    DrawMenu();

    return true;
}

bool Debuggers::GameOver(float dt)
{
    DrawSprite(0, 0, &backgroundSprite);
    DrawString(ScreenWidth() / 2 - 143, ScreenHeight() / 2 - 15, "GAME OVER", olc::BLACK, 4);
    DrawString(ScreenWidth() / 2 - 144, ScreenHeight() / 2 - 16, "GAME OVER", olc::RED, 4);

    DrawString(ScreenWidth() / 2 - 144, ScreenHeight() / 2 + 16, "Bugs catched: " + std::to_string(bugsCatched), olc::CYAN, 2);
    DrawString(ScreenWidth() / 2 - 144, ScreenHeight() / 2 + 34, "Press SPACE to continue");

    // TODO main menu

    if (GetKey(olc::SPACE).bPressed) {
        SwitchState(STATE_MAIN_MENU);
    }

    return true;
}

bool Debuggers::HelpMenu(float dt)
{
    DrawSprite(0, 0, &backgroundSprite);
    DrawSourceCode();
    DrawLogo();

    auto y = 96;

    DrawString(16, y, "A,S,D - move left/down/right");
    y += 9;
    DrawString(16, y, "SPACE - jump");
    y += 9;
    DrawString(16, y, "Bugs, you must destroy them: ");
    y += 9;

    helpRotation += dt;
    auto w = enemiesSprite.width / 4;
    auto h = enemiesSprite.height / 4;

    y += h;

    olc::GFX2D::Transform2D transform;
    transform.Scale(2, 2);
    transform.Rotate(helpRotation);
    transform.Translate(16 + w / 2, y);

    SetPixelMode(olc::Pixel::MASK);
    for (auto i = 0; i < 2; i++) {
        for (auto j = 0; j < 4; j++) {
            olc::GFX2D::DrawPartialSprite(&enemiesSprite, i * w, j * h, w, h, transform, true);
            transform.Translate(w * 2, 0);
        }
    }
    y += h;
    w = bonusesSprite.width / 4;
    h = bonusesSprite.height / 4;

    DrawString(16, y, "RTFM removes 10% bugs: ");
    y += 9 + h / 2;

    DrawPartialSprite(16 + w / 2, y, &bonusesSprite, 0, 0, w, h, 1, true);

    SetPixelMode(olc::Pixel::NORMAL);

    if (GetKey(olc::ESCAPE).bPressed) {
        SwitchState(prevState);
    }

    return true;
}
}