/*
    SPDX-FileCopyrightText: 2022 Waqar Ahmed <waqar.17a@gmail.com>
    SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "difflinenumarea.h"
#include "diffeditor.h"

#include <QApplication>
#include <QPainter>

#include <KTextEditor/Editor>

static constexpr int Margin = 4;

struct LineNumColors {
    const QPen &otherLine;
    //     const QPen &currentLine;
    const QPen &added;
    const QPen &removed;
};

LineNumArea::LineNumArea(DiffEditor *parent)
    : QWidget(parent)
    , textEdit(parent)
{
    setMouseTracking(true);
    setFont(textEdit->font());
    auto updateColors = [this](KTextEditor::Editor *e) {
        if (!e) {
            return;
        }
        auto theme = e->theme();
        //         m_currentLineBgColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::CurrentLine));
        //         m_currentLineColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::CurrentLineNumber));
        m_otherLinesColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::LineNumbers));
        m_borderColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::Separator));
        auto bg = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::IconBorder));
        auto pal = palette();
        pal.setColor(QPalette::Window, bg);
        setPalette(pal);
    };
    connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors);
    updateColors(KTextEditor::Editor::instance());
}

int LineNumArea::lineNumAreaWidth() const
{
    int digits = 1;
    int max = std::max(1, maxLineNum);
    while (max >= 10) {
        max /= 10;
        ++digits;
    }
    return 13 + (textEdit->fontMetrics().horizontalAdvance(u'9') * digits);
}

void LineNumArea::setLineNumData(std::vector<int> leftLineNos, std::vector<int> rightLineNos)
{
    m_lineToNumA = std::move(leftLineNos);
    m_lineToNumB = std::move(rightLineNos);
}

QSize LineNumArea::sizeHint() const
{
    int lineNosWidth = lineNumAreaWidth();
    if (!m_lineToNumB.empty()) {
        lineNosWidth *= 2;
    }
    return {lineNosWidth + textEdit->fontMetrics().height(), 0};
}

static void paintTriangle(QPainter &painter, QColor c, QRect r, bool open)
{
    painter.setRenderHint(QPainter::Antialiasing);

    qreal size = qMin(r.width(), r.height());

    QPen pen;
    pen.setJoinStyle(Qt::RoundJoin);
    pen.setColor(c);
    pen.setWidthF(1.5);
    painter.setPen(pen);
    painter.setBrush(c);

    // let some border, if possible
    size *= 0.6;

    qreal halfSize = size / 2;
    qreal halfSizeP = halfSize * 0.6;
    QPointF middle(r.x() + ((qreal)r.width() / 2), r.y() + ((qreal)r.height() / 2));

    if (open) {
        QPointF points[3] = {middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP)};
        painter.drawConvexPolygon(points, 3);
    } else {
        QPointF points[3] = {middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0)};
        painter.drawConvexPolygon(points, 3);
    }

    painter.setRenderHint(QPainter::Antialiasing, false);
}

void LineNumArea::paintEvent(QPaintEvent *event)
{
    if (m_lineToNumA.empty()) {
        return;
    }
    QPainter painter(this);

    painter.fillRect(event->rect(), palette().color(QPalette::Active, QPalette::Window));

    QTextBlock block = textEdit->firstVisibleBlock();
    int blockNumber = block.blockNumber();
    qreal top = textEdit->blockBoundingGeometry(block).translated(textEdit->contentOffset()).top();
    // Maybe the top is not 0?
    top += textEdit->viewportMargins().top();
    qreal bottom = top;

    //     const QPen currentLine = m_currentLineColor;
    const QPen otherLines = m_otherLinesColor;

    QColor c = textEdit->addedColor();
    c.setAlphaF(0.7f);
    const QPen added = c;
    c = textEdit->removedColor();
    c.setAlphaF(0.7f);
    const QPen removed = c;

    LineNumColors colors{.otherLine = otherLines /*, currentLine*/, .added = added, .removed = removed};

    painter.setFont(font());
    const int w = lineNumAreaWidth();

    while (block.isValid() && top <= event->rect().bottom()) {
        top = bottom;
        bottom = top + textEdit->blockBoundingRect(block).height();
        if (block.isVisible() && bottom >= event->rect().top()) {
            // Current line background
            //             const auto isCurrentLine = textEdit->textCursor().blockNumber() == blockNumber;
            //             if (isCurrentLine) {
            //                 painter.fillRect(0, top, rect().width(), textEdit->fontMetrics().height(), m_currentLineBgColor);
            //             }

            // Line number left
            if (size_t(blockNumber) < m_lineToNumA.size()) {
                int n = m_lineToNumA[blockNumber];
                QRect numRect(0, top, w, textEdit->fontMetrics().height());
                drawLineNumber(painter, numRect, blockNumber, n, colors);
            }

            if (!m_lineToNumB.empty()) {
                // we are in unified mode, draw the right line number
                if (size_t(blockNumber) < m_lineToNumB.size()) {
                    int n = m_lineToNumB[blockNumber];
                    QRect numRect(w, top, w, textEdit->fontMetrics().height());
                    drawLineNumber(painter, numRect, blockNumber, n, colors);
                }
            }

            // Hunk fold marker
            if (textEdit->isHunkLine(blockNumber)) {
                QRect r = foldTriangleRect(block);
                QColor c = (m_hoverData.mouseOverTriangle && m_hoverData.mouseOverBlockNumber == blockNumber) ? m_otherLinesColor.lighter() : m_otherLinesColor;
                paintTriangle(painter, c, r, !textEdit->isHunkFolded(blockNumber));
            }
        }

        block = block.next();
        ++blockNumber;
    }

    // draw the line num area border
    if (m_lineToNumB.empty()) {
        // side by side
        painter.setPen(m_borderColor);
        painter.drawLine(rect().topRight() - QPoint(1, 0), rect().bottomRight() - QPoint(1, 0));
    } else {
        // unified
        painter.setPen(m_borderColor);
        painter.drawLine(QPoint(w, 0) - QPoint(1, 0), QPoint(w, rect().bottom()) - QPoint(1, 0));
        painter.setPen(m_borderColor);
        painter.drawLine(rect().topRight() - QPoint(1, 0), rect().bottomRight() - QPoint(1, 0));
    }
}

void LineNumArea::drawLineNumber(QPainter &painter, QRect rect, int blockNumber, int num, const LineNumColors &c)
{
    if (num < 0) {
        return;
    }

    const QString number = QString::number(num);
    QPen p = c.otherLine;
    const LineHighlight *hl = textEdit->highlightingForLine(blockNumber);
    if (hl) {
        p = hl->added ? c.added : c.removed;
    }

    painter.setPen(p);
    rect.adjust(0, 0, -(Margin * 2), 0);
    painter.drawText(rect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, number);
}

void LineNumArea::mousePressEvent(QMouseEvent *e)
{
    auto pos = e->pos();
    auto r = rect();
    r.setLeft(lineNumAreaWidth());
    if (!r.contains(pos)) {
        QWidget::mousePressEvent(e);
        return;
    }

    int block = textEdit->cursorForPosition(pos).block().blockNumber();
    if (textEdit->isHunkLine(block)) {
        textEdit->toggleFoldHunk(block);
    }
}

QRect LineNumArea::foldTriangleRect(const QTextBlock &block) const
{
    const int x = rect().width() - (textEdit->fontMetrics().height() + Margin);
    int y = (int)textEdit->blockBoundingGeometry(block).translated(textEdit->contentOffset()).y();
    const int width = textEdit->fontMetrics().height();
    const int height = width;
    return QRect(x, y, width, height);
}

void LineNumArea::mouseMoveEvent(QMouseEvent *e)
{
    const auto oldHoverData = m_hoverData;
    m_hoverData.reset();

    const auto block = textEdit->cursorForPosition(e->pos()).block();
    if (block.isValid()) {
        m_hoverData.mouseOverBlockNumber = block.blockNumber();
        m_hoverData.mouseOverTriangle = textEdit->isHunkLine(block.blockNumber()) && foldTriangleRect(block).contains(e->pos());
    }

    if (oldHoverData != m_hoverData) {
        update();
    }

    QWidget::mouseMoveEvent(e);
}

void LineNumArea::wheelEvent(QWheelEvent *e)
{
    qApp->sendEvent(textEdit->viewport(), e);
}
