/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */
#pragma once

#include <osgEarthImGui/ImGuiPanel>

namespace osgEarth
{
    /**
    * OSG notify handler to redirect notify messages to an ImGui text buffer
    * usage:
    *
    * ImGuiNotifyHandler* notifyHandler = new ImGuiNotifyHandler();
    * osg::setNotifyHandler(notifyHandler);
    * osgEarth::setNotifyHandler(notifyHandler);
    */
    class ImGuiNotifyHandler : public osg::NotifyHandler
    {
    public:
        void notify(osg::NotifySeverity severity, const char *message)
        {
            std::lock_guard<std::mutex> lk(_mutex);
            int old_size = Buf.size();
            Buf.append(message);
            for (int new_size = Buf.size(); old_size < new_size; old_size++)
                if (Buf[old_size] == '\n')
                    LineOffsets.push_back(old_size + 1);
        }

        void Clear()
        {
            Buf.clear();
            LineOffsets.clear();
            LineOffsets.push_back(0);
        }

        Mutex _mutex;
        ImGuiTextBuffer     Buf;
        ImVector<int>       LineOffsets;        // Index to lines offset. We maintain this with AddLog() calls, allowing us to have a random access on lines
    };



    struct NotifyGUI : public ImGuiPanel
    {
        NotifyGUI() :
            ImGuiPanel("Notify Log")
        {
            _handler = dynamic_cast<ImGuiNotifyHandler*>(osg::getNotifyHandler());
        }

        void draw(osg::RenderInfo& ri) override
        {
            if (!isVisible() || _handler == nullptr)
                return;

            ImGui::Begin(name(), visible());
            {
                bool clear = ImGui::Button("Clear");
                ImGui::SameLine();
                Filter.Draw("Filter", -100.0f);

                ImGui::Separator();
                ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);

                if (clear)
                    _handler->Clear();

                ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
                const char* buf = _handler->Buf.begin();
                const char* buf_end = _handler->Buf.end();

                if (Filter.IsActive())
                {
                    // In this example we don't use the clipper when Filter is enabled.
                    // This is because we don't have a random access on the result on our filter.
                    // A real application processing logs with ten of thousands of entries may want to store the result of search/filter.
                    // especially if the filtering function is not trivial (e.g. reg-exp).
                    for (int line_no = 0; line_no < _handler->LineOffsets.Size; line_no++)
                    {
                        const char* line_start = buf + _handler->LineOffsets[line_no];
                        const char* line_end = (line_no + 1 < _handler->LineOffsets.Size) ? (buf + _handler->LineOffsets[line_no + 1] - 1) : buf_end;
                        if (Filter.PassFilter(line_start, line_end))
                            ImGui::TextUnformatted(line_start, line_end);
                    }
                }
                else
                {
                    // The simplest and easy way to display the entire buffer:
                    //   ImGui::TextUnformatted(buf_begin, buf_end);
                    // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward to skip non-visible lines.
                    // Here we instead demonstrate using the clipper to only process lines that are within the visible area.
                    // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them on your side is recommended.
                    // Using ImGuiListClipper requires A) random access into your data, and B) items all being the  same height,
                    // both of which we can handle since we an array pointing to the beginning of each line of text.
                    // When using the filter (in the block of code above) we don't have random access into the data to display anymore, which is why we don't use the clipper.
                    // Storing or skimming through the search result would make it possible (and would be recommended if you want to search through tens of thousands of entries)
                    ImGuiListClipper clipper;
                    clipper.Begin(_handler->LineOffsets.Size);
                    while (clipper.Step())
                    {
                        for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
                        {
                            const char* line_start = buf + _handler->LineOffsets[line_no];
                            const char* line_end = (line_no + 1 < _handler->LineOffsets.Size) ? (buf + _handler->LineOffsets[line_no + 1] - 1) : buf_end;
                            ImGui::TextUnformatted(line_start, line_end);
                        }
                    }
                    clipper.End();
                }
                ImGui::PopStyleVar();

                if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
                    ImGui::SetScrollHereY(1.0f);

                ImGui::EndChild();
                ImGui::End();
            }
        }

        ImGuiNotifyHandler* _handler;
        ImGuiTextFilter Filter;
    };
}

