#include <Magnum/GL/Framebuffer.h>
#include <Magnum/Platform/GLContext.h>
#include <QtCore/QRunnable>
#include <QtQuick/QQuickWindow>
#include <QOpenGLFramebufferObject>
#include "magnum_item.h"
#include "magnum_render.h"

using namespace std;
using namespace Magnum;
using namespace Math::Literals;

class MagnumFBORenderer : public QQuickFramebufferObject::Renderer {
public:
  MagnumFBORenderer(QQuickWindow *window, MagnumRenderer *renderer) :
    m_window(window),
    m_renderer(renderer) {}

  void render() override {
    m_renderer->render();
    m_window->resetOpenGLState();
    update();
  }

  void synchronize(QQuickFramebufferObject *item) override {
    auto mitem = static_cast<MagnumItem*>(item);
    m_renderer->t(mitem->t());
    m_renderer->hue(mitem->hue());
    m_renderer->count(mitem->count());
    m_renderer->lagging(mitem->lagging());
  }

  QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override {
    QOpenGLFramebufferObjectFormat format;
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    format.setSamples(4);
    m_fbo = new QOpenGLFramebufferObject(size, format);
    // ---
    if (m_ctx == nullptr)
      m_ctx = new Platform::GLContext();
    Platform::GLContext::makeCurrent(m_ctx);
    m_renderer->reset(
          m_ctx,
          GL::Framebuffer::wrap(
            m_fbo->handle(), {{}, {size.width(),
                                   size.height()}}),
          Vector2i{m_window->width(), m_window->height()},
          Vector2i{size.width(), size.height()});
    return m_fbo;
  }

private:
  QQuickWindow *m_window;
  QOpenGLFramebufferObject *m_fbo;
  Platform::GLContext *m_ctx{};
  MagnumRenderer *m_renderer;
};

MagnumItem::MagnumItem() : m_renderer(new MagnumRenderer()) {
  setMirrorVertically(true);
  setAcceptHoverEvents(true);
  setAcceptedMouseButtons(Qt::AllButtons);
  setFlag(ItemAcceptsInputMethod, true);
}

QQuickFramebufferObject::Renderer *MagnumItem::createRenderer() const {
  return new MagnumFBORenderer(window(), m_renderer);
}

void MagnumItem::setT(qreal t) {
  if (t == m_t) return;
  m_t = t;
  emit tChanged();
  update();
}

void MagnumItem::setHue(qreal hue) {
  if (hue == m_hue) return;
  m_hue = hue;
  emit hueChanged();
  update();
}

void MagnumItem::setCount(qint8 count) {
  if (count == m_count) return;
  m_count = count;
  emit countChanged();
  update();
}

void MagnumItem::setLagging(qreal lagging) {
  if (lagging == m_lagging) return;
  m_lagging = lagging;
  emit laggingChanged();
  update();
}

void MagnumItem::mousePressEvent(QMouseEvent *evt) {
  if (auto& camera = m_renderer->camera()) {
    camera->initTransformation({evt->pos().x(), evt->pos().y()});
  }
}

void MagnumItem::mouseMoveEvent(QMouseEvent *evt) {
  if (!evt->buttons())
    return;
  if (auto& camera = m_renderer->camera()) {
    if (evt->modifiers() & Qt::ShiftModifier) {
      camera->translate({evt->pos().x(), evt->pos().y()});
    } else {
      camera->rotate({evt->pos().x(), evt->pos().y()});
    }
  }
}

void MagnumItem::wheelEvent(QWheelEvent *evt) {
  if (auto& camera = m_renderer->camera()) {
    const Float delta = evt->angleDelta().y();
    if (Math::abs(delta) < 1.0e-2f)
      return;
    camera->zoom(delta*.01f);
  }
}

class CleanupJob : public QRunnable {
public:
  CleanupJob(MagnumRenderer *renderer) : m_renderer(renderer) {}
  void run() override { delete m_renderer; }
private:
  MagnumRenderer *m_renderer;
};

void MagnumItem::releaseResources() {
  window()->scheduleRenderJob(
        new CleanupJob(m_renderer),
        QQuickWindow::BeforeSynchronizingStage);
  m_renderer = nullptr;
}
