#include "Plot_p.h"

#include "../bridge/Qt_Event_Adapter.h"
#include "../bridge/Qt_Image_Adapter.h"
#include "../bridge/Qt_Presentation_Sink.h"

#include <QFont>
#include <QMouseEvent>
#include <QPainter>
#include <QResizeEvent>
#include <QTimer>
#include <QWheelEvent>

#include <algorithm>
#include <cmath>

namespace renderive {
namespace {

Color from_qcolor(const QColor& color) {
    return {
        static_cast<std::uint8_t>(color.red()),
        static_cast<std::uint8_t>(color.green()),
        static_cast<std::uint8_t>(color.blue()),
        static_cast<std::uint8_t>(color.alpha())
    };
}

QColor to_qcolor(Color color) {
    return QColor(color.r, color.g, color.b, color.a);
}

} // namespace

Abs_Plot_Private::Abs_Plot_Private(bool continuous_rendering)
    : scene(std::make_unique<Scene2D>()), continuous(continuous_rendering) {}

Abs_Plot_Private::~Abs_Plot_Private() = default;

void Abs_Plot_Private::attach_widget(Abs_Plot* plot) {
    presentation_sink = std::make_shared<Qt_Presentation_Sink>(plot);
    scene->set_presentation_sink(presentation_sink);
    timer = new QTimer(plot);
    timer->setTimerType(Qt::PreciseTimer);
    QObject::connect(timer, &QTimer::timeout, plot, [this] {
        if (scene->view_active())
            (void)scene->render_frame();
    });
}

Abs_Plot::Abs_Plot(Abs_Plot_Private* private_data)
    : d(private_data) {
    d->attach_widget(this);
    setAttribute(Qt::WA_OpaquePaintEvent, true);
    setMouseTracking(true);
    setFocusPolicy(Qt::NoFocus);
    update_render_interval();
}

Abs_Plot::~Abs_Plot() {
    if (!d)
        return;
    if (d->timer)
        d->timer->stop();
    d->scene->deactivate_view();
    delete d;
    d = nullptr;
}

void Abs_Plot::init() {
    d->scene->init();
}

Scene2D* Abs_Plot::scene() const {
    return d ? d->scene.get() : nullptr;
}

renderive_Owner<Renderable> Abs_Plot::root_renderable() const {
    return d->scene->root_renderable();
}

QColor Abs_Plot::background_color() const {
    return to_qcolor(d->scene->background_color());
}

void Abs_Plot::set_background_color(const QColor& color) {
    d->scene->set_background_color(from_qcolor(color));
}

void Abs_Plot::start_render_timer() {
    d->scene->activate_view();
    update_render_interval();
    if (d->timer && !d->timer->isActive())
        d->timer->start();
}

void Abs_Plot::stop_render_timer() {
    if (d->timer)
        d->timer->stop();
    d->scene->deactivate_view();
}

void Abs_Plot::update_render_interval() {
    if (!d->timer)
        return;
    const double fps = std::max(1.0, d->scene->max_render_fps());
    d->timer->setInterval(std::max(1, static_cast<int>(std::lround(1000.0 / fps))));
}

void Abs_Plot::paintEvent(QPaintEvent*) {
    d->paint_event(this);
}

void Abs_Plot_Private::paint_event(Abs_Plot* plot) {
    QPainter painter(plot);
    painter.fillRect(plot->rect(), plot->background_color());
    scene->with_frame([&painter](Image_View view) {
        if (!view.empty())
            painter.drawImage(QPoint(0, 0), Qt_Image_Adapter::to_qimage(view));
    });
    draw_performance_overlay(plot, painter);
    painter.end();
}

void Abs_Plot_Private::draw_performance_overlay(Abs_Plot* plot, QPainter& painter) {
    const auto overlay = scene->performance_overlay();
    if (!overlay || !overlay->enabled())
        return;
    const auto snapshot = overlay->display_snapshot();
    if (!snapshot || snapshot->viewport_size != scene->viewport_size())
        return;
    const auto& options = snapshot->style;
    QFont font = painter.font();
    font.setPointSizeF(std::max(1.0, options.font.size));
    font.setWeight(std::clamp(options.font.weight / 10, 0, 99));
    font.setItalic(options.font.italic);
    painter.setFont(font);
    const QFontMetrics metrics(font);
    const int line_height = std::max(1, metrics.height() + 2);
    const int left_margin = std::max(0, options.left_margin);
    const int right_margin = std::max(0, options.right_margin);
    const int top_margin = std::max(0, options.top_margin);
    const int bottom_margin = std::max(0, options.bottom_margin);
    int text_width{};
    for (const auto& line : snapshot->lines)
        text_width = std::max(text_width, metrics.horizontalAdvance(QString::fromStdString(line)));
    const int panel_width = std::min(plot->width(), left_margin + text_width + right_margin);
    const int panel_height = std::min(
        plot->height(), top_margin + bottom_margin +
                            line_height * static_cast<int>(snapshot->lines.size()));
    painter.fillRect(QRect(0, 0, panel_width, panel_height), to_qcolor(options.background));
    int y = top_margin + metrics.ascent();
    for (std::size_t index = 0; index < snapshot->lines.size(); ++index) {
        const QString line = QString::fromStdString(snapshot->lines[index]);
        if (index == 0) {
            painter.setPen(to_qcolor(options.section_color));
            painter.drawText(left_margin, y, line);
        } else if (const qsizetype separator = line.indexOf(':'); separator >= 0) {
            const QString label = line.left(separator + 1);
            painter.setPen(to_qcolor(options.label_color));
            painter.drawText(left_margin, y, label);
            painter.setPen(to_qcolor(options.value_color));
            painter.drawText(left_margin + metrics.horizontalAdvance(label), y,
                             line.mid(separator + 1));
        } else {
            painter.setPen(to_qcolor(options.label_color));
            painter.drawText(left_margin, y, line);
        }
        y += line_height;
    }
}

bool Abs_Plot::event(QEvent* event) {
    const auto dispatch = [this, event](const Event& translated) {
        d->scene->dispatch_event(translated);
        if (!translated.is_accepted())
            return false;
        event->accept();
        return true;
    };
    switch (event->type()) {
    case QEvent::Resize: {
        auto* resize = static_cast<QResizeEvent*>(event);
        d->scene->set_viewport_size(Qt_Event_Adapter::to_size(resize->size()));
        d->scene->dispatch_event(Qt_Event_Adapter::to_resize_event(resize));
        break;
    }
    case QEvent::Show:
        d->scene->dispatch_event(Event(Event_Type::Show));
        if (d->continuous)
            start_render_timer();
        break;
    case QEvent::Hide:
        d->scene->dispatch_event(Event(Event_Type::Hide));
        if (d->continuous)
            stop_render_timer();
        break;
    case QEvent::Leave: {
        const Event translated(Event_Type::Leave);
        if (dispatch(translated))
            return true;
        break;
    }
    case QEvent::Wheel: {
        const auto translated = Qt_Event_Adapter::to_wheel_event(
            static_cast<QWheelEvent*>(event));
        if (dispatch(translated))
            return true;
        break;
    }
    case QEvent::MouseMove: {
        const auto translated = Qt_Event_Adapter::to_pointer_event(
            static_cast<QMouseEvent*>(event), Event_Type::Pointer_Move);
        if (dispatch(translated))
            return true;
        break;
    }
    case QEvent::MouseButtonPress: {
        const auto translated = Qt_Event_Adapter::to_pointer_event(
            static_cast<QMouseEvent*>(event), Event_Type::Pointer_Press);
        if (dispatch(translated))
            return true;
        break;
    }
    case QEvent::MouseButtonRelease: {
        const auto translated = Qt_Event_Adapter::to_pointer_event(
            static_cast<QMouseEvent*>(event), Event_Type::Pointer_Release);
        if (dispatch(translated))
            return true;
        break;
    }
    case QEvent::KeyPress: {
        const auto translated = Qt_Event_Adapter::to_key_event(
            static_cast<QKeyEvent*>(event), Event_Type::Key_Press);
        if (dispatch(translated))
            return true;
        break;
    }
    case QEvent::KeyRelease: {
        const auto translated = Qt_Event_Adapter::to_key_event(
            static_cast<QKeyEvent*>(event), Event_Type::Key_Release);
        if (dispatch(translated))
            return true;
        break;
    }
    default:
        break;
    }
    return QWidget::event(event);
}

} // namespace renderive
