/*
 * 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.
 */

#ifdef _MSC_VER
// instantiated template classes whose methods are defined elsewhere
# pragma warning(disable:4661)
#endif

#include "../OpenGL.hpp"
#include "../ImageWidgets.hpp"

#include "SubWidgetPrivateData.hpp"
#include "TopLevelWidgetPrivateData.hpp"
#include "WidgetPrivateData.hpp"
#include "WindowPrivateData.hpp"

START_NAMESPACE_DGL

// --------------------------------------------------------------------------------------------------------------------
// OpenGLImage

OpenGLImage::OpenGLImage()
    : ImageBase(),
      setupCalled(false),
      textureInit(false),
      textureId(0)
   #ifdef DGL_USE_GLES
    , convertedData(nullptr)
    , rawDataLast(nullptr)
    #endif
{
}

OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, const ImageFormat fmt)
    : ImageBase(rdata, w, h, fmt),
      setupCalled(false),
      textureInit(true),
      textureId(0)
   #ifdef DGL_USE_GLES
    , convertedData(nullptr)
    , rawDataLast(nullptr)
    #endif
{
    glGenTextures(1, &textureId);
    DISTRHO_SAFE_ASSERT(textureId != 0);
}

OpenGLImage::OpenGLImage(const char* const rdata, const Size<uint>& s, const ImageFormat fmt)
    : ImageBase(rdata, s, fmt),
      setupCalled(false),
      textureInit(true),
      textureId(0)
   #ifdef DGL_USE_GLES
    , convertedData(nullptr)
    , rawDataLast(nullptr)
    #endif
{
    glGenTextures(1, &textureId);
    DISTRHO_SAFE_ASSERT(textureId != 0);
}

OpenGLImage::OpenGLImage(const OpenGLImage& image)
    : ImageBase(image),
      setupCalled(false),
      textureInit(true),
      textureId(0)
   #ifdef DGL_USE_GLES
    , convertedData(nullptr)
    , rawDataLast(nullptr)
    #endif
{
    glGenTextures(1, &textureId);
    DISTRHO_SAFE_ASSERT(textureId != 0);
}

OpenGLImage::~OpenGLImage()
{
    if (textureId != 0)
        glDeleteTextures(1, &textureId);

   #ifdef DGL_USE_GLES
    std::free(convertedData);
   #endif
}

void OpenGLImage::loadFromMemory(const char* const rdata, const Size<uint>& s, const ImageFormat fmt) noexcept
{
    if (!textureInit)
    {
        textureInit = true;
        glGenTextures(1, &textureId);
        DISTRHO_SAFE_ASSERT(textureId != 0);
    }
    setupCalled = false;
    ImageBase::loadFromMemory(rdata, s, fmt);
}

OpenGLImage& OpenGLImage::operator=(const OpenGLImage& image) noexcept
{
    rawData = image.rawData;
    size    = image.size;
    format  = image.format;
    setupCalled = false;

    if (image.isValid() && !textureInit)
    {
        textureInit = true;
        glGenTextures(1, &textureId);
        DISTRHO_SAFE_ASSERT(textureId != 0);
    }

    return *this;
}

#if DGL_ALLOW_DEPRECATED_METHODS
OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, const GLenum fmt)
    : ImageBase(rdata, w, h, asDISTRHOImageFormat(fmt)),
      setupCalled(false),
      textureInit(true),
      textureId(0)
   #ifdef DGL_USE_GLES
    , convertedData(nullptr)
    , rawDataLast(nullptr)
    #endif
{
    glGenTextures(1, &textureId);
    DISTRHO_SAFE_ASSERT(textureId != 0);
}

OpenGLImage::OpenGLImage(const char* const rdata, const Size<uint>& s, const GLenum fmt)
    : ImageBase(rdata, s, asDISTRHOImageFormat(fmt)),
      setupCalled(false),
      textureInit(true),
      textureId(0)
   #ifdef DGL_USE_GLES
    , convertedData(nullptr)
    , rawDataLast(nullptr)
    #endif
{
    glGenTextures(1, &textureId);
    DISTRHO_SAFE_ASSERT(textureId != 0);
}
#endif

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

void SubWidget::PrivateData::display(const uint width, const uint height, const double autoScaleFactor)
{
    if (skipDrawing)
        return;

    bool needsDisableScissor = false;

    if (needsViewportScaling)
    {
        // limit viewport to widget bounds
        const int x = absolutePos.getX();
        const int w = static_cast<int>(self->getWidth());
        const int h = static_cast<int>(self->getHeight());

        if (d_isNotZero(viewportScaleFactor) && d_isNotEqual(viewportScaleFactor, 1.0))
        {
            glViewport(x,
                       -d_roundToIntPositive(height * viewportScaleFactor - height + absolutePos.getY()),
                       d_roundToIntPositive(width * viewportScaleFactor),
                       d_roundToIntPositive(height * viewportScaleFactor));
        }
        else
        {
            const int y = static_cast<int>(height - self->getHeight()) - absolutePos.getY();
            glViewport(x, y, w, h);
        }
    }
    else if (needsFullViewportForDrawing || (absolutePos.isZero() && self->getSize() == Size<uint>(width, height)))
    {
        // full viewport size
        glViewport(0, 0, static_cast<int>(width), static_cast<int>(height));
    }
    else
    {
        // set viewport pos
        glViewport(d_roundToIntPositive(absolutePos.getX() * autoScaleFactor),
                   -d_roundToIntPositive(absolutePos.getY() * autoScaleFactor),
                   static_cast<int>(width),
                   static_cast<int>(height));

        // then cut the outer bounds
        glScissor(d_roundToIntPositive(absolutePos.getX() * autoScaleFactor),
                  d_roundToIntPositive(height - (static_cast<int>(self->getHeight()) + absolutePos.getY()) * autoScaleFactor),
                  d_roundToIntPositive(self->getWidth() * autoScaleFactor),
                  d_roundToIntPositive(self->getHeight() * autoScaleFactor));

        glEnable(GL_SCISSOR_TEST);
        needsDisableScissor = true;
    }

    // display widget
    self->onDisplay();

    if (needsDisableScissor)
        glDisable(GL_SCISSOR_TEST);

    selfw->pData->displaySubWidgets(width, height, autoScaleFactor);
}

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

void TopLevelWidget::PrivateData::display()
{
    if (! selfw->pData->visible)
        return;

    const Size<uint> size(window.getSize());
    const uint width  = size.getWidth();
    const uint height = size.getHeight();

    // full viewport size
    glViewport(0, 0, static_cast<int>(width), static_cast<int>(height));

    // main widget drawing
    self->onDisplay();

    // now draw subwidgets if there are any
    selfw->pData->displaySubWidgets(width, height, window.pData->autoScaleFactor);
}

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

void Window::PrivateData::renderToPicture(const char* const filename,
                                          const GraphicsContext&,
                                          const uint width,
                                          const uint height)
{
    FILE* const f = fopen(filename, "w");
    DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);

    GLubyte* const pixels = new GLubyte[width * height * 3 * sizeof(GLubyte)];

    glFlush();
    glReadPixels(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_RGB, GL_UNSIGNED_BYTE, pixels);

    fprintf(f, "P3\n%d %d\n255\n", width, height);
    for (uint y = 0; y < height; y++)
    {
        for (uint i, x = 0; x < width; x++)
        {
            i = 3 * ((height - y - 1) * width + x);
            fprintf(f, "%3d %3d %3d ", pixels[i], pixels[i+1], pixels[i+2]);
        }
        fprintf(f, "\n");
    }

    delete[] pixels;
    fclose(f);
}

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

END_NAMESPACE_DGL
