/*
    SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
    SPDX-FileCopyrightText: 2021 Valentin Boettcher <hiro at protagon.space; @hiro98:tchncs.de>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once

#include "dms.h"
#include "skyobject.h"
#include "nan.h"
#include "texturemanager.h"
#include <QString>
#include <QByteArray>
#include <QImage>
#include <array>
#include <utility>

class KSPopupMenu;
class KStarsData;
class CatalogComponent;
class CatalogEntryData;
namespace CatalogsDB
{
struct Catalog;
}

/**
 * A simple container object to hold the minimum information for a Deep
 * Sky Object to be drawn on the skymap.  Detailed information about the
 * object (like `Catalog` information) will be loaded from it's birthing
 * database when needed. Based on the original DeepSkyObject by Jason
 * Harris.
 *
 * You shouldn't create a CatalogObject manually!
 *
 * \todo maybe just turn the const members public
 */
class CatalogObject : public SkyObject
{
        friend class AddCatalogObject;

    public:
        using oid = QByteArray;

        /**
         * @param id oid (hash) of the object
         * @param t Type of object
         * @param r Right Ascension
         * @param d Declination
         * @param m magnitude (brightness)
         * @param n Primary name
         * @param lname Long name (common name)
         * @param catalog_identifier a catalog specific identifier
         * @param catalog_id catalog id
         * @param a major axis (arcminutes)
         * @param b minor axis (arcminutes)
         * @param pa position angle (degrees)
         * @param flux integrated flux (optional)
         * @param database_path the path to the birthing database of this
         * object, used to load additional information on-demand
         */
        CatalogObject(oid id = {}, const SkyObject::TYPE t = SkyObject::STAR,
                      const dms &r = dms(0.0), const dms &d = dms(0.0),
                      const float m = NaN::f, const QString &n = QString(),
                      const QString &lname              = QString(),
                      const QString &catalog_identifier = QString(),
                      const int catalog_id = -1, const float a = 0.0, const float b = 0.0,
                      const double pa = 0.0, const float flux = 0,
                      const QString &database_path = "")
            : SkyObject(t, r, d, m, n, catalog_identifier, lname),
              m_catalog_identifier{ catalog_identifier }, m_catalog_id{ catalog_id },
              m_database_path{ database_path }, m_major_axis{ a }, m_minor_axis{ b },
              m_position_angle{ pa }, m_flux{ flux }
        {
            if (id.length() == 0)
                m_object_id = getId();
            else
                m_object_id = std::move(id);
        };

        ~CatalogObject() override = default;

        /**
         * @return the string for the skymap label of the object.
         */
        QString labelString() const override;

        /**
         * Clones the object and returns a pointer to it. This is legacy
         * code.
         *
         * @deprecated do not use in new code
         */
        CatalogObject *clone() const override;

        /**
         * Generates a KStars internal UID from the object id.
         */
        SkyObject::UID getUID() const override;

        /**
         * @return the object's integrated flux, unit value is stored in
         * the custom catalog component.
         */
        inline float flux() const
        {
            return m_flux;
        }

        /**
         * @return the object's major axis length, in arcminutes.
         */
        inline float a() const
        {
            return m_major_axis;
        }

        /**
         * @return the object's minor axis length, in arcminutes.
         */
        inline float b() const
        {
            return m_minor_axis;
        }

        /**
         * @return the object's aspect ratio (MinorAxis/MajorAxis). Returns 1.0
         * if the object's MinorAxis=0.0.
         */
        float e() const;

        /**
         * @return the object's position angle in degrees, measured clockwise from North.
         */
        inline double pa() const override
        {
            return m_position_angle;
        }

        /**
         * Get information about the catalog that this objects stems from.
         *
         * The catalog information will be loaded from the catalog
         * database on demand.
         */
        const CatalogsDB::Catalog getCatalog() const;

        /**
         * Get the id of the catalog this object belongs to.
         */
        int catalogId() const
        {
            return m_catalog_id;
        }

        /**
         * @return the pixel distance for offsetting the object's name label
         */
        double labelOffset() const override;

        /**
         * Update the coordinates and the horizontal coordinates if
         * updateID or updateNumID have changed (global).
         */
        void JITupdate();

        /**
         * Initialize the popup menu for a `CatalogObject`.
         */
        void initPopupMenu(KSPopupMenu *pmenu) override;

        /**
         * \return the catalog specific identifier. (For example UGC ...)
         */

        const QString &catalogIdentifier() const
        {
            return m_catalog_identifier;
        };

        friend bool operator==(const CatalogObject &c1, const CatalogObject &c2)
        {
            return c1.m_object_id == c2.m_object_id;
        }

        friend bool operator!=(const CatalogObject &c1, const CatalogObject &c2)
        {
            return !(c1 == c2);
        }

        /**
         * \returns the unique ID of this object (not the physical ID =
         * m_object_id) by hashing unique properties of the object.
         *
         * This method provides the reference implementation for the oid hash.
         */
        const oid getId() const;
        static const oid getId(const SkyObject::TYPE type, const double ra, const double dec,
                               const QString &name, const QString &catalog_identifier);

        /**
         * \returns the physical object id of the catalogobject
         */
        const oid getObjectId() const
        {
            return m_object_id;
        };

        /**
         * Set the catalog identifier to \p `cat_ident`.
         */
        void setCatalogIdentifier(const QString &cat_ident)
        {
            m_catalog_identifier = cat_ident;
        }

        /**
         * Set the major axis to \p `a`.
         */
        void setMaj(const float a)
        {
            m_major_axis = a;
        }

        /**
         * Set the minor axis to \p `b`.
         */
        void setMin(const float b)
        {
            m_minor_axis = b;
        }

        /**
         * Set the flux to \p `flux`.
         */
        void setFlux(const float flux)
        {
            m_flux = flux;
        }

        /**
         * Set the position angle to \p `pa`.
         */
        void setPA(const double pa)
        {
            m_position_angle = pa;
        }

        /**
         * Set the magnitude of the object.
         */
        void setMag(const double mag)
        {
            SkyObject::setMag(mag);
        };

        /**
         * Load the image for this object.
         */
        void load_image();

        /**
         * Get the image for this object.
         *
         * @returns [has image?, the image]
         */
        inline std::pair<bool, const QImage &> image() const
        {
            return { !m_image.isNull(), m_image };
        }

    private:
        /**
         * The unique physical object identifier (hash).
         */
        oid m_object_id;

        /**
         * A catalog specific identifier.
         */
        QString m_catalog_identifier;

        /**
         * The id of the catalog, the object is originating from.
         *
         * A value of -1 means that the catalog is unknown. This member is
         * not exposed publicly because it is only useful along with
         * `m_database_path`.
         */
        int m_catalog_id;

        /**
         * The database path which this object was loaded from.
         */
        std::reference_wrapper<const QString> m_database_path;

        /**
         * Whether the image for this catalog object has been loaded.
         */
        bool m_image_loaded{ false };

        /**
         * The image for this object (if any).
         *
         * @todo use std::optional
         */
        QImage m_image;

        //@{
        /**
         * Astronical metadata.
         *
         * Those values may make sense depending on the type of the
         * object. A value of `-1` always means: "it doesn't make sense
         * here".
         */
        float m_major_axis;
        float m_minor_axis;

        double m_position_angle;
        float m_flux;
        //@}

        //@{
        /**
         * Update id counters.
         */
        quint64 m_updateID{ 0 };
        quint64 m_updateNumID{ 0 };
        //@}
};
