/*
	GameEngine and Editor
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Terminal View

#include "TerminalView.h"
#include "IUiSystem.h"
#include "Utilities.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <chrono>
#include <cstdio>
#include <string>

namespace GameEngine {

TerminalView::TerminalView(const ApplicationFramework::IApplication& app)
    : m_application(app)
    , m_emulator(app)
{
}

void TerminalView::AddShowMenuItem()
{
    ImGui::MenuItem("Show Terminal", NULL, &m_open);
}

void TerminalView::Update()
{
    if (m_open)
    {
        ImGui::SetNextWindowBgAlpha(0.75f); // Transparent background
        if (ImGui::Begin((std::string(m_emulator.m_title) + "###Terminal").c_str(), &m_open))
        {
            ImGuiContext& g = *GImGui;
            const ImGuiStyle& style = g.Style;
            ImGui::PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
            ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 2);
            ImGui::SetNextFrameWantCaptureKeyboard(true);

            ImVec2 termSiz = ImGui::GetContentRegionAvail();
            if (!m_emulator.m_altScreenBuffer)
                termSiz.y = Terminal::TerminalEmulator::MAX_SCROLL_BACK_LINES * m_emulator.m_fontHeight;
            ImGuiWindow* termWin = ImGui::GetCurrentWindow();
            ImGuiID termId = termWin->GetID("termwin");

            ImGui::BeginChild(termId, termSiz, ImGuiWindowFlags_AlwaysVerticalScrollbar);
            if (m_emulator.m_needScrollToY)
            {
                ImGui::SetScrollFromPosY(termWin, ((m_emulator.m_cursorY + 5) * m_emulator.m_fontHeight) - termWin->Scroll.y, 1.0);
                m_emulator.m_needScrollToY = false;
            }

            auto siz = ImGui::GetContentRegionAvail();
            m_emulator.UpdateSize((int)siz.x, (int)siz.y);
            m_emulator.Update();

            ImGui::EndChild();

            ImGui::PopStyleVar();
            ImGui::PopStyleColor();
        }
        ImGui::End();
    }
}

void TerminalView::Initialize()
{
    m_emulator.Initialize();
}

void TerminalView::Shutdown()
{
    m_emulator.Shutdown();
}

TerminalView::Emulator::Emulator(const ApplicationFramework::IApplication& app)
    : m_application(app)
{
}

// virtual
void TerminalView::Emulator::HandleInput()
{
    ImGuiIO& io = ImGui::GetIO();
    auto shift = io.KeyShift;
    auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
    auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
    
    if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows))
    {
        if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)))
            SendChar('\n');
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Tab)))
            SendChar('\t');
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace)))
            SendChar('\b');
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape)))
            SendChar('\033');

        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Insert)))
            m_process.SendBytes("\033[2~", 4);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
            m_process.SendBytes("\033[3~", 4);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)))
            m_process.SendBytes("\033[5~", 4);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)))
            m_process.SendBytes("\033[6~", 4);

        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F1)))
            m_process.SendBytes("\033[11~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F2)))
            m_process.SendBytes("\033[12~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F3)))
            m_process.SendBytes("\033[13~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F4)))
            m_process.SendBytes("\033[14~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F5)))
            m_process.SendBytes("\033[15~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F6)))
            m_process.SendBytes("\033[17~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F7)))
            m_process.SendBytes("\033[18~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F8)))
            m_process.SendBytes("\033[19~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F9)))
            m_process.SendBytes("\033[20~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F10)))
            m_process.SendBytes("\033[21~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F11)))
            m_process.SendBytes("\033[23~", 5);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_F12)))
            m_process.SendBytes("\033[24~", 5);

        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
            m_process.SendBytes("\033[A", 3);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)))
            m_process.SendBytes("\033[B", 3);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)))
            m_process.SendBytes("\033[C", 3);
        if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)))
            m_process.SendBytes("\033[D", 3);
      if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
            m_process.SendBytes("\033[E", 3);
      if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
            m_process.SendBytes("\033[F", 3);

        // Send CTRL-C
        if (ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
            SendChar('\003');

        for (int i = 0; i < ImGui::GetIO().InputQueueCharacters.Size; i++)
        {
            ImWchar c = ImGui::GetIO().InputQueueCharacters[i];
            if (c != 0 && (c == '\n' || c >= 32))
                SendChar(c);  // TODO: support internationalization
        }
    }
}

// virtual
void TerminalView::Emulator::Display()
{
    ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
    // Stop it moving when resizing
    cursorScreenPos.y += -m_fontHeight + (int)ImGui::GetContentRegionAvail().y % m_fontHeight;

    int x = cursorScreenPos.x;
    int y = cursorScreenPos.y;
    auto* drawList = ImGui::GetWindowDrawList();

    auto textCol = ImGui::GetColorU32(ImGuiCol_Text) | 0xFF000000;
    auto& font = ImGui::GetIO().Fonts->Fonts[m_application.UiSystem()->FixedFontIndex()];

    //ImGui::Text("%5i", j);  // Line numbers
    for (int j = 0; j < m_currentLines; ++j)
    {
        for (int i = 0; i < m_currentColumns; ++i)
        {
            char ch = Line(j)[i].ch;
            uint32_t fgCol = Line(j)[i].foregroundColor;
            uint32_t bgCol = Line(j)[i].backgroundColor;
            ImVec2 pos(x + ((m_leftMargin + i) * m_fontWidth), y + j * m_fontHeight);
            ImVec2 pos2(pos.x + m_fontWidth, pos.y + m_fontHeight);
            if (bgCol)
                drawList->AddRectFilled(pos, pos2, bgCol);
            if (::isprint(ch))
                font->RenderChar(drawList, -1.0, pos, fgCol ? fgCol : textCol, ch);
        }
    }
}

// virtual
void TerminalView::Emulator::DrawCursor()
{
    // Draw the flashing text cursor
    if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows))
    {
        static uint64_t mStartTime = 0;
        auto timeEnd = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
        auto elapsed = timeEnd - mStartTime;
        if (elapsed > 400)
        {
            auto cx = (m_leftMargin + Utilities::clamp(m_cursorX, 0, m_currentColumns - 1)) * m_fontWidth;
            auto cy = (0 - Utilities::clamp(m_cursorY, 0, m_currentLines - 1)) * m_fontHeight;
            ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
            // Stop it moving when resizing
            cursorScreenPos.y += -m_fontHeight + (int)ImGui::GetContentRegionAvail().y % m_fontHeight;

            ImVec2 pos(cursorScreenPos.x + cx, cursorScreenPos.y - cy);
            ImVec2 pos2(pos.x + m_fontWidth, pos.y + m_fontHeight);
            ImGui::GetWindowDrawList()->AddRectFilled(pos, pos2, 0xFFFFFFFF);
            if (elapsed > 800)
                mStartTime = timeEnd;
        }
    }
}

} // GameEngine namespace
