/***************************************************************************
                          kspopupmenu.cpp  -  K Desktop Planetarium
                             -------------------
    begin                : Sat Feb 27 2003
    copyright            : (C) 2001 by Jason Harris
    email                : jharris@30doradus.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "kspopupmenu.h"

#include "config-kstars.h"

#include "kstars.h"
#include "kstarsdata.h"
#include "skymap.h"
#include "syncedcatalogcomponent.h"
#include "skyobjects/skyobject.h"
#include "skyobjects/starobject.h"
#include "skyobjects/trailobject.h"
#include "skyobjects/deepskyobject.h"
#include "skyobjects/ksmoon.h"
#include "skyobjects/satellite.h"
#include "skyobjects/supernova.h"
#include "skycomponents/constellationboundarylines.h"
#include "skycomponents/flagcomponent.h"
#include "skycomponents/skymapcomposite.h"
#include "tools/whatsinteresting/wiview.h"

#include "observinglist.h"

#ifdef HAVE_INDI
#include "indi/indilistener.h"
#include "indi/guimanager.h"
#include "indi/driverinfo.h"
#include "indi/indistd.h"
#include "indi/indidevice.h"
#include "indi/indigroup.h"
#include "indi/indiproperty.h"
#include "indi/indielement.h"
#include "indi/inditelescope.h"
#include <basedevice.h>
#endif

#include <KLocalizedString>

#include <QSignalMapper>
#include <QWidgetAction>

namespace
{
// Convert magnitude to string representation for QLabel
QString magToStr(double m)
{
    if (m > -30 && m < 90)
        return QString("%1<sup>m</sup>").arg(m, 0, 'f', 2);
    else
        return QString();
}

// Return object name
QString getObjectName(SkyObject *obj)
{
    // FIXME: make logic less convoluted.
    if (obj->longname() != obj->name()) // Object has proper name
    {
        return obj->translatedLongName() + ", " + obj->translatedName();
    }
    else
    {
        if (!obj->translatedName2().isEmpty())
            return obj->translatedName() + ", " + obj->translatedName2();
        else
            return obj->translatedName();
    }
}

// String representation for rise/set time of object. If object
// doesn't rise/set returns descriptive string.
//
// Second parameter choose between raise and set. 'true' for
// raise, 'false' for set.
QString riseSetTimeLabel(SkyObject *o, bool isRaise)
{
    KStarsData *data = KStarsData::Instance();
    QTime t          = o->riseSetTime(data->ut(), data->geo(), isRaise);
    if (t.isValid())
    {
        //We can round to the nearest minute by simply adding 30 seconds to the time.
        QString time = QLocale().toString(t.addSecs(30), QLocale::ShortFormat);
        return isRaise ? i18n("Rise time: %1", time) :
                         i18nc("the time at which an object falls below the horizon", "Set time: %1", time);
    }
    if (o->alt().Degrees() > 0)
        return isRaise ? i18n("No rise time: Circumpolar") : i18n("No set time: Circumpolar");
    else
        return isRaise ? i18n("No rise time: Never rises") : i18n("No set time: Never rises");
}

// String representation for transit time for object
QString transitTimeLabel(SkyObject *o)
{
    KStarsData *data = KStarsData::Instance();
    QTime t          = o->transitTime(data->ut(), data->geo());
    if (t.isValid())
        //We can round to the nearest minute by simply adding 30 seconds to the time.
        return i18n("Transit time: %1", QLocale().toString(t.addSecs(30), QLocale::ShortFormat));
    else
        return "--:--";
}
}

KSPopupMenu::KSPopupMenu()
    : QMenu(KStars::Instance()), m_CurrentFlagIdx(-1), m_EditActionMapping(nullptr), m_DeleteActionMapping(nullptr)
{
}

KSPopupMenu::~KSPopupMenu()
{
    if (m_EditActionMapping)
    {
        delete m_EditActionMapping;
    }

    if (m_DeleteActionMapping)
    {
        delete m_DeleteActionMapping;
    }
}

void KSPopupMenu::createEmptyMenu(SkyPoint *nullObj)
{
    KStars *ks = KStars::Instance();
    SkyObject o(SkyObject::TYPE_UNKNOWN, nullObj->ra(), nullObj->dec());
    o.setAlt(nullObj->alt());
    o.setAz(nullObj->az());
    initPopupMenu(&o, i18n("Empty sky"), QString(), QString(), false, false);
    addAction(i18nc("Sloan Digital Sky Survey", "Show SDSS Image"), ks->map(), SLOT(slotSDSS()));
    addAction(i18nc("Digitized Sky Survey", "Show DSS Image"), ks->map(), SLOT(slotDSS()));
}

void KSPopupMenu::slotEditFlag()
{
    if (m_CurrentFlagIdx != -1)
    {
        KStars::Instance()->map()->slotEditFlag(m_CurrentFlagIdx);
    }
}

void KSPopupMenu::slotDeleteFlag()
{
    if (m_CurrentFlagIdx != -1)
    {
        KStars::Instance()->map()->slotDeleteFlag(m_CurrentFlagIdx);
    }
}

void KSPopupMenu::slotEditFlag(QAction *action)
{
    int idx = m_EditActionMapping->value(action, -1);

    if (idx == -1)
    {
        return;
    }

    else
    {
        KStars::Instance()->map()->slotEditFlag(idx);
    }
}

void KSPopupMenu::slotDeleteFlag(QAction *action)
{
    int idx = m_DeleteActionMapping->value(action, -1);

    if (idx == -1)
    {
        return;
    }

    else
    {
        KStars::Instance()->map()->slotDeleteFlag(idx);
    }
}

void KSPopupMenu::createStarMenu(StarObject *star)
{
    KStars *ks = KStars::Instance();
    //Add name, rise/set time, center/track, and detail-window items
    QString name;
    if (star->name() != "star")
    {
        name = star->translatedLongName();
    }
    else
    {
        if (star->getHDIndex())
        {
            name = QString("HD%1").arg(QString::number(star->getHDIndex()));
        }
        else
        {
            // FIXME: this should be some catalog name too
            name = "Star";
        }
    }
    initPopupMenu(star, name, i18n("star"), i18n("%1<sup>m</sup>, %2", star->mag(), star->sptype()));
    //If the star is named, add custom items to popup menu based on object's ImageList and InfoList
    if (star->name() != "star")
    {
        addLinksToMenu(star);
    }
    else
    {
        addAction(i18nc("Sloan Digital Sky Survey", "Show SDSS Image"), ks->map(), SLOT(slotSDSS()));
        addAction(i18nc("Digitized Sky Survey", "Show DSS Image"), ks->map(), SLOT(slotDSS()));
    }
}

void KSPopupMenu::createDeepSkyObjectMenu(DeepSkyObject *obj)
{
    QString name     = getObjectName(obj);
    QString typeName = obj->typeName();
    // FIXME: information about angular sizes should be added.
    QString info = magToStr(obj->mag());
    initPopupMenu(obj, name, typeName, info);
    addLinksToMenu(obj);
}

void KSPopupMenu::createPlanetMenu(SkyObject *p)
{
    QString info = magToStr(p->mag());
    QString type = i18n("Solar system object");
    ;
    initPopupMenu(p, p->translatedName(), type, info);
    addLinksToMenu(p, false); //don't offer DSS images for planets
}

void KSPopupMenu::createMoonMenu(KSMoon *moon)
{
    QString info = QString("%1, %2").arg(magToStr(moon->mag()), moon->phaseName());
    initPopupMenu(moon, moon->translatedName(), QString(), info);
    addLinksToMenu(moon, false); //don't offer DSS images for planets
}

void KSPopupMenu::createSatelliteMenu(Satellite *satellite)
{
    KStars *ks = KStars::Instance();
    QString velocity, altitude, range;
    velocity.setNum(satellite->velocity());
    altitude.setNum(satellite->altitude());
    range.setNum(satellite->range());

    clear();

    addFancyLabel(satellite->name());
    addFancyLabel(satellite->id());
    addFancyLabel(i18n("satellite"));
    addFancyLabel(KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(satellite));

    addSeparator();

    addFancyLabel(i18n("Velocity: %1 km/s", velocity), -2);
    addFancyLabel(i18n("Altitude: %1 km", altitude), -2);
    addFancyLabel(i18n("Range: %1 km", range), -2);

    addSeparator();

    //Insert item for centering on object
    addAction(i18n("Center && Track"), ks->map(), SLOT(slotCenter()));
    //Insert item for measuring distances
    //FIXME: add key shortcut to menu items properly!
    addAction(i18n("Angular Distance To...            ["), ks->map(), SLOT(slotBeginAngularDistance()));
    addAction(i18n("Starhop from here to...            "), ks->map(), SLOT(slotBeginStarHop()));

    //Insert "Add/Remove Label" item
    if (ks->map()->isObjectLabeled(satellite))
        addAction(i18n("Remove Label"), ks->map(), SLOT(slotRemoveObjectLabel()));
    else
        addAction(i18n("Attach Label"), ks->map(), SLOT(slotAddObjectLabel()));
}

void KSPopupMenu::createSupernovaMenu(Supernova *supernova)
{
    QString name = supernova->name();
    float mag    = supernova->mag();
    float z      = supernova->getRedShift();
    QString type = supernova->getType();

    QString info;

    if (mag < 99)
        info += QString("%1<sup>m</sup> ").arg(mag);
    info += type;
    if (z < 99)
        info += QString(" z: %1").arg(QString::number(z, 'f', 2));

    initPopupMenu(supernova, name, i18n("supernova"), info);
}

void KSPopupMenu::initPopupMenu(SkyObject *obj, const QString &name, const QString &type, QString info,
                                bool showDetails, bool showObsList, bool showFlags)
{
    KStarsData *data = KStarsData::Instance();
    SkyMap *map      = SkyMap::Instance();

    clear();
    bool showLabel = (name != i18n("star") && !name.isEmpty());
    QString Name = name;

    if (Name.isEmpty())
        Name = i18n("Empty sky");

    addFancyLabel(Name);
    addFancyLabel(type);
    addFancyLabel(info);
    addFancyLabel(KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(obj));

    //Insert Rise/Set/Transit labels
    SkyObject *o = obj->clone();
    addSeparator();
    addFancyLabel(riseSetTimeLabel(o, true), -2);
    addFancyLabel(riseSetTimeLabel(o, false), -2);
    addFancyLabel(transitTimeLabel(o), -2);
    addSeparator();
    delete o;

    // Show 'Select this object' item when in object pointing mode and when obj is not empty sky
    if (map->isInObjectPointingMode() && obj->type() != 21)
    {
        addAction(i18n("Select this object"), map, SLOT(slotObjectSelected()));
    }

    //Insert item for centering on object
    addAction(i18n("Center && Track"), map, SLOT(slotCenter()));

    if (showFlags)
    {
        //Insert actions for flag operations
        initFlagActions(obj);
    }

    //Insert item for measuring distances
    //FIXME: add key shortcut to menu items properly!
    addAction(i18n("Angular Distance To...            ["), map, SLOT(slotBeginAngularDistance()));
    addAction(i18n("Starhop from here to...            "), map, SLOT(slotBeginStarHop()));

    //Insert item for Showing details dialog
    if (showDetails)
        addAction(i18nc("Show Detailed Information Dialog", "Details"), map, SLOT(slotDetail()));

    addAction(i18n("Copy Coordinates"), map, SLOT(slotCopyCoordinates()));

    //Insert "Add/Remove Label" item
    if (showLabel)
    {
        if (map->isObjectLabeled(obj))
        {
            addAction(i18n("Remove Label"), map, SLOT(slotRemoveObjectLabel()));
        }
        else
        {
            addAction(i18n("Attach Label"), map, SLOT(slotAddObjectLabel()));
        }
    }
    // Should show observing list
    if (showObsList)
    {
        if (data->observingList()->contains(obj))
            addAction(i18n("Remove From Observing WishList"), data->observingList(), SLOT(slotRemoveObject()));
        else
            addAction(i18n("Add to Observing WishList"), data->observingList(), SLOT(slotAddObject()));
    }
    // Should we show trail actions
    TrailObject *t = dynamic_cast<TrailObject *>(obj);
    if (t)
    {
        if (t->hasTrail())
            addAction(i18n("Remove Trail"), map, SLOT(slotRemovePlanetTrail()));
        else
            addAction(i18n("Add Trail"), map, SLOT(slotAddPlanetTrail()));
    }

    addAction(i18n("Simulate eyepiece view"), map, SLOT(slotEyepieceView()));

    addSeparator();
    if (obj->isSolarSystem() &&
        obj->type() !=
            SkyObject::COMET) // FIXME: We now have asteroids -- so should this not be isMajorPlanet() || Pluto?
    {
        addAction(i18n("View in XPlanet"), map, SLOT(slotStartXplanetViewer()));
    }
    addSeparator();
    addINDI();

    addAction(i18n("View in What's Interesting"), this, SLOT(slotViewInWI()));


}

void KSPopupMenu::initFlagActions(SkyObject *obj)
{
    KStars *ks = KStars::Instance();

    QList<int> flags = ks->data()->skyComposite()->flags()->getFlagsNearPix(obj, 5);

    if (flags.isEmpty())
    {
        // There is no flag around clicked SkyObject
        addAction(i18n("Add flag..."), ks->map(), SLOT(slotAddFlag()));
    }

    else if (flags.size() == 1)
    {
        // There is only one flag around clicked SkyObject
        addAction(i18n("Edit flag"), this, SLOT(slotEditFlag()));
        addAction(i18n("Delete flag"), this, SLOT(slotDeleteFlag()));

        m_CurrentFlagIdx = flags.first();
    }

    else
    {
        // There are more than one flags around clicked SkyObject - we need to create submenus
        QMenu *editMenu   = new QMenu(i18n("Edit flag..."), KStars::Instance());
        QMenu *deleteMenu = new QMenu(i18n("Delete flag..."), KStars::Instance());

        connect(editMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotEditFlag(QAction*)));
        connect(deleteMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotDeleteFlag(QAction*)));

        if (m_EditActionMapping)
        {
            delete m_EditActionMapping;
        }

        if (m_DeleteActionMapping)
        {
            delete m_DeleteActionMapping;
        }

        m_EditActionMapping   = new QHash<QAction *, int>;
        m_DeleteActionMapping = new QHash<QAction *, int>;

        foreach (int idx, flags)
        {
            QIcon flagIcon(QPixmap::fromImage(ks->data()->skyComposite()->flags()->image(idx)));

            QString flagLabel = ks->data()->skyComposite()->flags()->label(idx);
            if (flagLabel.size() > 35)
            {
                flagLabel = flagLabel.left(35);
                flagLabel.append("...");
            }

            QAction *editAction = new QAction(flagIcon, flagLabel, ks);
            editAction->setIconVisibleInMenu(true);
            editMenu->addAction(editAction);
            m_EditActionMapping->insert(editAction, idx);

            QAction *deleteAction = new QAction(flagIcon, flagLabel, ks);
            deleteAction->setIconVisibleInMenu(true);
            deleteMenu->addAction(deleteAction);
            m_DeleteActionMapping->insert(deleteAction, idx);
        }

        addMenu(editMenu);
        addMenu(deleteMenu);
    }
}

void KSPopupMenu::addLinksToMenu(SkyObject *obj, bool showDSS)
{
    KStars *ks = KStars::Instance();
    QString sURL;
    QStringList::ConstIterator itList, itTitle, itListEnd;

    itList    = obj->ImageList().constBegin();
    itTitle   = obj->ImageTitle().constBegin();
    itListEnd = obj->ImageList().constEnd();
    if (!obj->ImageList().isEmpty())
    {
        QMenu *imageLinkSubMenu = new QMenu();
        imageLinkSubMenu->setTitle(i18n("Image Resources"));
        for (; itList != itListEnd; ++itList)
        {
            QString t = QString(*itTitle);
            sURL      = QString(*itList);
            imageLinkSubMenu->addAction(i18nc("Image/info menu item (should be translated)", t.toLocal8Bit()),
                                        ks->map(), SLOT(slotImage()));
            ++itTitle;
        }
        addMenu(imageLinkSubMenu);
    }

    // Look for a custom object
    if (KStarsData::Instance()->skyComposite()->internetResolvedComponent()->hasObject(*obj))
    {
        addAction(i18n("Remove From Local Catalog"), ks->map(), SLOT(slotRemoveCustomObject()));
    }

    if (showDSS)
    {
        addAction(i18nc("Sloan Digital Sky Survey", "Show SDSS Image"), ks->map(), SLOT(slotSDSS()));
        addAction(i18nc("Digitized Sky Survey", "Show DSS Image"), ks->map(), SLOT(slotDSS()));
    }

    if (showDSS)
        addSeparator();

    itList    = obj->InfoList().constBegin();
    itTitle   = obj->InfoTitle().constBegin();
    itListEnd = obj->InfoList().constEnd();

    if (!obj->InfoList().isEmpty())
    {
        QMenu *infoLinkSubMenu = new QMenu();
        infoLinkSubMenu->setTitle(i18n("Information Resources"));
        for (; itList != itListEnd; ++itList)
        {
            QString t = QString(*itTitle);
            sURL      = QString(*itList);
            infoLinkSubMenu->addAction(i18nc("Image/info menu item (should be translated)", t.toLocal8Bit()), ks->map(),
                                       SLOT(slotInfo()));
            ++itTitle;
        }
        addMenu(infoLinkSubMenu);
    }
}

void KSPopupMenu::addINDI()
{
#ifdef HAVE_INDI

    if (INDIListener::Instance()->size() == 0)
        return;

    foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices())
    {
        INDI::BaseDevice *bd = gd->getBaseDevice();

        if (bd == nullptr)
            continue;

        //if (bd->isConnected() == false)
        //    continue;

        QMenu *menuDevice           = nullptr;
        ISD::GDInterface *telescope = nullptr;

        foreach (INDI::Property *pp, gd->getProperties())
        {
            if (pp->getType() != INDI_SWITCH || INDIListener::Instance()->isStandardProperty(pp->getName()) == false
                                             || QString(pp->getName()).startsWith("TELESCOPE_MOTION"))
                continue;

            QSignalMapper *sMapper = new QSignalMapper(this);

            ISwitchVectorProperty *svp = pp->getSwitch();

            for (int i = 0; i < svp->nsp; i++)
            {
                if (menuDevice == nullptr)
                {
                    menuDevice = new QMenu(gd->getDeviceName());
                    addMenu(menuDevice);
                }

                QAction *a = menuDevice->addAction(svp->sp[i].label);

                ISD::GDSetCommand *cmd =
                    new ISD::GDSetCommand(INDI_SWITCH, pp->getName(), svp->sp[i].name, ISS_ON, this);

                sMapper->setMapping(a, cmd);

                connect(a, SIGNAL(triggered()), sMapper, SLOT(map()));

                if (!strcmp(svp->sp[i].name, "SLEW") || !strcmp(svp->sp[i].name, "SYNC") ||
                    !strcmp(svp->sp[i].name, "TRACK"))
                {
                    telescope = INDIListener::Instance()->getDevice(gd->getDeviceName());

                    if (telescope)
                    {
                        QSignalMapper *scopeMapper = new QSignalMapper(this);
                        scopeMapper->setMapping(a, INDI_SEND_COORDS);

                        connect(a, SIGNAL(triggered()), scopeMapper, SLOT(map()));

                        connect(scopeMapper, SIGNAL(mapped(int)), telescope, SLOT(runCommand(int)));
                    }
                }
            }

            connect(sMapper, SIGNAL(mapped(QObject*)), gd, SLOT(setProperty(QObject*)));

            if (menuDevice != nullptr)
                menuDevice->addSeparator();
        }

        if (telescope != nullptr && menuDevice != nullptr)
        {
            if (dynamic_cast<ISD::Telescope*>(telescope)->canCustomPark())
            {
                menuDevice->addSeparator();
                QAction *a = menuDevice->addAction(i18n("Slew && Set As Parking Position"));

                QSignalMapper *scopeMapper = new QSignalMapper(this);
                scopeMapper->setMapping(a, INDI_CUSTOM_PARKING);
                connect(a, SIGNAL(triggered()), scopeMapper, SLOT(map()));
                connect(scopeMapper, SIGNAL(mapped(int)), telescope, SLOT(runCommand(int)));
            }

            menuDevice->addSeparator();

            QAction *a = menuDevice->addAction(i18n("Center Crosshair"));

            QSignalMapper *scopeMapper = new QSignalMapper(this);
            scopeMapper->setMapping(a, INDI_ENGAGE_TRACKING);
            connect(a, SIGNAL(triggered()), scopeMapper, SLOT(map()));
            connect(scopeMapper, SIGNAL(mapped(int)), telescope, SLOT(runCommand(int)));
        }
    }

#endif
}

void KSPopupMenu::addFancyLabel(const QString &name, int deltaFontSize)
{
    if (name.isEmpty())
        return;
    QLabel *label = new QLabel("<b>" + name + "</b>", this);
    label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    if (deltaFontSize != 0)
    {
        QFont font = label->font();
        font.setPointSize(font.pointSize() + deltaFontSize);
        label->setFont(font);
    }
    QWidgetAction *act = new QWidgetAction(this);
    act->setDefaultWidget(label);
    addAction(act);
}

void KSPopupMenu::slotViewInWI()
{
    if (!KStars::Instance()->map()->clickedObject())
        return;
    if (!KStars::Instance()->isWIVisible())
        KStars::Instance()->slotToggleWIView();
    KStars::Instance()->wiView()->inspectSkyObject(KStars::Instance()->map()->clickedObject());
}
