// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "QVTKOpenGLWindow.h"

#include <QApplication>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <QPointer>
#include <QScopedValueRollback>
#include <QtDebug>

#include "QVTKInteractor.h"
#include "QVTKInteractorAdapter.h"
#include "QVTKRenderWindowAdapter.h"
#include "vtkCommand.h"
#include "vtkGenericOpenGLRenderWindow.h"
#include "vtkInteractorStyleTrackballCamera.h"
#include "vtkLogger.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGLState.h"

VTK_ABI_NAMESPACE_BEGIN
QVTKOpenGLWindow::QVTKOpenGLWindow(QOpenGLWindow::UpdateBehavior ub, QWindow* p)
  : QVTKOpenGLWindow(vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New(), nullptr, ub, p)
{
}

QVTKOpenGLWindow::QVTKOpenGLWindow(
  QOpenGLContext* shareContext, QOpenGLWindow::UpdateBehavior ub, QWindow* p)
  : QVTKOpenGLWindow(vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New(), shareContext, ub, p)
{
}

QVTKOpenGLWindow::QVTKOpenGLWindow(
  vtkGenericOpenGLRenderWindow* rw, QOpenGLWindow::UpdateBehavior ub, QWindow* p)
  : QVTKOpenGLWindow(rw, nullptr, ub, p)
{
}

QVTKOpenGLWindow::QVTKOpenGLWindow(vtkGenericOpenGLRenderWindow* renderWin,
  QOpenGLContext* shareContext, QOpenGLWindow::UpdateBehavior ub, QWindow* p)
  : Superclass(shareContext, ub, p)
  , RenderWindow(nullptr)
  , RenderWindowAdapter(nullptr)
  , EnableHiDPI(true)
  , UnscaledDPI(72)
  , CustomDevicePixelRatio(0.0)
  , DefaultCursor(QCursor(Qt::ArrowCursor))
{
  this->setRenderWindow(renderWin);
}

//------------------------------------------------------------------------------
QVTKOpenGLWindow::~QVTKOpenGLWindow()
{
  this->makeCurrent();
  this->cleanupContext();
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::setRenderWindow(vtkRenderWindow* win)
{
  vtkGenericOpenGLRenderWindow* gwin = vtkGenericOpenGLRenderWindow::SafeDownCast(win);
  if (gwin == nullptr && win != nullptr)
  {
    qDebug() << "QVTKOpenGLWindow requires a `vtkGenericOpenGLRenderWindow`. `"
             << win->GetClassName() << "` is not supported.";
  }
  this->setRenderWindow(gwin);
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::setRenderWindow(vtkGenericOpenGLRenderWindow* win)
{
  if (this->RenderWindow == win)
  {
    return;
  }

  // this will release all OpenGL resources associated with the old render
  // window, if any.
  if (this->RenderWindowAdapter)
  {
    this->makeCurrent();
    this->RenderWindowAdapter.reset(nullptr);
  }
  this->RenderWindow = win;
  if (this->RenderWindow)
  {
    this->RenderWindow->SetReadyForRendering(false);
    this->RenderWindow->SetFrameBlitModeToNoBlit();

    // if an interactor wasn't provided, we'll make one by default
    if (!this->RenderWindow->GetInteractor())
    {
      // create a default interactor
      vtkNew<QVTKInteractor> iren;
      // iren->SetUseTDx(this->UseTDx);
      this->RenderWindow->SetInteractor(iren);
      iren->Initialize();

      // now set the default style
      vtkNew<vtkInteractorStyleTrackballCamera> style;
      iren->SetInteractorStyle(style);
    }

    if (this->isValid())
    {
      // this typically means that the render window is being changed after the
      // QVTKOpenGLWindow has initialized itself in a previous update
      // pass, so we emulate the steps to ensure that the new vtkRenderWindow is
      // brought to the same state (minus the actual render).
      this->makeCurrent();
      this->initializeGL();
      this->updateSize();
    }
  }
}

//------------------------------------------------------------------------------
vtkRenderWindow* QVTKOpenGLWindow::renderWindow() const
{
  return this->RenderWindow;
}

//------------------------------------------------------------------------------
QVTKInteractor* QVTKOpenGLWindow::interactor() const
{
  return this->RenderWindow ? QVTKInteractor::SafeDownCast(this->RenderWindow->GetInteractor())
                            : nullptr;
}

//------------------------------------------------------------------------------
QSurfaceFormat QVTKOpenGLWindow::defaultFormat(bool stereo_capable)
{
  return QVTKRenderWindowAdapter::defaultFormat(stereo_capable);
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::setEnableTouchEventProcessing(bool enable)
{
  this->EnableTouchEventProcessing = enable;
  if (this->RenderWindowAdapter)
  {
    this->RenderWindowAdapter->setEnableTouchEventProcessing(enable);
  }
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::setEnableHiDPI(bool enable)
{
  this->EnableHiDPI = enable;
  if (this->RenderWindowAdapter)
  {
    this->RenderWindowAdapter->setEnableHiDPI(enable);
  }
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::setUnscaledDPI(int dpi)
{
  this->UnscaledDPI = dpi;
  if (this->RenderWindowAdapter)
  {
    this->RenderWindowAdapter->setUnscaledDPI(dpi);
  }
}
//-----------------------------------------------------------------------------
void QVTKOpenGLWindow::setCustomDevicePixelRatio(double sf)
{
  this->CustomDevicePixelRatio = sf;
  if (this->RenderWindowAdapter)
  {
    this->RenderWindowAdapter->setCustomDevicePixelRatio(sf);
  }
}

//-----------------------------------------------------------------------------
double QVTKOpenGLWindow::effectiveDevicePixelRatio() const
{
  return this->CustomDevicePixelRatio > 0.0 ? this->CustomDevicePixelRatio
                                            : this->devicePixelRatioF();
}
//------------------------------------------------------------------------------
void QVTKOpenGLWindow::setDefaultCursor(const QCursor& cursor)
{
  this->DefaultCursor = cursor;
  if (this->RenderWindowAdapter)
  {
    this->RenderWindowAdapter->setDefaultCursor(cursor);
  }
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::initializeGL()
{
  this->Superclass::initializeGL();
  if (this->RenderWindow)
  {
    Q_ASSERT(this->RenderWindowAdapter.data() == nullptr);

    if (!this->RenderWindow->GetInitialized())
    {
#if !defined(__APPLE__)
      auto loadFunc = [](
                        void* userData, const char* name) -> vtkOpenGLRenderWindow::VTKOpenGLAPIProc
      {
        if (auto* context = reinterpret_cast<QOpenGLContext*>(userData))
        {
          if (auto* symbol = context->getProcAddress(name))
          {
            return symbol;
          }
        }
        return nullptr;
      };
      this->RenderWindow->SetOpenGLSymbolLoader(loadFunc, this->context());
#endif
      this->RenderWindow->vtkOpenGLRenderWindow::OpenGLInit();
    }
    auto ostate = this->RenderWindow->GetState();
    ostate->Reset();
    // By default, Qt sets the depth function to GL_LESS but VTK expects GL_LEQUAL
    ostate->vtkglDepthFunc(GL_LEQUAL);

    this->RenderWindowAdapter.reset(
      new QVTKRenderWindowAdapter(this->context(), this->RenderWindow, this));
    this->RenderWindowAdapter->setDefaultCursor(this->defaultCursor());
    this->RenderWindowAdapter->setEnableTouchEventProcessing(this->EnableTouchEventProcessing);
    this->RenderWindowAdapter->setEnableHiDPI(this->EnableHiDPI);
    this->RenderWindowAdapter->setUnscaledDPI(this->UnscaledDPI);
    this->RenderWindowAdapter->setCustomDevicePixelRatio(this->CustomDevicePixelRatio);
  }
  this->connect(this->context(), SIGNAL(aboutToBeDestroyed()), SLOT(cleanupContext()),
    static_cast<Qt::ConnectionType>(Qt::UniqueConnection | Qt::DirectConnection));
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::updateSize()
{
  if (this->RenderWindowAdapter)
  {
    this->RenderWindowAdapter->resize(this->width(), this->height());
  }
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::resizeGL(int w, int h)
{
  vtkLogF(TRACE, "resizeGL(%d, %d)", w, h);
  this->Superclass::resizeGL(w, h);
  this->updateSize();
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::paintGL()
{
  vtkLogF(TRACE, "paintGL");
  this->Superclass::paintGL();
  if (this->RenderWindow)
  {
    auto ostate = this->RenderWindow->GetState();
    ostate->Reset();
    ostate->Push();
    // By default, Qt sets the depth function to GL_LESS but VTK expects GL_LEQUAL
    ostate->vtkglDepthFunc(GL_LEQUAL);
    Q_ASSERT(this->RenderWindowAdapter);
    this->RenderWindowAdapter->paint();

    // If render was triggered by above calls, that may change the current context
    // due to things like progress events triggering updates on other widgets
    // (e.g. progress bar). Hence we need to make sure to call makeCurrent()
    // before proceeding with blit-ing.
    this->makeCurrent();

    const QSize deviceSize = this->size() * this->devicePixelRatioF();
    const auto fmt = this->context()->format();
    if (fmt.stereo() && this->RenderWindow->GetStereoRender() &&
      this->RenderWindow->GetStereoType() == VTK_STEREO_CRYSTAL_EYES)
    {
      this->RenderWindowAdapter->blitLeftEye(
        this->defaultFramebufferObject(), GL_BACK_LEFT, QRect(QPoint(0, 0), deviceSize));
      this->RenderWindowAdapter->blitRightEye(
        this->defaultFramebufferObject(), GL_BACK_RIGHT, QRect(QPoint(0, 0), deviceSize));
    }
    // Prevent blitting with "ZSpace Inspire" mode since ZSpace API will take care of it
    else if (this->RenderWindow->GetStereoType() != VTK_STEREO_ZSPACE_INSPIRE)
    {
      this->RenderWindowAdapter->blit(
        this->defaultFramebufferObject(), GL_BACK_LEFT, QRect(QPoint(0, 0), deviceSize));
    }
    ostate->Pop();
  }
  else
  {
    // no render window set, just fill with white.
    QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
    f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    f->glClear(GL_COLOR_BUFFER_BIT);
  }
}

//------------------------------------------------------------------------------
void QVTKOpenGLWindow::cleanupContext()
{
  this->RenderWindowAdapter.reset(nullptr);
}

//------------------------------------------------------------------------------
bool QVTKOpenGLWindow::event(QEvent* evt)
{
  // Forward event to the Widget containing this window. This is required
  // due to QTBUG-61836 that prevents the use of the flag
  // Qt::TransparentForMouseInput. This flag should indicate that this window
  // should not catch any event and let them pass through to the widget.
  // The containing widget should then forward back only the required events for
  // this window (such as mouse events and resize events).
  // Until this misbehavior is fixed, we have to handle forwarding of events.
  Q_EMIT this->windowEvent(evt);

  if (this->RenderWindowAdapter)
  {
    this->RenderWindowAdapter->handleEvent(evt);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    const auto t = evt->type();
    if (t == QEvent::TouchBegin || t == QEvent::TouchUpdate || t == QEvent::TouchEnd)
    {
      return true;
    }
#endif
  }

  return this->Superclass::event(evt);
}
VTK_ABI_NAMESPACE_END
