#include <imgui/imgui.h>
#include <cstring>
#include <resources.h>
#include <window.h>
#include <GLFW/glfw3.h>
#include <app.h>
#include "levelEditor.h"

std::string iconNames[2][4] = {
    {"editor-cross.png", "editor-circle.png", "editor-square.png", "editor-triangle.png"},
    {"editor-down.png", "editor-right.png", "editor-left.png", "editor-up.png"}
};

std::string placeholderNames[2][4] = {
    {"placeholder-cross.png", "placeholder-circle.png", "placeholder-square.png", "placeholder-triangle.png"},
    {"placeholder-down.png", "placeholder-right.png", "placeholder-left.png", "placeholder-up.png"}
};

bool comprareNoteTime(const game::noteInfo *a, const game::noteInfo *b);

namespace editor {

LevelEditor::LevelEditor(const game::levelInfo &level, Editor *root)
{
    this->root = root;
    this->info = &level;

    active = true;
    musicPlaying = false;

    showTemplateList = false;
    showNewTemplate = false;

    background = new render::sprite::image();
    recorder = new NoteRecorder();
    music = new SoLoud::WavStream();

    this->Load();

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 4; j++) {
            resources::atlas.getIcon(iconNames[i][j], buttonIcons[i][j]);
            buttonIcons[i][j].size = vec2f(0.5);
        }
    }

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 4; j++) {
            resources::atlas.getIcon(placeholderNames[i][j], placeholderIcons[i][j]);
            placeholderIcons[i][j].size = vec2f(1);
        }
    }

    resources::atlas.getIcon("editor-long.png", longIcon);
    longIcon.size = {1, 0.25};
}

LevelEditor::~LevelEditor()
{
    delete background;
}

void LevelEditor::Update()
{
    if (recorder->recording || this->musicPlaying) {
        if (App::GetInput()->IsButtonPressed(GLFW_KEY_ESCAPE)) {
            StopRealTimeRecord();
        } else if (recorder->recording) {

            songPosition = App::GetSound()->getEngine()->getStreamPosition(this->playingMusic);

            recorder->SetPosition(songPosition);

            //songPosition = recorder->GetPosition();
            recorder->Update();
        } else {

            songPosition = App::GetSound()->getEngine()->getStreamPosition(this->playingMusic);

            //songPosition += window::deltaTime;
        }

        if (songPosition > songLength) {
            StopRealTimeRecord();
        }
    }

    if (!recorder->recording) {
        if (App::GetInput()->IsButtonJustPressed(GLFW_KEY_SPACE)) {
            if (this->musicPlaying) {
                StopRealTimeRecord();
            } else {
                StartMusic(songPosition);
            }
        }
    }


    UpdateGui();

    PrepareOnScreenNotes();
}

void LevelEditor::UpdateGui()
{
    if (ImGui::BeginMainMenuBar()) {

        if (ImGui::BeginMenu("File")) {

            if (ImGui::MenuItem("Save")) {
                this->Save();
            }
            ImGui::Separator();
            if (ImGui::MenuItem("Reload")) {
                this->Load();
            }
            if (ImGui::MenuItem("Close")) {
                this->active = false;
            }

            ImGui::EndMenu();
        }

        if (ImGui::BeginMenu("Templates")) {
            if (ImGui::MenuItem("Create")) {
                ShowNewTemplate();
            }

            if (ImGui::MenuItem("Show list")) {
                showTemplateList = true;
            }

            ImGui::EndMenu();
        }

        ImGui::Dummy({100, 0});
        ImGui::Text(info->name.c_str());

        ImGui::EndMainMenuBar();
    }

    UpdateTimeline();
    UpdateNoteSelector();
    UpdateTemplates();

    ImGui::SetNextWindowPos({960, 30}, ImGuiCond_Always);
    ImGui::Begin("left-panel", nullptr, {320, 450}, 1, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize);

    if (activeNote != notes.end()) {
        UpdateNoteEditor();
    }

    ImGui::End();
}

void LevelEditor::Load()
{
    background->load(info->backgroundFile, true);
    music->load(info->musicFile.c_str());

    songLength = music->getLength();
    songPosition = 1;

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

    notes.clear();
    activeNote = notes.end();

    LoadNotes();
    LoadTemplates();
}

void LevelEditor::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 LevelEditor::Save()
{
    this->notes.sort(comprareNoteTime);

    game::noteInfo *buffer = new game::noteInfo[this->notes.size()];

    game::notesHeader header;
    memset(&header, 0, sizeof(header));

    header.notesOffset = sizeof(header);

    for (const game::noteInfo *i: this->notes) {
        memcpy(&buffer[header.notesCount++], i, sizeof(game::noteInfo));
    }

    FILE *f = fopen(this->info->notesFile.c_str(), "wb");

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

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

    fclose(f);

    delete buffer;
}

void LevelEditor::StartMusic(float time)
{
    playingMusic = App::GetSound()->getEngine()->play(*music, 1, 0, 1);
    App::GetSound()->getEngine()->seek(playingMusic, time);
    App::GetSound()->getEngine()->setPause(playingMusic, false);
    this->musicPlaying = true;
}

void LevelEditor::SeekMusic(float time)
{
    if (this->musicPlaying) {
        App::GetSound()->getEngine()->seek(playingMusic, time);
    }
}

void LevelEditor::StopMusic()
{
    App::GetSound()->getEngine()->stop(playingMusic);
    this->musicPlaying = false;
}

void LevelEditor::StartRealTimeRecord(float time)
{
    activeNote = notes.end();
    this->StartMusic(time);
    recorder->Start(time);
}

void LevelEditor::StopRealTimeRecord()
{
    this->StopMusic();
    if (this->recorder->recording) {
        recorder->Stop();
        CopyNotes();
    }
}

void LevelEditor::CopyNotes()
{
    if (recorder->recordedNotes.empty()) {
        return;
    }

    float startTime = recorder->recordedNotes.front()->time;
    float endTime = recorder->recordedNotes.back()->time;

    if (recorder->recordedNotes.back()->type == 1) {
        endTime += recorder->recordedNotes.back()->duration;
    }

    notes.remove_if([startTime, endTime](const game::noteInfo *note) {
        if (note->time <= endTime && note->getEndTime() >= startTime) {
            delete note; // память освободить не забыть бы
            return true;
        }

        return false;
    });

    for (const game::noteInfo *i: recorder->recordedNotes) {
        game::noteInfo *note = new game::noteInfo;
        memcpy(note, i, sizeof(game::noteInfo));
        notes.push_back(note);
    }

    SortNotes();
    this->recorder->Reset();
}

void LevelEditor::InsertDefaultNote(float time) {

    game::noteInfo *note = new game::noteInfo();
    note->button = 0;
    note->type = 0;
    note->time = time;
    note->duration = 0.1;

    notes.push_back(note);

    activeNote = std::prev(notes.end());
    SortNotes();
}

void LevelEditor::PrepareOnScreenNotes()
{
    constexpr float halfTime = timelineZoom / 2;
    notesOnScreen.clear();

    for (game::noteInfo *i: notes) {
        if (i->getEndTime() + halfTime >= songPosition && i->time - halfTime < songPosition) {
            notesOnScreen.push_back(i);
        }
    }

    notesOnScreen.sort(comprareNoteTime);
}

void LevelEditor::Render()
{
    background->draw({0, 0}, {30, 17});

    RenderGrid();
    RenderTimeline();
}

void LevelEditor::RenderGrid()
{
    constexpr vec2f renderOffset(3, 2);

    for (int i = 0; i <= 24; i++) {
        render::primitives2d::line(vec2f{i + 3, 2}, vec2f{i + 3, 14}, color::alpha(0.5));
    }

    for (int i = 0; i < 24; i++) {
        render::primitives2d::line(vec2f{i + 3.5, 2}, vec2f{i + 3.5, 14}, color::alpha(0.15));
    }

    for (int i = 0; i <= 12; i++) {
        render::primitives2d::line(vec2f{3, i + 2}, vec2f{27, i + 2}, color::alpha(0.5));
    }

    for (int i = 0; i < 12; i++) {
        render::primitives2d::line(vec2f{3, i + 2.5}, vec2f{27, i + 2.5}, color::alpha(0.15));
    }

    for (game::noteInfo *i: notesOnScreen) {
        render::sprite::icon *noteIcon = &placeholderIcons[i->type == 2][i->button];

        if (i == (*activeNote)) {
            continue;
        }

        noteIcon->draw(i->position + renderOffset);
    }

    if (activeNote != notes.end()) {
        game::noteInfo *i = (*activeNote);
        render::sprite::icon *noteIcon = &placeholderIcons[i->type == 2][i->button];

        noteIcon->color = noteColors[i->button];
        longIcon.color = noteColors[i->button];

        longIcon.size.x = 40;
        longIcon.scale.x = 1;
        longIcon.angle = i->angle;

        longIcon.draw(i->position + renderOffset);
        longIcon.angle = 0;
        longIcon.size.x = 1;

        noteIcon->draw(i->position + renderOffset);
        noteIcon->color = color::white;
    }

    render::primitives2d::quad({30, 0}, {10, 22}, color::black);
}

bool LevelEditor::ShouldClose()
{
    return !active;
}

void LevelEditor::SortNotes()
{
    this->notes.sort(comprareNoteTime);
}

}

bool comprareNoteTime(const game::noteInfo *a, const game::noteInfo *b) {
    return a->time < b->time;
}

