// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2011 Guillaume Martres <smarter@ubuntu.com>
// SPDX-FileCopyrightText: 2012 Rene Kuettner <rene@bitkanal.net>
//

#include "SatellitesPlugin.h"

#include "GeoDataPlacemark.h"
#include "MarbleDebug.h"
#include "MarbleModel.h"
#include "MarbleWidget.h"
#include "MarbleWidgetPopupMenu.h"
#include "SatellitesConfigModel.h"
#include "SatellitesMSCItem.h"
#include "ViewportParams.h"

#include "ui_SatellitesConfigDialog.h"

#include <QAction>
#include <QMouseEvent>
#include <QUrl>

namespace Marble
{

SatellitesPlugin::SatellitesPlugin(const MarbleModel *marbleModel)
    : RenderPlugin(marbleModel)
    , m_satModel(nullptr)
    , m_isInitialized(false)
    , m_configDialog(nullptr)
{
    connect(this, &RenderPlugin::settingsChanged, this, &SatellitesPlugin::updateSettings);
    connect(this, &RenderPlugin::enabledChanged, this, &SatellitesPlugin::enableModel);
    connect(this, &RenderPlugin::visibilityChanged, this, &SatellitesPlugin::visibleModel);

    setVisible(false);
    setSettings(QHash<QString, QVariant>());

    m_showOrbitAction = new QAction(tr("Display orbit"), this);
    m_showOrbitAction->setCheckable(true);
    m_showOrbitAction->setData(0);

    m_trackPlacemarkAction = new QAction(tr("Keep centered"), this);
    m_trackPlacemarkAction->setData(0);
    connect(m_showOrbitAction, &QAction::triggered, this, &SatellitesPlugin::showOrbit);
    connect(m_trackPlacemarkAction, &QAction::triggered, this, &SatellitesPlugin::trackPlacemark);
}

SatellitesPlugin::~SatellitesPlugin()
{
    delete m_satModel;

    delete m_configDialog;
    delete m_showOrbitAction;
    delete m_trackPlacemarkAction;
}

QStringList SatellitesPlugin::backendTypes() const
{
    return QStringList(QStringLiteral("satellites"));
}

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

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

QString SatellitesPlugin::name() const
{
    return tr("Satellites");
}

QString SatellitesPlugin::nameId() const
{
    return QStringLiteral("satellites");
}

QString SatellitesPlugin::guiString() const
{
    return tr("&Satellites");
}

QString SatellitesPlugin::version() const
{
    return QStringLiteral("2.0");
}

QString SatellitesPlugin::description() const
{
    return tr("This plugin displays satellites and their orbits.");
}

QString SatellitesPlugin::copyrightYears() const
{
    return QStringLiteral("2012");
}

QList<PluginAuthor> SatellitesPlugin::pluginAuthors() const
{
    return QList<PluginAuthor>() << PluginAuthor(QStringLiteral("Guillaume Martres"), QStringLiteral("smarter@ubuntu.com"))
                                 << PluginAuthor(QStringLiteral("Rene Kuettner"), QStringLiteral("rene@bitkanal.net"))
                                 << PluginAuthor(QStringLiteral("Gerhard Holtkamp"), QString());
}

QString SatellitesPlugin::aboutDataText() const
{
    return tr(
        "Earth-Satellites orbital elements from <ul><li>"
        "<a href=\"https://www.celestrak.com\">https://www.celestrak.com</a>"
        "</li></ul>"
        "Planetary-Satellites orbital elements from <ul><li>"
        "<a href=\"https://ssd.jpl.nasa.gov/?horizons\">"
        "JPL Horizons</a></li></ul>");
}

QIcon SatellitesPlugin::icon() const
{
    return QIcon(QStringLiteral(":/data/bitmaps/satellite.png"));
}

RenderPlugin::RenderType SatellitesPlugin::renderType() const
{
    return OnlineRenderType;
}

void SatellitesPlugin::initialize()
{
    // FIXME: remove the const_cast, it may be best to create a new type of
    // plugins where marbleModel() is not const, since traditional
    // RenderPlugins do not require that
    m_satModel = new SatellitesModel(const_cast<MarbleModel *>(marbleModel())->treeModel(), marbleModel()->clock());

    m_configModel = new SatellitesConfigModel(this);

    delete m_configDialog;
    m_configDialog = new SatellitesConfigDialog();
    connect(m_configDialog, SIGNAL(activatePluginClicked()), this, SLOT(activate()));
    connect(this, SIGNAL(visibilityChanged(bool, QString)), m_configDialog, SLOT(setDialogActive(bool)));
    m_configDialog->configWidget()->treeView->setModel(m_configModel);

    connect(m_satModel, SIGNAL(fileParsed(QString)), SLOT(dataSourceParsed(QString)));
    connect(m_satModel, SIGNAL(fileParsed(QString)), SLOT(updateDataSourceConfig(QString)));
    connect(m_configDialog, SIGNAL(dataSourcesReloadRequested()), SLOT(updateSettings()));
    connect(m_configDialog, SIGNAL(accepted()), SLOT(writeSettings()));
    connect(m_configDialog, SIGNAL(rejected()), SLOT(readSettings()));
    connect(m_configDialog->configWidget()->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), SLOT(restoreDefaultSettings()));
    connect(m_configDialog, SIGNAL(userDataSourcesChanged()), SLOT(writeSettings()));
    connect(m_configDialog, SIGNAL(userDataSourceAdded(QString)), SLOT(userDataSourceAdded(QString)));

    m_isInitialized = true;
    readSettings();
    updateSettings();
    enableModel(enabled());
}

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

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

    enableModel(enabled());

    return true;
}

bool SatellitesPlugin::eventFilter(QObject *object, QEvent *event)
{
    // only if active plugin
    if (!enabled() || !visible()) {
        return false;
    }

    if (event->type() != QEvent::MouseButtonPress) {
        return false;
    }

    auto widget = qobject_cast<MarbleWidget *>(object);
    Q_ASSERT(widget);

    auto mouseEvent = dynamic_cast<QMouseEvent *>(event);
    Q_ASSERT(mouseEvent);

    if (mouseEvent->button() == Qt::LeftButton) {
        m_trackerList.clear();
        QList<const GeoDataFeature *> vector = widget->whichFeatureAt(mouseEvent->pos());
        for (const GeoDataFeature *feature : std::as_const(vector)) {
            const auto placemark = dynamic_cast<const GeoDataPlacemark *>(feature);
            if (placemark) {
                for (TrackerPluginItem *obj : m_satModel->items()) {
                    if (obj->placemark() == placemark) {
                        m_showOrbitAction->data() = m_trackerList.size();
                        m_showOrbitAction->setChecked(obj->isTrackVisible());
                        widget->popupMenu()->addAction(Qt::LeftButton, m_showOrbitAction);

                        m_trackPlacemarkAction->data() = m_trackerList.size();
                        widget->popupMenu()->addAction(Qt::LeftButton, m_trackPlacemarkAction);

                        m_trackerList.append(obj);
                    }
                }
            }
        }
    }
    return false;
}

void SatellitesPlugin::showOrbit(bool show)
{
    auto action = qobject_cast<QAction *>(sender());
    Q_ASSERT(action);

    int actionIndex = action->data().toInt();
    TrackerPluginItem *item = m_trackerList.at(actionIndex);
    item->setTrackVisible(show);
    m_satModel->updateVisibility();
}

void SatellitesPlugin::trackPlacemark()
{
    auto action = qobject_cast<QAction *>(sender());
    Q_ASSERT(action);

    int actionIndex = action->data().toInt();
    TrackerPluginItem *item = m_trackerList.at(actionIndex);
    const_cast<MarbleModel *>(marbleModel())->setTrackedPlacemark(item->placemark());
}

QHash<QString, QVariant> SatellitesPlugin::settings() const
{
    QHash<QString, QVariant> result = RenderPlugin::settings();

    typedef QHash<QString, QVariant>::ConstIterator Iterator;
    Iterator end = m_settings.constEnd();
    for (Iterator iter = m_settings.constBegin(); iter != end; ++iter) {
        result.insert(iter.key(), iter.value());
    }

    return result;
}

void SatellitesPlugin::setSettings(const QHash<QString, QVariant> &settings)
{
    RenderPlugin::setSettings(settings);

    // reset
    m_newDataSources.clear();
    // TODO: cancel also all on-going downloads

    // add default data sources
    if (!settings.contains(QStringLiteral("dataSources"))) {
        QStringList dsList;
        dsList << QStringLiteral("https://www.celestrak.com/NORAD/elements/visual.txt");
        m_settings.insert(QStringLiteral("dataSources"), dsList);
        m_settings.insert(QStringLiteral("idList"), dsList);
    } else {
        // HACK: KConfig can't guess the type of the settings, when we use
        // KConfigGroup::readEntry() in marble_part it returns a QString which
        // is then wrapped into a QVariant when added to the settings hash.
        // QVariant can handle the conversion for some types, like toDateTime()
        // but when calling toStringList() on a QVariant::String, it will
        // return a one element list
        if (settings.value(QStringLiteral("dataSources")).type() == QVariant::String) {
            m_settings.insert(QStringLiteral("dataSources"), settings.value(QStringLiteral("dataSources")).toString().split(QLatin1Char(',')));
        }
        if (settings.value(QStringLiteral("idList")).type() == QVariant::String) {
            m_settings.insert(QStringLiteral("idList"), settings.value(QStringLiteral("idList")).toString().split(QLatin1Char(',')));
        }
    }

    // add default user data source
    if (!settings.contains(QStringLiteral("userDataSources"))) {
        QStringList udsList;
        udsList << QStringLiteral("http://files.kde.org/marble/satellites/PlanetarySatellites.msc");
        m_settings.insert(QStringLiteral("userDataSources"), udsList);
        userDataSourceAdded(udsList[0]);
    } else if (settings.value(QStringLiteral("userDataSources")).type() == QVariant::String) {
        // same HACK as above
        m_settings.insert(QStringLiteral("userDataSources"), settings.value(QStringLiteral("userDataSources")).toString().split(QLatin1Char(',')));
    }

    Q_EMIT settingsChanged(nameId());
}

void SatellitesPlugin::readSettings()
{
    m_configDialog->setUserDataSources(m_settings.value(QStringLiteral("userDataSources")).toStringList());
    m_configModel->loadSettings(m_settings);
    m_satModel->loadSettings(m_settings);
}

void SatellitesPlugin::writeSettings()
{
    m_settings.insert(QStringLiteral("userDataSources"), m_configDialog->userDataSources());
    m_settings.insert(QStringLiteral("dataSources"), m_configModel->urlList());
    m_settings.insert(QStringLiteral("idList"), m_configModel->idList());

    Q_EMIT settingsChanged(nameId());
}

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

    // TODO: cancel also all on-going downloads
    m_satModel->clear();

    m_configModel->clear();
    addBuiltInDataSources();

    // (re)load data sources
    QStringList dsList = m_settings[QStringLiteral("dataSources")].toStringList();
    dsList << m_settings[QStringLiteral("userDataSources")].toStringList();
    dsList.removeDuplicates();
    for (const QString &ds : std::as_const(dsList)) {
        mDebug() << "Loading satellite data from:" << ds;
        m_satModel->downloadFile(QUrl(ds), ds);
    }
}

void SatellitesPlugin::dataSourceParsed(const QString &source)
{
    m_configDialog->setUserDataSourceLoaded(source, true);
}

void SatellitesPlugin::userDataSourceAdded(const QString &source)
{
    // items contained in catalog data sources are not known before
    // the catalog has been parsed. so we store new data sources in
    // order to activate them later (new datasources are enabled by
    // default)
    if (!m_newDataSources.contains(source)) {
        m_newDataSources.append(source);
    }
}

SatellitesConfigDialog *SatellitesPlugin::configDialog()
{
    return m_configDialog;
}

void SatellitesPlugin::activate()
{
    action()->trigger();
}

void SatellitesPlugin::enableModel(bool enabled)
{
    if (!m_isInitialized) {
        return;
    }

    m_satModel->setPlanet(marbleModel()->planetId());
    m_satModel->enable(enabled && visible());
}

void SatellitesPlugin::visibleModel(bool visible)
{
    if (!m_isInitialized) {
        return;
    }

    m_satModel->setPlanet(marbleModel()->planetId());
    m_satModel->enable(enabled() && visible);
}

void SatellitesPlugin::updateDataSourceConfig(const QString &source)
{
    mDebug() << "Updating orbiter configuration";

    for (TrackerPluginItem *obj : m_satModel->items()) {
        // catalog items
        auto item = dynamic_cast<SatellitesMSCItem *>(obj);
        if ((item != nullptr) && (item->catalog() == source)) {
            m_configDialog->addSatelliteItem(item->relatedBody(), item->category(), item->name(), item->id());
        }
    }

    // activate new datasources by default
    if (m_newDataSources.contains(source)) {
        m_newDataSources.removeAll(source);
        activateDataSource(source);
    }

    readSettings();
    m_configDialog->update();
}

void SatellitesPlugin::activateDataSource(const QString &source)
{
    // activate the given data source (select it)
    mDebug() << "Activating Data Source:" << source;
    QStringList list = m_configModel->fullIdList().filter(source);
    QStringList idList = m_settings[QStringLiteral("idList")].toStringList();
    idList << list;
    m_settings.insert(QStringLiteral("idList"), idList);
}

void SatellitesPlugin::addBuiltInDataSources()
{
    QString currentCategory;

    currentCategory = tr("Special-Interest Satellites");
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Last 30 Days' Launches", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/tle-new.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Space Stations", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/stations.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("100 (or so) Brightest", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/visual.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("FENGYUN 1C Debris", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/1999-025.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("IRIDIUM 33 Debris", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/iridium-33-debris.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("COSMOS 2251 Debris", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/cosmos-2251-debris.txt"));

    currentCategory = tr("Weather & Earth Resources Satellites");
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Weather", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/weather.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("NOAA", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/noaa.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("GOES", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/goes.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Earth Resources", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/resource.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Search & Rescue (SARSAT)", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/sarsat.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Disaster Monitoring", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/dmc.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Tracking and Data Relay Satellite System (TDRSS)", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/tdrss.txt"));

    currentCategory = tr("Communications Satellites");
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Geostationary", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/geo.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Intelsat", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/intelsat.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Gorizont", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/gorizont.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Raduga", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/raduga.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Molniya", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/molniya.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Iridium", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/iridium.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Orbcomm", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/orbcomm.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Globalstar", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/globalstar.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Amateur radio", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/amateur.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Experimental", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/x-comm.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Other", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/other-comm.txt"));

    currentCategory = tr("Navigation Satellites");
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("GPS Operational", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/gps-ops.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Glonass Operational", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/glo-ops.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Galileo", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/galileo.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Satellite-Based Augmentation System (WAAS/EGNOS/MSAS)", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/sbas.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Navy Navigation Satellite System (NNSS)", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/nnss.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Russian LEO Navigation", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/musson.txt"));

    currentCategory = tr("Scientific Satellites");
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Space & Earth Science", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/science.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Geodetic", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/geodetic.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Engineering", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/engineering.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Education", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/education.txt"));

    currentCategory = tr("Miscellaneous Satellites");
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Miscellaneous Military", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/military.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Radar Calibration", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/radar.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("CubeSats", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/cubesat.txt"));
    m_configDialog->addTLESatelliteItem(currentCategory,
                                        tr("Other", "Name of a satellite group"),
                                        QStringLiteral("https://www.celestrak.com/NORAD/elements/other.txt"));

    readSettings();
    m_configDialog->update();
}

} // namespace Marble

#include "moc_SatellitesPlugin.cpp"
