#include <app.h>
#include <GLFW/glfw3.h>
#include <imgui/imgui.h>
#include <window.h>
#include "game.h"
#include "level.h"
#include "menu.h"
#include "settingsMenu.h"

namespace game {

Level::Level()
{
    background = new render::sprite::image();
    camera = new render::camera2D();
    manager = new NoteManager(this);
    controlManager = new ButtonManager();
    music = new SoLoud::WavStream();

    camera->screenSize = {30, 17};

    font = new render::text::Font();
    font->loadQuad("data/fonts/font");
    scoreText = new render::text::text(font);
    comboText = new render::text::text(font);

    comboText->align = render::text::text::align_center;
    comboText->valign = render::text::text::valign_middle;

    scoreText->align = render::text::text::align_center;
    scoreText->valign = render::text::text::valign_middle;
    scoreText->color = color::white;

    startFlags = 0;
}

Level::~Level()
{
    delete camera;
    delete manager;
    delete controlManager;
    delete background;
    delete scoreText;
    delete comboText;
    delete font;
}

void Level::Load(levelInfo *info)
{
    this->info = info;

    background->load(info->backgroundFile, true);
    music->load(info->musicFile.c_str());

    songLength = music->getLength();

    if (!notes.empty()) {
        for (game::noteInfo *i: notes) {
            delete i;
        }
    }
    notes.clear();

    LoadNotes();

    manager->Load(&notes);
}

void Level::Show()
{
    flags = startFlags;
    startFlags = 0;
    this->playingHandler = App::GetSound()->getEngine()->play(*music, -1, 0, true);
    this->Reset();
}

void Level::Hide()
{
    App::GetSound()->getEngine()->stop(this->playingHandler);
}

void Level::Start()
{
    this->paused = false;
    this->showSettings = false;
    this->shouldClose = false;
    this->manager->Start();
    App::GetSound()->getEngine()->setPause(this->playingHandler, false);
    SetSoundVolume(App::GetConfig()->getConfig().musicVolume);
}

void Level::Pause()
{
    this->paused = true;
    this->manager->Start();
    App::GetSound()->getEngine()->setPause(this->playingHandler, true);
}

void Level::Reset()
{
    this->score = 0;
    this->comboShowTime = 0;
    this->combo = 0;

    if (flags&(uint32_t)StartFlags::noHeals) {
        this->health = 100;
    } else {
        this->health = 50;
    }

    healthBar.Reset(this->health);

    memset(&this->statistic, 0, sizeof(currentLevelStatistic));

    this->manager->Reset(songLength);
    App::GetSound()->getEngine()->seek(this->playingHandler, 0);
    this->Start();
}

void Level::LoadNotes()
{
    FILE *f = fopen(this->info->notesFile.c_str(), "rb");
    if (!f) {
        return;
    }

    game::notesHeader header;

    fread(&header, 1, sizeof(header), f);

    fseek(f, header.notesOffset, SEEK_SET);

    game::noteInfo *buffer = new game::noteInfo[header.notesCount];

    fread(buffer, header.notesCount, sizeof(game::noteInfo), f);

    for (int i = 0; i < header.notesCount; i++) {
        auto note = new game::noteInfo;
        memcpy(note, &buffer[i], sizeof(game::noteInfo));
        notes.push_back(note);
    }

    delete [] buffer;

    fclose(f);
}

void Level::AddNoteResult(NoteResult result, const noteInfo *note)
{
    if (this->shouldClose) {
        return;
    }

    int scoreCoeff = 1;

    if (health == 100) {
        scoreCoeff = 2;
    }

    switch (result) {
    case NoteResult::wrong:
    case NoteResult::miss:
        if (!(flags&(uint32_t)StartFlags::immortality)) {
            health -= 10 / scoreCoeff;
        }
        statistic.wrongButtons++;
        break;
    case NoteResult::almost:
        if (!(flags&(uint32_t)StartFlags::immortality)) {
            health -= 2 / scoreCoeff;
        }
        score += 10;
        statistic.almostButtons++;
        break;
    case NoteResult::ok:
        if (!(flags&(uint32_t)StartFlags::noHeals)) {
            health+=2;
        }
        score += 50 * scoreCoeff;
        statistic.okButtons++;
        break;
    case NoteResult::great:
        if (!(flags&(uint32_t)StartFlags::noHeals)) {
          health += 5;
        }
        score += 100 * scoreCoeff;
        statistic.greatButtons++;
        break;
    }

    switch (result) {
        case NoteResult::wrong:
        case NoteResult::miss:
        case NoteResult::almost:
            combo = 0;
            break;
        case NoteResult::ok:
        case NoteResult::great:
            combo++;
            break;
    }

    if (statistic.maxCombo < combo) {
        statistic.maxCombo = combo;
    }

    statistic.score = score;

    comboPosition = note->position;
    comboShowTime = 2;
    PrepareComboText(result);

    if (health > 100) {
        health = 100;
    } else if (health < 0) {
        health = 0;
        this->MakeFade();
    }

    healthBar.SetHealth(health);
}

uint32_t Level::GetFlags() const
{
    return flags;
}

bool Level::IsFailed() const
{
    return this->health <= 0;
}

float Level::GetSoundLength() const
{
    return this->music->getLength();
}

int Level::GetNoteCount() const
{
    return this->notes.size();
}

void Level::Update()
{
    if (shouldClose) {
        fadeTime -= window::deltaTime;

        if (fadeTime <= 0) {
            game::SetState(gameState::stateMenu);
        } else if (!paused) {
            this->SetSoundVolume(fadeTime * App::GetConfig()->getConfig().musicVolume);
            manager->Update(controlManager->currentState);
        }

        return;
    }

    if (App::GetInput()->IsControlJustPressed(INPUT_MENU)) {
        if (this->paused) {
            this->Start();
            this->showSettings = false;
        } else {
            this->Pause();
        }
    }

    controlManager->Update();

    // this->UpdateGui();

    if (this->paused) {
        this->UpdateMenu();
    } else {

        healthBar.Update();

        if (comboShowTime > 0) {
            comboShowTime -= window::deltaTime;
        }

        manager->Update(controlManager->currentState);

        if (manager->GetTime() > songLength) {
            this->MakeFade();
        }
    }
}

void Level::Render()
{
    render::bindShader(0);
    camera->bind();

    background->draw({0, 0}, {30, 17});

    this->RenderGui();

    manager->Render();

    if (comboShowTime > 0) {

        if (comboShowTime < 0.25) {
            comboText->color.a = comboShowTime * 4;
        } else {
            comboText->color.a = 1;
        }

        if (comboShowTime > 1.75) {
            float mod = comboShowTime - 1.75 + 1;
            scoreText->scale = mod/16.f;
            comboText->scale = mod/24.f;
        } else {
            scoreText->scale = 1.f/16.f;
            comboText->scale = 1.f/24.f;
        }

        comboText->draw(comboPosition + vec2f{3, 1});
    }

    scoreText->draw({15, 1});

    if (shouldClose) {
        render::primitives2d::quad({0, 0}, {30, 17}, color::black * color::alpha(1.0f - fadeTime));
    }
}

void Level::RenderGui()
{
    float lengthCoeff = this->manager->GetTime() / this->songLength;
    render::primitives2d::quad({0, 0}, {lengthCoeff * 30, 0.25}, color::alpha(0.8));

    healthBar.Render();
}

void Level::UpdateGui()
{

}

void Level::UpdateMenu()
{
    if (showSettings) {
        if (SettingsMenu::Update()) {
            showSettings = false;
            SetSoundVolume(App::GetConfig()->getConfig().musicVolume);
        }
        return;
    }


    ImGui::SetNextWindowPos({320, 120}, ImGuiCond_Always);
    ImGui::Begin("Pause", nullptr, {320, 265}, 1, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);

    if (ImGui::Button("Return to game", {304, 50})) {
        this->Start();
    }

    if (ImGui::Button("Restart", {304, 50})) {
        this->Reset();
    }

    if (ImGui::Button("Settings", {304, 50})) {
        this->showSettings = true;
    }

    ImGui::Separator();

    if (ImGui::Button("Back to menu", {304, 50})) {
        game::GetMenu()->ShowLevelSelector();
        this->MakeFade();
    }

    ImGui::End();
}

void Level::PrepareComboText(NoteResult result)
{
    const char *labels[] = {
        "MISS?",
        "MISS",
        "BAD",
        "GOOD",
        "GREAT",
    };

    const vec4f colors[] = {
        color::uint2rgba(0x8b9bb4),
        color::uint2rgba(0xe43b44),
        color::uint2rgba(0xf77622),
        color::uint2rgba(0x63c74d),
        color::uint2rgba(0x0095e9)
    };

    comboText->color = colors[(int) result];

    if (combo > 1) {
        comboText->format("%s   %d", labels[(int) result], combo);
    } else {
        comboText->make(labels[(int) result]);
    }


    scoreText->format("%d", score);
}

void Level::MakeFade()
{
    if (!this->shouldClose) {
        this->shouldClose = true;
        this->fadeTime = 1;
    }
}

void Level::SetSoundVolume(float volume)
{
    App::GetSound()->getEngine()->setVolume(this->playingHandler, volume);
}

}

