// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2012 Rene Kuettner <rene@bitkanal.net>
//

#include "EclipsesPlugin.h"

#include "GeoPainter.h"
#include "MarbleClock.h"
#include "MarbleColors.h"
#include "MarbleModel.h"
#include "MarbleWidget.h"
#include "ViewportParams.h"

#include "EclipsesBrowserDialog.h"
#include "EclipsesItem.h"
#include "EclipsesModel.h"

#include "ui_EclipsesConfigDialog.h"
#include "ui_EclipsesReminderDialog.h"

#include <QMenu>
#include <QPushButton>

namespace Marble
{

EclipsesPlugin::EclipsesPlugin()
    : RenderPlugin(nullptr)
    , m_isInitialized(false)
    , m_marbleWidget(nullptr)
    , m_model(nullptr)
    , m_eclipsesActionGroup(nullptr)
    , m_eclipsesMenuAction(nullptr)
    , m_eclipsesListMenu(nullptr)
    , m_menuYear(0)
    , m_configDialog(nullptr)
    , m_configWidget(nullptr)
    , m_browserDialog(nullptr)
    , m_reminderDialog(nullptr)
    , m_reminderWidget(nullptr)
{
}

EclipsesPlugin::EclipsesPlugin(const MarbleModel *marbleModel)
    : RenderPlugin(marbleModel)
    , m_isInitialized(false)
    , m_marbleWidget(nullptr)
    , m_model(nullptr)
    , m_eclipsesActionGroup(nullptr)
    , m_eclipsesMenuAction(nullptr)
    , m_eclipsesListMenu(nullptr)
    , m_menuYear(0)
    , m_configDialog(nullptr)
    , m_configWidget(nullptr)
    , m_browserDialog(nullptr)
    , m_reminderDialog(nullptr)
    , m_reminderWidget(nullptr)
{
    connect(this, &RenderPlugin::settingsChanged, this, &EclipsesPlugin::updateSettings);
}

EclipsesPlugin::~EclipsesPlugin()
{
    if (m_isInitialized) {
        delete m_model;
        delete m_eclipsesActionGroup;
        delete m_eclipsesListMenu;
        delete m_configDialog;
        delete m_configWidget;
        delete m_browserDialog;
        delete m_reminderDialog;
        delete m_reminderWidget;
    }
}

QStringList EclipsesPlugin::backendTypes() const
{
    return QStringList(QStringLiteral("eclipses"));
}

QString EclipsesPlugin::renderPolicy() const
{
    return QStringLiteral("ALWAYS");
}

QStringList EclipsesPlugin::renderPosition() const
{
    return QStringList(QStringLiteral("ORBIT"));
}

QString EclipsesPlugin::name() const
{
    return tr("Eclipses");
}

QString EclipsesPlugin::nameId() const
{
    return QStringLiteral("eclipses");
}

QString EclipsesPlugin::guiString() const
{
    return tr("E&clipses");
}

QString EclipsesPlugin::version() const
{
    return QStringLiteral("1.0");
}

QString EclipsesPlugin::description() const
{
    return tr("This plugin visualizes solar eclipses.");
}

QString EclipsesPlugin::copyrightYears() const
{
    return QStringLiteral("2013");
}

QList<PluginAuthor> EclipsesPlugin::pluginAuthors() const
{
    return QList<PluginAuthor>() << PluginAuthor(QStringLiteral("Rene Kuettner"), QStringLiteral("rene@bitkanal.net"))
                                 << PluginAuthor(QStringLiteral("Gerhard Holtkamp"), QString());
}

QIcon EclipsesPlugin::icon() const
{
    return QIcon(QStringLiteral(":res/eclipses.png"));
}

RenderPlugin::RenderType EclipsesPlugin::renderType() const
{
    return RenderPlugin::ThemeRenderType;
    // return UnknownRenderType;
}

QList<QActionGroup *> *EclipsesPlugin::actionGroups() const
{
    return const_cast<QList<QActionGroup *> *>(&m_actionGroups);
}

QDialog *EclipsesPlugin::configDialog()
{
    Q_ASSERT(m_isInitialized);
    return m_configDialog;
}

void EclipsesPlugin::initialize()
{
    if (isInitialized()) {
        return;
    }

    // initialize dialogs
    delete m_configDialog;
    m_configDialog = new QDialog();
    delete m_configWidget;
    m_configWidget = new Ui::EclipsesConfigDialog();
    m_configWidget->setupUi(m_configDialog);

    connect(m_configDialog, &QDialog::accepted, this, &EclipsesPlugin::writeSettings);
    connect(m_configDialog, &QDialog::rejected, this, &EclipsesPlugin::readSettings);
    connect(m_configWidget->buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, this, &EclipsesPlugin::readSettings);
    connect(m_configWidget->buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &EclipsesPlugin::writeSettings);
    connect(m_configWidget->buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &EclipsesPlugin::updateEclipses);

    m_browserDialog = new EclipsesBrowserDialog(marbleModel());
    connect(m_browserDialog, &EclipsesBrowserDialog::buttonShowClicked, this, &EclipsesPlugin::showEclipse);
    connect(m_browserDialog, &EclipsesBrowserDialog::buttonSettingsClicked, m_configDialog, &QWidget::show);

    delete m_reminderDialog;
    m_reminderDialog = new QDialog();
    delete m_reminderWidget;
    m_reminderWidget = new Ui::EclipsesReminderDialog();
    m_reminderWidget->setupUi(m_reminderDialog);

    // initialize menu entries
    m_eclipsesActionGroup = new QActionGroup(this);
    m_actionGroups.append(m_eclipsesActionGroup);

    m_eclipsesListMenu = new QMenu();
    m_eclipsesActionGroup->addAction(m_eclipsesListMenu->menuAction());
    connect(m_eclipsesListMenu, &QMenu::triggered, this, &EclipsesPlugin::showEclipseFromMenu);

    m_eclipsesMenuAction = new QAction(tr("Browse Ecli&pses..."), m_eclipsesActionGroup);
    m_eclipsesMenuAction->setIcon(QIcon(QStringLiteral(":res/eclipses.png")));
    m_eclipsesActionGroup->addAction(m_eclipsesMenuAction);
    connect(m_eclipsesMenuAction, &QAction::triggered, m_browserDialog, &QWidget::show);

    // initialize eclipses model
    m_model = new EclipsesModel(marbleModel());

    connect(marbleModel()->clock(), &MarbleClock::timeChanged, this, &EclipsesPlugin::updateEclipses);

    m_isInitialized = true;

    readSettings();
    updateEclipses();
    updateMenuItemState();
    updateSettings();
}

bool EclipsesPlugin::isInitialized() const
{
    return m_isInitialized;
}

bool EclipsesPlugin::eventFilter(QObject *object, QEvent *e)
{
    // delayed initialization of pointer to marble widget
    auto widget = dynamic_cast<MarbleWidget *>(object);
    if (widget && m_marbleWidget != widget) {
        connect(widget, &MarbleWidget::themeChanged, this, &EclipsesPlugin::updateMenuItemState);
        m_marbleWidget = widget;
    }

    return RenderPlugin::eventFilter(object, e);
}

bool EclipsesPlugin::render(GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer)
{
    Q_UNUSED(viewport);
    Q_UNUSED(renderPos);
    Q_UNUSED(layer);

    if (marbleModel()->planetId() == QLatin1StringView("earth")) {
        for (EclipsesItem *item : m_model->items()) {
            if (item->takesPlaceAt(marbleModel()->clock()->dateTime())) {
                return renderItem(painter, item);
            }
        }
    }

    return true;
}

bool EclipsesPlugin::renderItem(GeoPainter *painter, EclipsesItem *item) const
{
    int phase = item->phase();

    // Draw full penumbra shadow cone
    if (m_configWidget->checkBoxShowFullPenumbra->isChecked()) {
        painter->setPen(Oxygen::aluminumGray1);
        QColor sunBoundingBrush(Oxygen::aluminumGray6);
        sunBoundingBrush.setAlpha(48);
        painter->setBrush(sunBoundingBrush);
        painter->drawPolygon(item->shadowConePenumbra());
    }

    // Draw 60% penumbra shadow cone
    if (m_configWidget->checkBoxShow60MagPenumbra->isChecked()) {
        painter->setPen(Oxygen::aluminumGray2);
        QColor penumbraBrush(Oxygen::aluminumGray6);
        penumbraBrush.setAlpha(96);
        painter->setBrush(penumbraBrush);
        painter->drawPolygon(item->shadowCone60MagPenumbra());
    }

    // Draw southern boundary of the penumbra
    if (m_configWidget->checkBoxShowSouthernPenumbra->isChecked()) {
        QColor southernBoundaryColor(Oxygen::brickRed1);
        southernBoundaryColor.setAlpha(128);
        QPen southernBoundary(southernBoundaryColor);
        southernBoundary.setWidth(3);
        painter->setPen(southernBoundary);
        painter->drawPolyline(item->southernPenumbra());
        painter->setPen(Oxygen::brickRed5);
        painter->drawPolyline(item->southernPenumbra());
    }

    // Draw northern boundary of the penumbra
    if (m_configWidget->checkBoxShowNorthernPenumbra->isChecked()) {
        QColor northernBoundaryColor(Oxygen::brickRed1);
        northernBoundaryColor.setAlpha(128);
        QPen northernBoundary(northernBoundaryColor);
        northernBoundary.setWidth(3);
        painter->setPen(northernBoundary);
        painter->drawPolyline(item->northernPenumbra());
        painter->setPen(Oxygen::brickRed5);
        painter->drawPolyline(item->northernPenumbra());
    }

    // Draw Sunrise / Sunset Boundaries
    if (m_configWidget->checkBoxShowSunBoundaries->isChecked()) {
        painter->setPen(Oxygen::hotOrange6);
        const QList<GeoDataLinearRing> boundaries = item->sunBoundaries();
        QList<GeoDataLinearRing>::const_iterator i = boundaries.constBegin();
        QColor sunBoundingBrush(Oxygen::hotOrange5);
        sunBoundingBrush.setAlpha(64);
        painter->setBrush(sunBoundingBrush);
        for (; i != boundaries.constEnd(); ++i) {
            painter->drawPolygon(*i);
        }
    }

    // total or annular eclipse
    if (m_configWidget->checkBoxShowUmbra->isChecked() && phase > 3) {
        painter->setPen(Oxygen::aluminumGray4);
        QColor sunBoundingBrush(Oxygen::aluminumGray6);
        sunBoundingBrush.setAlpha(128);
        painter->setBrush(sunBoundingBrush);
        painter->drawPolygon(item->umbra());

        // draw shadow cone
        painter->setPen(Qt::black);
        QColor shadowConeBrush(Oxygen::aluminumGray6);
        shadowConeBrush.setAlpha(128);
        painter->setBrush(shadowConeBrush);
        painter->drawPolygon(item->shadowConeUmbra());
    }

    // plot central line
    if (m_configWidget->checkBoxShowCentralLine->isChecked() && phase > 3) {
        painter->setPen(Qt::black);
        painter->drawPolyline(item->centralLine());
    }

    // mark point of maximum eclipse
    if (m_configWidget->checkBoxShowMaximum->isChecked()) {
        painter->setPen(Qt::white);
        QColor sunBoundingBrush(Qt::white);
        sunBoundingBrush.setAlpha(128);
        painter->setBrush(sunBoundingBrush);

        painter->drawEllipse(item->maxLocation(), 15, 15);
        painter->setPen(Oxygen::brickRed4);
        painter->drawText(item->maxLocation(), tr("Maximum of Eclipse"));
    }

    return true;
}

QHash<QString, QVariant> EclipsesPlugin::settings() const
{
    return RenderPlugin::settings();
}

void EclipsesPlugin::setSettings(const QHash<QString, QVariant> &settings)
{
    RenderPlugin::setSettings(settings);
    m_settings = settings;
    Q_EMIT settingsChanged(nameId());
}

void EclipsesPlugin::readSettings()
{
    m_configWidget->checkBoxEnableLunarEclipses->setChecked(m_settings.value(QStringLiteral("enableLunarEclipses"), false).toBool());
    m_configWidget->checkBoxShowMaximum->setChecked(m_settings.value(QStringLiteral("showMaximum"), true).toBool());
    m_configWidget->checkBoxShowUmbra->setChecked(m_settings.value(QStringLiteral("showUmbra"), true).toBool());
    m_configWidget->checkBoxShowSouthernPenumbra->setChecked(m_settings.value(QStringLiteral("showSouthernPenumbra"), true).toBool());
    m_configWidget->checkBoxShowNorthernPenumbra->setChecked(m_settings.value(QStringLiteral("showNorthernPenumbra"), true).toBool());
    m_configWidget->checkBoxShowCentralLine->setChecked(m_settings.value(QStringLiteral("showCentralLine"), true).toBool());
    m_configWidget->checkBoxShowFullPenumbra->setChecked(m_settings.value(QStringLiteral("showFullPenumbra"), true).toBool());
    m_configWidget->checkBoxShow60MagPenumbra->setChecked(m_settings.value(QStringLiteral("show60MagPenumbra"), false).toBool());
    m_configWidget->checkBoxShowSunBoundaries->setChecked(m_settings.value(QStringLiteral("showSunBoundaries"), true).toBool());
}

void EclipsesPlugin::writeSettings()
{
    m_settings.insert(QStringLiteral("enableLunarEclipses"), m_configWidget->checkBoxEnableLunarEclipses->isChecked());
    m_settings.insert(QStringLiteral("showMaximum"), m_configWidget->checkBoxShowMaximum->isChecked());
    m_settings.insert(QStringLiteral("showUmbra"), m_configWidget->checkBoxShowUmbra->isChecked());
    m_settings.insert(QStringLiteral("showSouthernPenumbra"), m_configWidget->checkBoxShowSouthernPenumbra->isChecked());
    m_settings.insert(QStringLiteral("showNorthernPenumbra"), m_configWidget->checkBoxShowNorthernPenumbra->isChecked());
    m_settings.insert(QStringLiteral("showCentralLine"), m_configWidget->checkBoxShowCentralLine->isChecked());
    m_settings.insert(QStringLiteral("showFullPenumbra"), m_configWidget->checkBoxShowFullPenumbra->isChecked());
    m_settings.insert(QStringLiteral("show60MagPenumbra"), m_configWidget->checkBoxShow60MagPenumbra->isChecked());
    m_settings.insert(QStringLiteral("showSunBoundaries"), m_configWidget->checkBoxShowSunBoundaries->isChecked());

    Q_EMIT settingsChanged(nameId());
}

void EclipsesPlugin::updateSettings()
{
    if (!isInitialized()) {
        return;
    }

    m_browserDialog->setWithLunarEclipses(m_settings.value(QStringLiteral("enableLunarEclipses")).toBool());
    if (m_model->withLunarEclipses() != m_settings.value(QStringLiteral("enableLunarEclipses")).toBool()) {
        updateEclipses();
    }
}

void EclipsesPlugin::updateEclipses()
{
    // mDebug() << "Updating eclipses....";
    const int year = marbleModel()->clock()->dateTime().date().year();
    const bool lun = m_settings.value(QStringLiteral("enableLunarEclipses")).toBool();

    if ((m_menuYear != year) || (m_model->withLunarEclipses() != lun)) {
        // remove old menus
        for (QAction *action : m_eclipsesListMenu->actions()) {
            m_eclipsesListMenu->removeAction(action);
            delete action;
        }

        // update year and create menus for this year's eclipse events
        if (m_model->year() != year) {
            m_model->setYear(year);
        }
        m_menuYear = year;

        // enable/disable lunar eclipses if necessary
        if (m_model->withLunarEclipses() != lun) {
            m_model->setWithLunarEclipses(lun);
        }

        m_eclipsesListMenu->setTitle(tr("Eclipses in %1").arg(year));

        for (EclipsesItem *item : m_model->items()) {
            QAction *action = m_eclipsesListMenu->addAction(item->dateMaximum().date().toString());
            action->setData(QVariant(1000 * item->dateMaximum().date().year() + item->index()));
            action->setIcon(item->icon());
        }

        Q_EMIT actionGroupsChanged();
    }
}

void EclipsesPlugin::updateMenuItemState()
{
    if (!isInitialized()) {
        return;
    }

    // eclipses are only supported for earth based observers at the moment
    // so we disable the menu items for other celestial bodies

    const bool active = (marbleModel()->planetId() == QLatin1StringView("earth"));

    m_eclipsesListMenu->setEnabled(active);
    m_eclipsesMenuAction->setEnabled(active);
}

void EclipsesPlugin::showEclipse(int year, int index)
{
    if (m_model->year() != year) {
        m_model->setYear(year);
    }

    EclipsesItem *item = m_model->eclipseWithIndex(index);
    Q_ASSERT(item);

    if (item) {
        m_marbleWidget->model()->clock()->setDateTime(item->dateMaximum());
        m_marbleWidget->centerOn(item->maxLocation());
    }
}

void EclipsesPlugin::showEclipseFromMenu(QAction *action)
{
    Q_ASSERT(action->data().isValid());
    int year = action->data().toInt() / 1000;
    int index = action->data().toInt() - 1000 * year;

    showEclipse(year, index);
}

} // namespace Marble

#include "moc_EclipsesPlugin.cpp"
