/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvQFeatureTextItem.h"

#include <QApplication>
#include <QCursor>
#include <QDebug>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>
#include <QImage>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QTextBlock>
#include <QTextEdit>
#include <QTransform>
#include <QStyle>
#include <QVBoxLayout>

#include "MvQJson.h"
#include "MvQPlotView.h"
#include "MvQFeatureFactory.h"
#include "MvQFeatureCommand.h"
#include "MvQMethods.h"
#include "MvQPalette.h"
#include "MvQPanel.h"
#include "MgQLayoutItem.h"
#include "ObjectList.h"
#include "WsDecoder.h"

#include <assert.h>

//#define MVQFEATURETEXT_DEBUG_

//================================================================
//
// MvQFeatureTextEditor
//
//================================================================

MvQFeatureTextEditor::MvQFeatureTextEditor(MvQFeatureTextItemPtr item, QGraphicsItem* parent) :
    QGraphicsProxyWidget(parent),
    item_(item)
{
    Q_ASSERT(item_);

    holderW_ = new MvQPanel();
    auto vb  = new QVBoxLayout(holderW_);
    vb->setContentsMargins(5, 5, 5, 5);

    QString sh = "QWidget {background-color: transparent;}";
    holderW_->setStyleSheet(sh);

    editor_ = new QTextEdit();
    editor_->setWordWrapMode(QTextOption::WordWrap);
    editor_->document()->setDocumentMargin(item_->margin());
    editor_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    editor_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    vb->addWidget(editor_);

    updateFromItem();

    setWidget(holderW_);

    // must be on top of everything
    setZValue(100);

    QTransform tr;
    tr.scale(1, -1);
    setTransform(tr);

    //    setAcceptHoverEvents(true);

    connect(editor_, SIGNAL(textChanged()),
            this, SLOT(textChanged()));

    item_->textEditStarted(this);

    //    holderW_->setCursor(QCursor(Qt::SizeAllCursor));
}

void MvQFeatureTextEditor::paint(QPainter* painter, const QStyleOptionGraphicsItem* o, QWidget* w)
{
    QGraphicsProxyWidget::paint(painter, o, w);

    // paint frame around editor
    auto r = boundingRect();

    painter->save();

    // fill frame
    auto fr = r.adjusted(0, 0, 0, -(r.height() - frame_));
    painter->fillRect(fr, frameBrush_);

    fr = r.adjusted(0, 0, -(r.width() - frame_), 0);
    painter->fillRect(fr, frameBrush_);

    fr = r.adjusted(0, r.height() - frame_, 0, 0);
    painter->fillRect(fr, frameBrush_);

    fr = r.adjusted(r.width() - frame_, 0, 0, 0);
    painter->fillRect(fr, frameBrush_);

    // borders
    painter->setPen(outerPen_);
    painter->drawRect(r);
    painter->setPen(innerPen_);
    painter->drawRect(r.adjusted(frame_, frame_, -frame_, -frame_));

    // corners
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setPen(cornerPen_);
    MvQ::safeDrawLine(r.topLeft(), r.topLeft() + QPointF(frame_, frame_), painter);
    MvQ::safeDrawLine(r.topRight(), r.topRight() + QPointF(-frame_, frame_), painter);
    MvQ::safeDrawLine(r.bottomRight(), r.bottomRight() + QPointF(-frame_, -frame_), painter);
    MvQ::safeDrawLine(r.bottomLeft(), r.bottomLeft() + QPointF(frame_, -frame_), painter);
    painter->setRenderHint(QPainter::Antialiasing, false);

    // border inner shadow
    painter->setPen(shadowPen_);
    MvQ::safeDrawLine(QPointF(r.x() + frame_, r.y() + frame_),
                      QPointF(r.x() + frame_, r.y() + r.height() - frame_), painter);
    MvQ::safeDrawLine(QPointF(r.x() + frame_, r.y() + frame_),
                      QPointF(r.x() + r.width() - frame_, r.y() + frame_), painter);

    // border outer shadow
    painter->setPen(shadowPen_);
    MvQ::safeDrawLine(QPointF(r.x() + r.width() - 1, r.y()),
                      QPointF(r.x() + r.width() - 1, r.y() + r.height()), painter);
    MvQ::safeDrawLine(QPointF(r.x(), r.y() + r.height() - 1),
                      QPointF(r.x() + r.width(), r.y() + r.height() - 1), painter);

    painter->restore();
}

QTextEdit* MvQFeatureTextEditor::editor() const
{
    return editor_;
}

MvQFeatureTextItemPtr MvQFeatureTextEditor::item() const
{
    return item_;
}

void MvQFeatureTextEditor::initPos()
{
    if (item_) {
        QPointF scPos = item_->parentItem()->mapToScene(item_->textPos());
        setPos(scPos + QPointF(-frame_, frame_));
    }
}

// Text edited in editor
void MvQFeatureTextEditor::textChanged()
{
    QString text = editor_->toPlainText();
    if (!doNotNotifyItem_) {
        item_->setTextFromEditor(text);
    }
    QSize eSize = item_->itemRect().size().toSize();
    auto wSize  = eSize + QSize(2 * frame_, 2 * frame_);

    holderW_->resize(wSize);
    holderW_->setMaximumWidth(wSize.width() + 1);
    holderW_->setMaximumHeight(wSize.height() + 1);

    editor_->resize(eSize);
    editor_->setMaximumWidth(eSize.width() + 1);
    editor_->setMaximumHeight(eSize.height() + 1);
}

void MvQFeatureTextEditor::finish()
{
    //item_->setText( editor_->toPlainText());
    item_->textEditFinished();
}

void MvQFeatureTextEditor::updateFromItem()
{
    QString colStr = "transparent";
    if (item_->boxBrush().style() != Qt::NoBrush) {
        auto bgCol = item_->boxBrush().color();
        if (bgCol.alpha() != 255) {
            colStr = "rgba(" + QString::number(bgCol.red()) + "," +
                     QString::number(bgCol.green()) + "," +
                     QString::number(bgCol.blue()) + "," +
                     QString::number(bgCol.alpha()) + ")";
        }
        else {
            colStr = bgCol.name();
        }
    }

    QString sh = "QTextEdit {color: " + item_->fontPen().color().name() +
                 "; background-color: " + colStr +
                 "; border: none;}";

    editor_->setStyleSheet(sh);
    editor_->setFont(item_->font());
    editor_->setPlainText(item_->text());
    adjustAlignment();

    QSize eSize = item_->itemRect().size().toSize();
    auto wSize  = eSize + QSize(2 * frame_, 2 * frame_);
    holderW_->setMaximumWidth(wSize.width() + 1);
    holderW_->setMaximumHeight(wSize.height() + 1);
    editor_->setMaximumWidth(eSize.width() + 1);
    editor_->setMaximumHeight(eSize.height() + 1);
    doNotNotifyItem_ = false;
}

void MvQFeatureTextEditor::adjustAlignment()
{
    auto tc    = editor_->textCursor();
    auto oriTc = tc;
    tc.movePosition(QTextCursor::Start);
    editor_->setTextCursor(tc);
    editor_->setAlignment(item_->alignment());
    //qDebug() << "block=" << tc.blockNumber() << tc.block().text();
    while (tc.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor)) {
        editor_->setTextCursor(tc);
        editor_->setAlignment(item_->alignment());
        //qDebug() << "block=" << tc.blockNumber() << tc.block().text();
    }
    editor_->setTextCursor(oriTc);
}

void MvQFeatureTextEditor::itemChanged()
{
    doNotNotifyItem_ = true;
    updateFromItem();
    doNotNotifyItem_ = false;
}

void MvQFeatureTextEditor::itemTextChanged()
{
    if (item_->text() != editor_->toPlainText()) {
        doNotNotifyItem_ = true;
        updateFromItem();
        doNotNotifyItem_ = false;
    }
}

void MvQFeatureTextEditor::adjustCursorToMouseLeave(QMouseEvent* viewEvent)
{
    // check if the mouse left the widget and we need to reset the cursor
    if (dragCursor_ && !containsScenePos(item_->plotView()->mapToScene(viewEvent->pos()))) {
        dragCursor_ = false;
        qDebug() << "ADJUST CURSOR";
        MvQFeatureHandler::Instance()->resetCursor();
    }
}

bool MvQFeatureTextEditor::containsScenePos(const QPointF& scenePos) const
{
    return contains(mapFromScene(scenePos));
}

bool MvQFeatureTextEditor::canBeDraggedAt(const QPointF& scPos) const
{
    if (!item_->plotView()->inFeatureAddMode()) {
        auto p = mapFromScene(scPos);
        return !boundingRect().adjusted(frame_ + 1, frame_ + 1, -frame_ - 1, -frame_ - 1).contains(p);
    }
    return false;
}

void MvQFeatureTextEditor::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    // When we click on the frame the texteditor will be closed and
    // deleted and we start dragging the text item
    if (MvQFeatureHandler::Instance()->canAcceptMouseEvent()) {
        if (canBeDraggedAt(event->scenePos())) {
            MvQFeatureHandler::Instance()->dragTextEditor(this);
            return;
        }
    }
    QGraphicsProxyWidget::mouseMoveEvent(event);
}

// NOTE: hover events did not work on MacOS so we implemented the
// cursor handling by using mouse events.
void MvQFeatureTextEditor::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    // On MacOS mouseMoveEvent is called even if the cursor is outside this
    // widget! So we need to be sure it is inside the widget to handle the cursor change
    if (MvQFeatureHandler::Instance()->canAcceptMouseEvent() && containsScenePos(event->scenePos())) {
        if (canBeDraggedAt(event->scenePos())) {
            if (!dragCursor_) {
                dragCursor_ = true;
                MvQFeatureHandler::Instance()->setDragCursor();
            }
        }
        else if (dragCursor_) {
            dragCursor_ = false;
            MvQFeatureHandler::Instance()->resetCursor();
        }
    }
    QGraphicsProxyWidget::mouseMoveEvent(event);
}

//================================================================
//
// MvQFeatureTextItem
//
//================================================================

void MvQFeatureTextItem::init(const QJsonObject& obj)
{
    QJsonObject g = obj["geometry"].toObject();
    auto gcLst    = MvQJson::toPointFList(g["coordinates"].toArray(), g["type"].toString());
    if (!gcLst.isEmpty()) {
        geoCoord_         = gcLst[0];
        hasValidGeoCoord_ = isValidPoint(geoCoord_);
    }
    else {
        hasValidGeoCoord_ = false;
    }

    double w, h;
    WsDecoder::styleValue(obj, "WIDTH", w);
    WsDecoder::styleValue(obj, "HEIGHT", h);
    if (w > 0 && w < 10000 && h > 0 && h < 10000) {
        textRect_ =  QRectF(0, 0, w, h);
    }

    MvQFeatureItem::init(obj);
}

void MvQFeatureTextItem::toJson(QJsonObject& obj)
{
    MvQFeatureItem::toJson(obj);
    WsDecoder::setStyleValue(obj, "WIDTH", textRect_.width());
    WsDecoder::setStyleValue(obj, "HEIGHT", textRect_.height());
}

void MvQFeatureTextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*o*/, QWidget* /*w*/)
{
    if (!inTextEdit_) {
        // we draw a rect as a path because line and rect rendering
        // has a problem in Qt 6.2 on MacOS
        QPainterPath p;
        p.addRect(itemRect_);
        painter->setPen(pen_);
        painter->setBrush(brush_);
        painter->drawPath(p);

        painter->setBrush(Qt::NoBrush);
        painter->setPen(fontPen_);
        painter->setFont(font_);
        painter->drawText(textRect_, alignment_ | Qt::AlignTop | Qt::TextWordWrap, text_);
    }
}

// rect is in parent coordinates
void MvQFeatureTextItem::resize(QRectF targetRectInParent)
{
#ifdef MVQFEATURETEXT_DEBUG_
    qDebug() << "MvQFeatureTextItem::resize";
    qDebug() << "pos:" << pos() << "targetRectInParent:" << targetRectInParent;
#endif
    QRectF targetRect = mapFromParent(targetRectInParent).boundingRect();

    // reduce the targetRect with the halo. We want to fit the itemRect into it!!!
    targetRect.adjust(halo(), halo(), -halo(), -halo());
    targetRect.adjust(margin_, margin_, -margin_, -margin_);

    prepareGeometryChange();

    // textRect_ is the at (0,0) !!!
    auto w    = targetRect.width();
    auto h    = targetRect.height();
    textRect_ = QRectF(0, 0, w, h);
    itemRect_ = textRect_.adjusted(-margin_, -margin_, margin_, margin_);
    bRect_    = itemRect_;
    setPos(targetRectInParent.bottomLeft() + QPointF(halo() + margin_, -halo() - margin_));
    adjustBRect();

#ifdef MVQFEATURETEXT_DEBUG_
    qDebug() << " -> itemRect:" << itemRect_ << "bRect:" << bRect_ << "textRect:" << textRect_ << "targetRect:" << targetRect;
    qDebug() << "    pos:" << pos() << "targetRectInParent:" << targetRectInParent;
#endif
    adjustGeoCoord();
}

void MvQFeatureTextItem::resizeTo(const MvQFeatureGeometry& geom)
{
    prepareGeometryChange();
    setPos(geom.pos_);
    itemRect_ = geom.itemRect_;
    textRect_ = itemRect_.adjusted(margin_, margin_, -margin_, -margin_);
    bRect_    = itemRect_;
    adjustBRect();
    adjustGeoCoord();
}

void MvQFeatureTextItem::resizeFinished()
{
    //auto tr = estimateTextRect();
}

QPointF MvQFeatureTextItem::textPos() const
{
    return mapToParent(itemRect_.topLeft());
}

void MvQFeatureTextItem::adjustBRect()
{
    if (fabs(halo() > 1E-5)) {
        prepareGeometryChange();
        bRect_ = itemRect_.adjusted(-halo(), -halo(), halo(), halo());
    }
}

void MvQFeatureTextItem::setStyle(const MvRequest& req, bool init)
{
    if (req) {
        req_ = req;
    }

    if (!getRequestParameters()) {
        return;
    }
    prepareGeometryChange();
    adjustGeoLocked();

    if (init && textRect_.isValid()) {
        itemRect_ = textRect_.adjusted(-margin_, -margin_, margin_, margin_);
        bRect_    = itemRect_;
    }
    else {
        adjustText();
    }
    adjustBRect();

    if (editor_) {
        editor_->itemChanged();
    }
}

bool MvQFeatureTextItem::getRequestParameters()
{
    if (!req_)
        return false;

    expandStyleRequest();

    std::string val;
    fontPen_ = MvQ::makePen("SOLID", 0, (const char*)req_("FONT_COLOUR"));

    req_.getValue("LINE", val);
    if (val == "OFF") {
        pen_ = QPen(Qt::NoPen);
    }
    else {
        pen_ = MvQ::makePen((const char*)req_("LINE_STYLE"),
                            (int)req_("LINE_THICKNESS"),
                            (const char*)req_("LINE_COLOUR"),
                            Qt::SquareCap, Qt::MiterJoin);
    }

    req_.getValue("FILL", val);
    if (val == "OFF") {
        brush_ = QBrush(Qt::NoBrush);
    }
    else {
        brush_ = MvQ::makeBrush("SOLID",
                                (const char*)req_("FILL_COLOUR"));
    }

    // text + font
    req_.getValue("TEXT", val);
    text_ = QString::fromStdString(val);

    std::string family;
    int fontSize = (int)req_("FONT_SIZE");
    req_.getValue("FONT_FAMILY", family);

    req_.getValue("FONT_BOLD_STYLE", val);
    bool bold = (val == "ON");

    req_.getValue("FONT_ITALIC_STYLE", val);
    bool italic = (val == "ON");

    req_.getValue("FONT_UNDERLINE_STYLE", val);
    bool underline = (val == "ON");

    font_    = MvQ::makeFont(family, bold, italic, underline, fontSize);
    fontPen_ = MvQ::makePen("SOLID", 1, (const char*)req_("FONT_COLOUR"));

    req_.getValue("TEXT_ALIGNMENT", val);
    if (val == "LEFT")
        alignment_ = Qt::AlignLeft;
    else if (val == "RIGHT")
        alignment_ = Qt::AlignRight;
    else if (val == "CENTRE")
        alignment_ = Qt::AlignHCenter;
    else if (val == "JUSTIFY")
        alignment_ = Qt::AlignJustify;

    return true;
}

// only the handler can call it
void MvQFeatureTextItem::setText(QString t)
{
    text_        = t;
    req_("TEXT") = text_.toStdString().c_str();

    prepareGeometryChange();
    adjustText();
    adjustBRect();

    if (editor_) {
        editor_->itemTextChanged();
    }
}

void MvQFeatureTextItem::setTextFromEditor(QString t)
{
    if (t != text_) {
        MvQFeatureHandler::Instance()->setText(getptr(), t);
    }
}

void MvQFeatureTextItem::adjustText()
{
    QFontMetrics fm(font_);
    minTextSize_ = QSize(MvQ::textWidth(fm, "A"), fm.height());

    // a resonable default size at the very beginning
    if (text_.isEmpty() && !textRect_.isValid()) {
        textRect_ = QRectF(0, 0, MvQ::textWidth(fm, "AAAAAAAA"), fm.height());
        // otherwise we just adjust the text height
    }
    else {
        auto tr = estimateTextRect();
#ifdef MVQFEATURETEXT_DEBUG_
        qDebug() << "esitmate:" << tr.size() << "textRect:" << textRect_.size();
#endif
        // check min size
        if (tr.width() < minTextSize_.width()) {
            tr.setWidth(minTextSize_.width());
        }
        if (tr.height() < minTextSize_.height()) {
            tr.setHeight(minTextSize_.height());
        }

        //        if (tr.width() < textSizeHint.width() ) {
        //             tr.setWidth(textSizeHint.width());
        //        }
        //        if (tr.height() < textSizeHint.width() ) {
        //             tr.setWidth(textSizeHint.width());
        //        }

        //        textRect_.setWidth(tr.width());
        //        textRect_.setHeight(tr.height());

        //        // textRect_ can only grow by setting the text
        if (textRect_.width() < tr.width()) {
            textRect_.setWidth(tr.width());
        }
        if (textRect_.height() < tr.height()) {
            textRect_.setHeight(tr.height());
        }
    }

    // textRect_ is always the at (0,0) !!!
    itemRect_ = textRect_.adjusted(-margin_, -margin_, margin_, margin_);
    bRect_    = itemRect_;
}

QRectF MvQFeatureTextItem::estimateTextRect() const
{
    QFontMetrics fm(font_);
    QRect r(0, 0, textRect_.width() - 1, 1000);
    return fm.boundingRect(r, alignment_ | Qt::AlignTop | Qt::TextWordWrap,
                           (text_.isEmpty() ? "A" : text_));
}

void MvQFeatureTextItem::editText()
{
    MvQFeatureHandler::Instance()->editText(getptr());
}

void MvQFeatureTextItem::initContents()
{
    editText();
}

void MvQFeatureTextItem::textEditStarted(MvQFeatureTextEditor* editor)
{
    inTextEdit_ = true;
    editor_     = editor;
    update();
}

void MvQFeatureTextItem::textEditFinished()
{
    inTextEdit_ = false;
    editor_     = nullptr;
    update();
    setSelected(false);
}


double MvQFeatureTextItem::halo() const
{
    auto w = pen_.width();
    return (w <= 1) ? 0 : 1 + w / 2;
}

QSizeF MvQFeatureTextItem::minSize() const
{
    return minTextSize_ + QSizeF(2 * halo() + 2 * margin_, 2 * halo() + 2 * margin_);
}

static MvQFeatureMaker<MvQFeatureTextItem> textMaker("text");
