/*
 * DISTRHO Plugin Framework (DPF)
 * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any purpose with
 * or without fee is hereby granted, provided that the above copyright notice and this
 * permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "tests.hpp"
#include "dgl.cpp"
#include "dgl/src/Color.cpp"
#include "dgl/src/EventHandlers.cpp"
#include "dgl/src/ImageBase.cpp"
#ifdef DGL_OPENGL
#include "dgl/src/NanoVG.cpp"
#include "dgl/src/Resources.cpp"
#endif

#include "widgets/ExampleColorWidget.hpp"
#include "widgets/ExampleImagesWidget.hpp"
#include "widgets/ExampleRectanglesWidget.hpp"
#include "widgets/ExampleShapesWidget.hpp"
#ifdef DGL_OPENGL
#include "widgets/ExampleTextWidget.hpp"
#endif
#include "widgets/ResizeHandle.hpp"

#include "demo_res/DemoArtwork.cpp"
#include "images_res/CatPics.cpp"

#ifdef DGL_CAIRO
typedef DGL_NAMESPACE::CairoImage DemoImage;
#endif
#ifdef DGL_OPENGL
typedef DGL_NAMESPACE::OpenGLImage DemoImage;
#endif
#ifdef DGL_VULKAN
typedef DGL_NAMESPACE::VulkanImage DemoImage;
#endif

START_NAMESPACE_DGL

// Partial specialization is not allowed in C++, so we need to define these here
template<> inline
ExampleImagesWidget<SubWidget, DemoImage>::ExampleImagesWidget(Widget* const parentWidget)
: SubWidget(parentWidget) { init(parentWidget->getApp()); }

template<> inline
ExampleImagesWidget<TopLevelWidget, DemoImage>::ExampleImagesWidget(Window& windowToMapTo)
: TopLevelWidget(windowToMapTo) { init(windowToMapTo.getApp()); }

template<>
ExampleImagesWidget<StandaloneWindow, DemoImage>::ExampleImagesWidget(Application& app)
: StandaloneWindow(app) { init(app); done(); }

typedef ExampleImagesWidget<SubWidget, DemoImage> ExampleImagesSubWidget;
typedef ExampleImagesWidget<TopLevelWidget, DemoImage> ExampleImagesTopLevelWidget;
typedef ExampleImagesWidget<StandaloneWindow, DemoImage> ExampleImagesStandaloneWindow;

// --------------------------------------------------------------------------------------------------------------------
// Left side tab-like widget

class LeftSideWidget : public SubWidget
{
public:
   #ifdef DGL_OPENGL
    static const int kPageCount = 5;
   #else
    static const int kPageCount = 4;
   #endif

    class Callback
    {
    public:
        virtual ~Callback() {}
        virtual void curPageChanged(int curPage) = 0;
    };

    LeftSideWidget(Widget* parent, Callback* const cb)
        : SubWidget(parent),
          callback(cb),
          curPage(0),
          curHover(-1)
    {

        using namespace DemoArtwork;
        img1.loadFromMemory(ico1Data, ico1Width, ico1Height, kImageFormatBGR);
        img2.loadFromMemory(ico2Data, ico2Width, ico2Height, kImageFormatBGR);
        img3.loadFromMemory(ico3Data, ico3Width, ico2Height, kImageFormatBGR);
        img4.loadFromMemory(ico4Data, ico4Width, ico4Height, kImageFormatBGR);

       #ifdef DGL_OPENGL
        img5.loadFromMemory(ico5Data, ico5Width, ico5Height, kImageFormatBGR);

        // for text
        nvg.loadSharedResources();
       #endif
    }

protected:
    void onDisplay() override
    {
        const GraphicsContext& context(getGraphicsContext());
        const double scaleFactor = getWindow().getScaleFactor();
        const int iconSize = bgIcon.getWidth();

        Color(0.027f, 0.027f, 0.027f).setFor(context);
        Rectangle<uint>(0, 0, getSize()).draw(context);

        bgIcon.setY(curPage * iconSize + curPage * 3 * scaleFactor);

        Color(0.129f, 0.129f, 0.129f).setFor(context);
        bgIcon.draw(context);

        Color(0.184f, 0.184f, 0.184f).setFor(context);
        bgIcon.drawOutline(context);

        if (curHover != curPage && curHover != -1)
        {
            Rectangle<int> rHover(1 * scaleFactor, curHover * iconSize + curHover * 3 * scaleFactor,
                                  iconSize - 2 * scaleFactor, iconSize - 2 * scaleFactor);

            Color(0.071f, 0.071f, 0.071f).setFor(context);
            rHover.draw(context);

            Color(0.102f, 0.102f, 0.102f).setFor(context);
            rHover.drawOutline(context);
        }

        Color(0.184f, 0.184f, 0.184f).setFor(context);
        lineSep.draw(context);

        // reset color
        Color(1.0f, 1.0f, 1.0f, 1.0f).setFor(context, true);

        const int pad = iconSize/2 - DemoArtwork::ico1Width/2;

        img1.drawAt(context, pad, pad);
        img2.drawAt(context, pad, pad + 3 + iconSize);
        img3.drawAt(context, pad, pad + 6 + iconSize*2);
        img4.drawAt(context, pad, pad + 9 + iconSize*3);

       #ifdef DGL_OPENGL
        img5.drawAt(context, pad, pad + 12 + iconSize*4);

        // draw some text
        nvg.beginFrame(this);

        nvg.fontSize(23.0f * scaleFactor);
        nvg.textAlign(NanoVG::ALIGN_LEFT|NanoVG::ALIGN_TOP);
        //nvg.textLineHeight(20.0f);

        nvg.fillColor(220,220,220,220);
        nvg.textBox(10 * scaleFactor, 420 * scaleFactor, iconSize, "Haha,", nullptr);
        nvg.textBox(15 * scaleFactor, 440 * scaleFactor, iconSize, "Look!", nullptr);

        nvg.endFrame();
       #endif
    }

    bool onMouse(const MouseEvent& ev) override
    {
        if (ev.button != 1 || ! ev.press)
            return false;
        if (! contains(ev.pos))
            return false;

        const int iconSize = bgIcon.getWidth();

        for (int i=0; i<kPageCount; ++i)
        {
            bgIcon.setY(i*iconSize + i*3);

            if (bgIcon.contains(ev.pos))
            {
                curPage = i;
                callback->curPageChanged(i);
                repaint();
                break;
            }
        }

        return true;
    }

    bool onMotion(const MotionEvent& ev) override
    {
        if (contains(ev.pos))
        {
            const int iconSize = bgIcon.getWidth();

            for (int i=0; i<kPageCount; ++i)
            {
                bgIcon.setY(i*iconSize + i*3);

                if (bgIcon.contains(ev.pos))
                {
                    if (curHover == i)
                        return true;

                    curHover = i;
                    repaint();
                    return true;
                }
            }

            if (curHover == -1)
                return true;

            curHover = -1;
            repaint();
            return true;
        }
        else
        {
            if (curHover == -1)
                return false;

            curHover = -1;
            repaint();
            return true;
        }
    }

    void onResize(const ResizeEvent& ev) override
    {
        const uint width  = ev.size.getWidth();
        const uint height = ev.size.getHeight();
        const double scaleFactor = getWindow().getScaleFactor();

        bgIcon.setWidth(width - 4 * scaleFactor);
        bgIcon.setHeight(width - 4 * scaleFactor);

        lineSep.setStartPos(width, 0);
        lineSep.setEndPos(width, height);
    }

private:
    Callback* const callback;
    int curPage, curHover;
    Rectangle<double> bgIcon;
    Line<int> lineSep;
    DemoImage img1, img2, img3, img4, img5;

   #ifdef DGL_OPENGL
    // for text
    NanoVG nvg;
   #endif
};

// --------------------------------------------------------------------------------------------------------------------
// Main Demo Window, having a left-side tab-like widget and main area for current widget

class DemoWindow : public StandaloneWindow,
                   public LeftSideWidget::Callback
{
    static const int kSidebarWidth = 81;

public:
   #if defined(DGL_CAIRO)
    static constexpr const char* const kExampleWidgetName = "Demo - Cairo";
   #elif defined(DGL_OPENGL)
    static constexpr const char* const kExampleWidgetName = "Demo - OpenGL";
   #elif defined(DGL_VULKAN)
    static constexpr const char* const kExampleWidgetName = "Demo - Vulkan";
   #endif

    DemoWindow(Application& app)
        : StandaloneWindow(app),
          curWidget(nullptr)
    {
        const ScopedGraphicsContext sgc(*this);
        const double scaleFactor = getScaleFactor();

        wColor = new ExampleColorSubWidget(this);
        wColor->hide();
        wColor->setAbsoluteX(kSidebarWidth * scaleFactor);

        wImages = new ExampleImagesSubWidget(this);
        wImages->hide();
        wImages->setAbsoluteX(kSidebarWidth * scaleFactor);

        wRects = new ExampleRectanglesSubWidget(this);
        wRects->hide();
        wRects->setAbsoluteX(kSidebarWidth * scaleFactor);

        wShapes = new ExampleShapesSubWidget(this);
        wShapes->hide();
        wShapes->setAbsoluteX(kSidebarWidth * scaleFactor);

       #ifdef DGL_OPENGL
        wText = new ExampleTextSubWidget(this),
        wText->hide();
        wText->setAbsoluteX(kSidebarWidth * scaleFactor);
       #endif
        wLeft = new LeftSideWidget(this, this),
        wLeft->setAbsolutePos(2 * scaleFactor, 2 * scaleFactor);

        resizer = new ResizeHandle(this);

        curPageChanged(0);
        done();
    }

protected:
    void curPageChanged(int curPage) override
    {
        if (curWidget != nullptr)
            curWidget->hide();

        switch (curPage)
        {
        case 0:
            curWidget = wColor;
            break;
        case 1:
            curWidget = wImages;
            break;
        case 2:
            curWidget = wRects;
            break;
        case 3:
            curWidget = wShapes;
            break;
       #ifdef DGL_OPENGL
        case 4:
            curWidget = wText;
            break;
       #endif
        default:
            curWidget = nullptr;
            break;
        }

        if (curWidget != nullptr)
            curWidget->show();
    }

    void onDisplay() override
    {
    }

    void onReshape(uint width, uint height) override
    {
        StandaloneWindow::onReshape(width, height);

        const double scaleFactor = getScaleFactor();

        if (width < kSidebarWidth * scaleFactor)
            return;

        const Size<uint> size(width - kSidebarWidth * scaleFactor, height);
        wColor->setSize(size);
        wImages->setSize(size);
        wRects->setSize(size);
        wShapes->setSize(size);
       #ifdef DGL_OPENGL
        wText->setSize(size);
       #endif
        wLeft->setSize((kSidebarWidth - 4) * scaleFactor, (height - 4) * scaleFactor);
    }

private:
    ScopedPointer<ExampleColorSubWidget> wColor;
    ScopedPointer<ExampleImagesSubWidget> wImages;
    ScopedPointer<ExampleRectanglesSubWidget> wRects;
    ScopedPointer<ExampleShapesSubWidget> wShapes;
   #ifdef DGL_OPENGL
    ScopedPointer<ExampleTextSubWidget> wText;
   #endif
    ScopedPointer<LeftSideWidget> wLeft;
    ScopedPointer<ResizeHandle> resizer;

    Widget* curWidget;
};

// --------------------------------------------------------------------------------------------------------------------
// Special handy function that runs a StandaloneWindow inside the function scope

template <class ExampleWidgetStandaloneWindow>
void createAndShowExampleWidgetStandaloneWindow(Application& app)
{
    ExampleWidgetStandaloneWindow swin(app);
    const double scaleFactor = swin.getScaleFactor();
    swin.setGeometryConstraints(128 * scaleFactor, 128 * scaleFactor);
    swin.setResizable(true);
    swin.setSize(600 * scaleFactor, 500 * scaleFactor);
    swin.setTitle(ExampleWidgetStandaloneWindow::kExampleWidgetName);
    swin.show();
    app.exec();
}

// --------------------------------------------------------------------------------------------------------------------

END_NAMESPACE_DGL

int main(int argc, char* argv[])
{
    using DGL_NAMESPACE::Application;
    using DGL_NAMESPACE::DemoWindow;
    using DGL_NAMESPACE::ExampleColorStandaloneWindow;
    using DGL_NAMESPACE::ExampleImagesStandaloneWindow;
    using DGL_NAMESPACE::ExampleRectanglesStandaloneWindow;
    using DGL_NAMESPACE::ExampleShapesStandaloneWindow;
   #ifdef DGL_OPENGL
    using DGL_NAMESPACE::ExampleTextStandaloneWindow;
   #endif
    using DGL_NAMESPACE::createAndShowExampleWidgetStandaloneWindow;

    Application app;

    if (argc > 1)
    {
        /**/ if (std::strcmp(argv[1], "color") == 0)
            createAndShowExampleWidgetStandaloneWindow<ExampleColorStandaloneWindow>(app);
        else if (std::strcmp(argv[1], "images") == 0)
            createAndShowExampleWidgetStandaloneWindow<ExampleImagesStandaloneWindow>(app);
        else if (std::strcmp(argv[1], "rectangles") == 0)
            createAndShowExampleWidgetStandaloneWindow<ExampleRectanglesStandaloneWindow>(app);
        else if (std::strcmp(argv[1], "shapes") == 0)
            createAndShowExampleWidgetStandaloneWindow<ExampleShapesStandaloneWindow>(app);
       #ifdef DGL_OPENGL
        else if (std::strcmp(argv[1], "text") == 0)
            createAndShowExampleWidgetStandaloneWindow<ExampleTextStandaloneWindow>(app);
       #endif
        else
            d_stderr2("Invalid demo mode, must be one of: color, images, rectangles or shapes");
    }
    else
    {
        createAndShowExampleWidgetStandaloneWindow<DemoWindow>(app);
    }

    return 0;
}

// --------------------------------------------------------------------------------------------------------------------
