/*  Copyright (C) 2013 VATSIM Community / contributors
 *  This Source Code Form is subject to the terms of the Mozilla Public
 *  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef BLACKMISC_COORDINATEGEODETIC_H
#define BLACKMISC_COORDINATEGEODETIC_H

#include "blackmisc/mathvector3dbase.h"
#include "blackmisc/geolatitude.h"
#include "blackmisc/geolongitude.h"
#include "blackmisc/pqlength.h"

namespace BlackMisc
{
    namespace Geo
    {

        /*!
         * Latitude and longitude interface
         * \brief Interface for geodetic ccordinates
         */
        class ICoordinateGeodetic
        {
        public:

            /*!
             * \brief Latitude
             * \return
             */
            virtual const CLatitude &latitude() const = 0;

            /*!
             * \brief Longitude
             * \return
             */
            virtual const CLongitude &longitude() const = 0;

            /*!
             * \brief As string
             * \return
             */
            QString latitudeAsString() const
            {
                return this->latitude().toQString(true);
            }

            /*!
             * \brief As string
             * \return
             */
            QString longitudeAsString() const
            {
                return this->longitude().toQString(true);
            }

        };

        /*!
         * \brief Great circle distance between points
         * \param coordinate1
         * \param coordinate2
         * \return
         */
        BlackMisc::PhysicalQuantities::CLength greatCircleDistance(const ICoordinateGeodetic &coordinate1, const ICoordinateGeodetic &coordinate2);

        /*!
         * \brief Geodetic coordinate
         */
        class CCoordinateGeodetic : public CValueObject, public ICoordinateGeodetic
        {
        private:
            BLACK_ENABLE_TUPLE_CONVERSION(CCoordinateGeodetic)
            BlackMisc::Geo::CLatitude m_latitude; //!< Latitude
            BlackMisc::Geo::CLongitude m_longitude; //!< Longitude
            BlackMisc::PhysicalQuantities::CLength m_height; //!< height

        protected:
            //! \copydoc CValueObject::convertToQString
            virtual QString convertToQString(bool i18n = false) const override;

            //! \copydoc CValueObject::getMetaTypeId
            virtual int getMetaTypeId() const override;

            //! \copydoc CValueObject::isA
            virtual bool isA(int metaTypeId) const override;

            //! \copydoc CValueObject::compareImpl
            virtual int compareImpl(const CValueObject &other) const override;

            //! \copydoc CValueObject::marshallToDbus
            virtual void marshallToDbus(QDBusArgument &argument) const override;

            //! \copydoc CValueObject::unmarshallFromDbus
            virtual void unmarshallFromDbus(const QDBusArgument &argument) override;

        public:
            /*!
             * \brief Default constructor
             */
            CCoordinateGeodetic() : m_latitude(), m_longitude(), m_height() {}

            /*!
             * \brief Constructor by values
             * \param latitude
             * \param longitude
             * \param height
             */
            CCoordinateGeodetic(CLatitude latitude, CLongitude longitude, BlackMisc::PhysicalQuantities::CLength height) :
                m_latitude(latitude), m_longitude(longitude), m_height(height) {}

            /*!
             * \brief Constructor by values
             * \param latitudeDegrees
             * \param longitudeDegrees
             * \param heightMeters
             */
            CCoordinateGeodetic(double latitudeDegrees, double longitudeDegrees, double heightMeters) :
                m_latitude(latitudeDegrees, BlackMisc::PhysicalQuantities::CAngleUnit::deg()), m_longitude(longitudeDegrees, BlackMisc::PhysicalQuantities::CAngleUnit::deg()), m_height(heightMeters, BlackMisc::PhysicalQuantities::CLengthUnit::m()) {}

            //! \copydoc ICoordinateGeodetic::latitude
            virtual const CLatitude &latitude() const override
            {
                return this->m_latitude;
            }

            //! \copydoc ICoordinateGeodetic::longitude
            virtual const CLongitude &longitude() const override
            {
                return this->m_longitude;
            }

            //! \brief Height
            const BlackMisc::PhysicalQuantities::CLength &height() const
            {
                return this->m_height;
            }

            //! \copydoc CValueObject::toQVariant
            virtual QVariant toQVariant() const override
            {
                return QVariant::fromValue(*this);
            }

            //! \brief Switch unit of latitude / longitude
            CCoordinateGeodetic &switchUnit(const BlackMisc::PhysicalQuantities::CAngleUnit &unit)
            {
                this->m_latitude.switchUnit(unit);
                this->m_longitude.switchUnit(unit);
                return *this;
            }

            //! \brief Switch unit of height
            CCoordinateGeodetic &switchUnit(const BlackMisc::PhysicalQuantities::CLengthUnit &unit)
            {
                this->m_height.switchUnit(unit);
                return *this;
            }

            //! \brief Set latitude
            void setLatitude(const CLatitude &latitude)
            {
                this->m_latitude = latitude;
            }

            //! \brief Set longitude
            void setLongitude(const CLongitude &longitude)
            {
                this->m_longitude = longitude;
            }

            //! \brief Set height
            void setHeight(const BlackMisc::PhysicalQuantities::CLength &height)
            {
                this->m_height = height;
            }

            //! \brief Equal operator ==
            bool operator ==(const CCoordinateGeodetic &other) const;

            //! \brief Unequal operator !=
            bool operator !=(const CCoordinateGeodetic &other) const;

            //! \copydoc CValueObject::getValueHash
            virtual uint getValueHash() const override;

            //! \copydoc CValueObject::toJson
            virtual QJsonObject toJson() const override;

            //! \copydoc CValueObject::fromJson
            void fromJson(const QJsonObject &json) override;

            //! \brief Register metadata
            static void registerMetadata();

            //! \brief Members
            static const QStringList &jsonMembers();

            /*!
             * \brief Coordinate by WGS84 position data
             * \param latitudeWgs84
             * \param longitudeWgs84
             * \param height
             * \return
             */
            static CCoordinateGeodetic fromWgs84(const QString &latitudeWgs84, const QString &longitudeWgs84, const BlackMisc::PhysicalQuantities::CLength height = BlackMisc::PhysicalQuantities::CLength());
        };

    } // namespace
} // namespace

BLACK_DECLARE_TUPLE_CONVERSION(BlackMisc::Geo::CCoordinateGeodetic, (o.m_latitude, o.m_longitude, o.m_height))
Q_DECLARE_METATYPE(BlackMisc::Geo::CCoordinateGeodetic)

#endif // guard
