// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2014 Calin Cruceru <crucerucalincristian@gmail.com>
//

// Self
#include "EditPolygonDialog.h"
#include "ui_EditPolygonDialog.h"

// Qt
#include <QColorDialog>
#include <QMessageBox>

// Marble
#include "FormattedTextWidget.h"
#include "GeoDataLineStyle.h"
#include "GeoDataLinearRing.h"
#include "GeoDataPlacemark.h"
#include "GeoDataPolyStyle.h"
#include "GeoDataPolygon.h"
#include "GeoDataStyle.h"
#include "NodeItemDelegate.h"
#include "NodeModel.h"
#include "StyleBuilder.h"
#include "osm/OsmPlacemarkData.h"
#include "osm/OsmRelationManagerWidget.h"
#include "osm/OsmTagEditorWidget.h"

namespace Marble
{

class Q_DECL_HIDDEN EditPolygonDialog::Private : public Ui::UiEditPolygonDialog
{
public:
    Private(GeoDataPlacemark *placemark);
    ~Private();

    GeoDataPlacemark *const m_placemark;

    QColorDialog *m_linesDialog = nullptr;
    QColorDialog *m_polyDialog = nullptr;

    QString m_initialDescription;
    QString m_initialName;
    GeoDataStyle m_initialStyle;
    GeoDataLinearRing m_initialOuterBoundary;
    OsmPlacemarkData m_initialOsmData;
    bool m_hadInitialOsmData;

    NodeModel *const m_nodeModel;
    NodeItemDelegate *m_delegate = nullptr;
    OsmTagEditorWidget *m_osmTagEditorWidget = nullptr;
    OsmRelationManagerWidget *m_osmRelationManagerWidget = nullptr;
};

EditPolygonDialog::Private::Private(GeoDataPlacemark *placemark)
    : Ui::UiEditPolygonDialog()
    , m_placemark(placemark)
    , m_linesDialog(nullptr)
    , m_polyDialog(nullptr)
    , m_nodeModel(new NodeModel)
    , m_osmTagEditorWidget(nullptr)
    , m_osmRelationManagerWidget(nullptr)
{
    // nothing to do
}

EditPolygonDialog::Private::~Private()
{
    delete m_linesDialog;
    delete m_polyDialog;
    delete m_nodeModel;
    delete m_delegate;
}

EditPolygonDialog::EditPolygonDialog(GeoDataPlacemark *placemark, const QHash<qint64, OsmPlacemarkData> *relations, QWidget *parent)
    : QDialog(parent)
    , d(new Private(placemark))
{
    d->setupUi(this);

    // There's no point showing Relations and Tags tabs if the editor was not
    // loaded from the annotate plugin ( loaded from tourWidget.. )
    if (relations) {
        // Adding the osm tag editor widget tab
        d->m_osmTagEditorWidget = new OsmTagEditorWidget(placemark, this);
        d->tabWidget->addTab(d->m_osmTagEditorWidget, tr("Tags"));
        connect(d->m_osmTagEditorWidget, SIGNAL(placemarkChanged(GeoDataFeature *)), this, SLOT(updatePolygon()));

        // Adding the osm relation editor widget tab
        d->m_osmRelationManagerWidget = new OsmRelationManagerWidget(placemark, relations, this);
        d->tabWidget->addTab(d->m_osmRelationManagerWidget, tr("Relations"));
        connect(d->m_osmRelationManagerWidget, SIGNAL(relationCreated(OsmPlacemarkData)), this, SIGNAL(relationCreated(OsmPlacemarkData)));

        adjustSize();
    }

    d->m_hadInitialOsmData = placemark->hasOsmData();
    if (d->m_hadInitialOsmData) {
        d->m_initialOsmData = placemark->osmData();
    }

    d->m_initialStyle = *placemark->style();

    // If the polygon has just been drawn, assign it a default name.
    if (d->m_placemark->name().isNull()) {
        d->m_placemark->setName(tr("Untitled Polygon"));
    }
    d->m_name->setText(placemark->name());
    d->m_initialName = placemark->name();
    connect(d->m_name, SIGNAL(editingFinished()), this, SLOT(updatePolygon()));

    d->m_formattedTextWidget->setText(placemark->description());
    d->m_initialDescription = placemark->description();

    // Get the current style properties.
    const GeoDataLineStyle lineStyle = placemark->style()->lineStyle();
    const GeoDataPolyStyle polyStyle = placemark->style()->polyStyle();

    d->m_linesWidth->setRange(0.1, 5.0);
    d->m_linesWidth->setValue(lineStyle.width());
    connect(d->m_linesWidth, SIGNAL(valueChanged(double)), this, SLOT(handleChangingStyle()));

    // Adjust the "Filled"/"Not Filled" option according to its current fill.
    if (polyStyle.fill()) {
        d->m_filledColor->setCurrentIndex(0);
    } else {
        d->m_filledColor->setCurrentIndex(1);
    }
    connect(d->m_filledColor, SIGNAL(currentIndexChanged(int)), this, SLOT(handleChangingStyle()));

    // Adjust the color buttons' icons to the current lines and polygon colors.
    QPixmap linesPixmap(d->m_linesColorButton->iconSize().width(), d->m_linesColorButton->iconSize().height());
    linesPixmap.fill(lineStyle.color());
    d->m_linesColorButton->setIcon(QIcon(linesPixmap));

    QPixmap polyPixmap(d->m_polyColorButton->iconSize().width(), d->m_polyColorButton->iconSize().height());
    polyPixmap.fill(polyStyle.color());
    d->m_polyColorButton->setIcon(QIcon(polyPixmap));

    // Setup the color dialogs.
    d->m_linesDialog = new QColorDialog(this);
    d->m_linesDialog->setOption(QColorDialog::ShowAlphaChannel);
    d->m_linesDialog->setCurrentColor(lineStyle.color());
    connect(d->m_linesColorButton, SIGNAL(clicked()), d->m_linesDialog, SLOT(exec()));
    connect(d->m_linesDialog, SIGNAL(colorSelected(QColor)), this, SLOT(updateLinesDialog(QColor)));
    connect(d->m_linesDialog, SIGNAL(colorSelected(QColor)), this, SLOT(handleChangingStyle()));

    d->m_polyDialog = new QColorDialog(this);
    d->m_polyDialog->setOption(QColorDialog::ShowAlphaChannel);
    d->m_polyDialog->setCurrentColor(polyStyle.color());
    connect(d->m_polyColorButton, SIGNAL(clicked()), d->m_polyDialog, SLOT(exec()));
    connect(d->m_polyDialog, SIGNAL(colorSelected(QColor)), this, SLOT(updatePolyDialog(QColor)));
    connect(d->m_polyDialog, SIGNAL(colorSelected(QColor)), this, SLOT(handleChangingStyle()));

    // Setting the NodeView's delegate: mainly used for the editing the polygon's nodes
    d->m_delegate = new NodeItemDelegate(d->m_placemark, d->m_nodeView);

    connect(d->m_delegate, SIGNAL(modelChanged(GeoDataPlacemark *)), this, SLOT(handleItemMoving(GeoDataPlacemark *)));
    connect(d->m_delegate, SIGNAL(geometryChanged()), this, SLOT(updatePolygon()));

    d->m_nodeView->setItemDelegate(d->m_delegate);
    d->m_nodeView->setEditTriggers(QAbstractItemView::AllEditTriggers);

    // Populating the model
    if (const GeoDataPolygon *polygon = geodata_cast<GeoDataPolygon>(placemark->geometry())) {
        GeoDataLinearRing outerBoundary = polygon->outerBoundary();
        for (int i = 0; i < outerBoundary.size(); ++i) {
            d->m_nodeModel->addNode(outerBoundary.at(i));
        }
        d->m_initialOuterBoundary = outerBoundary;
    }

    d->m_nodeView->setModel(d->m_nodeModel);

    // Resize column to contents size for better UI.
    d->m_nodeView->resizeColumnToContents(0);

    // Promote "Ok" button to default button.
    d->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);

    connect(d->buttonBox->button(QDialogButtonBox::Ok), SIGNAL(pressed()), this, SLOT(checkFields()));
    connect(this, &QDialog::accepted, this, &EditPolygonDialog::updatePolygon);
    connect(this, &QDialog::finished, this, &EditPolygonDialog::restoreInitial);

    // Ensure that the dialog gets deleted when closing it (either when clicking OK or
    // Close).
    connect(this, SIGNAL(finished(int)), SLOT(deleteLater()));
}

EditPolygonDialog::~EditPolygonDialog()
{
    delete d;
}

void EditPolygonDialog::handleAddingNode(const GeoDataCoordinates &node)
{
    d->m_nodeModel->addNode(node);
}

void EditPolygonDialog::handleItemMoving(GeoDataPlacemark *item)
{
    if (item == d->m_placemark) {
        d->m_nodeModel->clear();
        if (const auto polygon = geodata_cast<GeoDataPolygon>(d->m_placemark->geometry())) {
            GeoDataLinearRing outerBoundary = polygon->outerBoundary();
            for (int i = 0; i < outerBoundary.size(); ++i) {
                d->m_nodeModel->addNode(outerBoundary.at(i));
            }
        }
    }
}

void EditPolygonDialog::handleChangingStyle()
{
    // The default style of the polygon has been changed, thus the old style URL is no longer valid
    d->m_placemark->setStyleUrl(QString());

    GeoDataStyle::Ptr style(new GeoDataStyle(*d->m_placemark->style()));
    style->lineStyle().setWidth(d->m_linesWidth->value());
    // 0 corresponds to "Filled" and 1 corresponds to "Not Filled".
    style->polyStyle().setFill(!d->m_filledColor->currentIndex());
    style->setId(d->m_placemark->id() + QLatin1StringView("Style"));

    // Adjust the lines/polygon colors.
    // QColorDialog::currentColor() also works even if the color dialog
    // has not been exec'ed, while QColorDialog::selectedColor() does not.
    style->lineStyle().setColor(d->m_linesDialog->currentColor());
    style->polyStyle().setColor(d->m_polyDialog->currentColor());

    d->m_placemark->setStyle(style);

    updatePolygon();
}

void EditPolygonDialog::updatePolygon()
{
    d->m_placemark->setName(d->m_name->text());
    d->m_placemark->setDescription(d->m_formattedTextWidget->text());

    // If there is not custom style initialized( default #polyline url is used ) and there is a osmTag-based style
    // available, set it
    const OsmPlacemarkData osmData = d->m_osmTagEditorWidget->placemarkData();
    const GeoDataPlacemark::GeoDataVisualCategory category = StyleBuilder::determineVisualCategory(osmData);
    if (d->m_placemark->styleUrl() == QLatin1StringView("#polygon") && category != GeoDataPlacemark::None) {
        d->m_placemark->setStyle(GeoDataStyle::Ptr()); // first clear style so style gets set by setVisualCategory()
        d->m_placemark->setVisualCategory(category);
    }

    Q_EMIT polygonUpdated(d->m_placemark);
}

void EditPolygonDialog::updateLinesDialog(const QColor &color)
{
    QPixmap linesPixmap(d->m_linesColorButton->iconSize().width(), d->m_linesColorButton->iconSize().height());
    linesPixmap.fill(color);
    d->m_linesColorButton->setIcon(QIcon(linesPixmap));
}

void EditPolygonDialog::updatePolyDialog(const QColor &color)
{
    QPixmap polyPixmap(d->m_polyColorButton->iconSize().width(), d->m_polyColorButton->iconSize().height());
    polyPixmap.fill(color);
    d->m_polyColorButton->setIcon(QIcon(polyPixmap));
}

void EditPolygonDialog::checkFields()
{
    bool ok = true;
    if (d->m_name->text().isEmpty()) {
        QMessageBox::warning(this, tr("No name specified"), tr("Please specify a name for this polygon."));
        ok = false;
    } else {
        if (const auto polygon = geodata_cast<GeoDataPolygon>(d->m_placemark->geometry())) {
            if (polygon->outerBoundary().size() < 3) {
                QMessageBox::warning(this, tr("Not enough nodes specified."), tr("Please specify at least 3 nodes for the polygon by clicking on the map."));
                ok = false;
            }
        }
    }
    if (ok) {
        accept();
    }
}

void EditPolygonDialog::restoreInitial(int result)
{
    if (result) {
        return;
    }

    auto polygon = static_cast<GeoDataPolygon *>(d->m_placemark->geometry());
    GeoDataLinearRing outerBoundary = polygon->outerBoundary();

    if (outerBoundary != d->m_initialOuterBoundary) {
        polygon->setOuterBoundary(d->m_initialOuterBoundary);
    }

    if (d->m_placemark->name() != d->m_initialName) {
        d->m_placemark->setName(d->m_initialName);
    }

    if (d->m_placemark->description() != d->m_initialDescription) {
        d->m_placemark->setDescription(d->m_initialDescription);
    }

    if (*d->m_placemark->style() != d->m_initialStyle) {
        d->m_placemark->setStyle(GeoDataStyle::Ptr(new GeoDataStyle(d->m_initialStyle)));
    }

    if (d->m_hadInitialOsmData) {
        d->m_placemark->setOsmData(d->m_initialOsmData);
    }

    Q_EMIT polygonUpdated(d->m_placemark);
}

}
#include "moc_EditPolygonDialog.cpp"
