//
// xemu User Interface
//
// Copyright (C) 2020-2022 Matt Borgerson
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
#include "monitor.hh"
#include "imgui.h"
#include "misc.hh"
#include "font-manager.hh"

// Portable helpers
static int   Stricmp(const char* str1, const char* str2)         { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; }
static char* Strdup(const char *str)                             { size_t len = strlen(str) + 1; void* buf = malloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)str, len); }
static void  Strtrim(char* str)                                  { char* str_end = str + strlen(str); while (str_end > str && str_end[-1] == ' ') str_end--; *str_end = 0; }

MonitorWindow::MonitorWindow()
{
    is_open = false;
    memset(InputBuf, 0, sizeof(InputBuf));
    HistoryPos = -1;
    AutoScroll = true;
    ScrollToBottom = false;
}
MonitorWindow::~MonitorWindow()
{
}

void MonitorWindow::Draw()
{
    if (!is_open) return;
    int style_pop_cnt = PushWindowTransparencySettings(true) + 1;
    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImU32(ImColor(0, 0, 0, 80)));
    ImGuiIO& io = ImGui::GetIO();
    ImVec2 window_pos = ImVec2(0,io.DisplaySize.y/2);
    ImGui::SetNextWindowPos(window_pos, ImGuiCond_Appearing);
    ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y/2), ImGuiCond_Appearing);
    if (ImGui::Begin("Monitor", &is_open, ImGuiWindowFlags_NoCollapse)) {
        const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text
        ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText

        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing
        ImGui::PushFont(g_font_mgr.m_fixed_width_font);

        // FIXME: Replace scroll to bottom hack when https://github.com/ocornut/imgui/issues/1972 is resolved.
        // ImGui does not provide any mechanism to adjust scrolling in an InputTextMultiline and does not
        // provide any other widget that allows for selectable text.
        char *buffer = xemu_get_monitor_buffer();
        size_t buffer_len = strlen(buffer);
        // Calculating the precise size will cause an unnecessary vertical scrollbar in the InputTextMultiline.
        int num_newlines = 2;
        const char *start = buffer;
        while (start) {
            start = strchr(start, '\n');
            if (start) {
                ++num_newlines;
                ++start;
            }
        }
        float input_height = fmax(ImGui::GetWindowHeight(),
                                  g_font_mgr.m_fixed_width_font->FontSize * num_newlines);

        ImGui::PushID("#MonitorOutput");
        ImGui::InputTextMultiline("",
                                  buffer,
                                  buffer_len + 1,
                                  ImVec2(-1.0f, input_height),
                                  ImGuiInputTextFlags_ReadOnly|ImGuiInputTextFlags_NoUndoRedo);
        ImGui::PopID();
        ImGui::PopFont();

        if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
            ImGui::SetScrollHereY(1.0f);
        }
        ScrollToBottom = false;

        ImGui::PopStyleVar();
        ImGui::EndChild();
        ImGui::Separator();
        // Command-line
        bool reclaim_focus = ImGui::IsWindowAppearing();

        ImGui::SetNextItemWidth(-1);
        ImGui::PushFont(g_font_mgr.m_fixed_width_font);
        if (ImGui::InputText("#commandline", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) {
            char* s = InputBuf;
            Strtrim(s);
            if (s[0])
                ExecCommand(s);
            strcpy(s, "");
            reclaim_focus = true;
        }
        ImGui::PopFont();
        // Auto-focus on window apparition
        ImGui::SetItemDefaultFocus();
        if (reclaim_focus) {
            ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
        }
    }
    ImGui::End();
    ImGui::PopStyleColor(style_pop_cnt);
}

void MonitorWindow::ToggleOpen(void)
{
    is_open = !is_open;
}

void MonitorWindow::ExecCommand(const char* command_line)
{
    xemu_run_monitor_command(command_line);

    // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal.
    HistoryPos = -1;
    for (int i = History.Size-1; i >= 0; i--)
        if (Stricmp(History[i], command_line) == 0)
        {
            free(History[i]);
            History.erase(History.begin() + i);
            break;
        }
    History.push_back(Strdup(command_line));

    // On commad input, we scroll to bottom even if AutoScroll==false
    ScrollToBottom = true;
}

int MonitorWindow::TextEditCallbackStub(ImGuiInputTextCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks
{
    MonitorWindow* console = (MonitorWindow*)data->UserData;
    return console->TextEditCallback(data);
}

int MonitorWindow::TextEditCallback(ImGuiInputTextCallbackData* data)
{
    switch (data->EventFlag)
    {
    case ImGuiInputTextFlags_CallbackHistory:
        {
            // Example of HISTORY
            const int prev_history_pos = HistoryPos;
            if (data->EventKey == ImGuiKey_UpArrow)
            {
                if (HistoryPos == -1)
                    HistoryPos = History.Size - 1;
                else if (HistoryPos > 0)
                    HistoryPos--;
            }
            else if (data->EventKey == ImGuiKey_DownArrow)
            {
                if (HistoryPos != -1)
                    if (++HistoryPos >= History.Size)
                        HistoryPos = -1;
            }

            // A better implementation would preserve the data on the current input line along with cursor position.
            if (prev_history_pos != HistoryPos)
            {
                const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : "";
                data->DeleteChars(0, data->BufTextLen);
                data->InsertChars(0, history_str);
            }
        }
    }
    return 0;
}

MonitorWindow monitor_window;
