/***************************************************************************
    qgssensorthingsutils.h
    --------------------
    begin                : November 2023
    copyright            : (C) 2023 by Nyall Dawson
    email                : nyall dot dawson at gmail dot com
 ***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
#ifndef QGSSENSORTHINGSUTILS_H
#define QGSSENSORTHINGSUTILS_H

#include "qgis.h"
#include "qgis_core.h"
#include "qgis_sip.h"

#include <QString>

using namespace Qt::StringLiterals;

class QgsFields;
class QgsFeedback;
class QgsRectangle;
class QgsSensorThingsExpansionDefinition;

/**
 * \ingroup core
 * \brief Utility functions for working with OGC SensorThings API services.
 *
 * \since QGIS 3.36
 */
class CORE_EXPORT QgsSensorThingsUtils
{
  public:
    //! Default page size
    static constexpr int DEFAULT_PAGE_SIZE = 200 SIP_SKIP;

    //! Default limit on number of features fetched
    static constexpr int DEFAULT_FEATURE_LIMIT = 10000 SIP_SKIP;

    //! Default limit on number of expanded features fetched
    static constexpr int DEFAULT_EXPANSION_LIMIT = 100 SIP_SKIP;

    /**
     * Converts a string value to a Qgis::SensorThingsEntity type.
     *
     * Returns Qgis::SensorThingsEntity::Invalid if the string could not be converted to a known entity type.
     */
    static Qgis::SensorThingsEntity stringToEntity( const QString &type );

    /**
     * Converts a Qgis::SensorThingsEntity \a type to a user-friendly translated string.
     *
     * If \a plural is TRUE then a plural string is returned (ie "Things" instead of "Thing").
    */
    static QString displayString( Qgis::SensorThingsEntity type, bool plural = false );

    /**
     * Converts a string value corresponding to a SensorThings entity set to a Qgis::SensorThingsEntity type.
     *
     * Returns Qgis::SensorThingsEntity::Invalid if the string could not be converted to a known entity set type.
    */
    static Qgis::SensorThingsEntity entitySetStringToEntity( const QString &type );

    /**
     * Converts a SensorThings entity set to a SensorThings entity set string.
     *
     * \since QGIS 3.38
    */
    static QString entityToSetString( Qgis::SensorThingsEntity type );

    /**
     * Returns the SensorThings properties which correspond to a specified entity \a type.
     *
     * \since QGIS 3.38
     */
    static QStringList propertiesForEntityType( Qgis::SensorThingsEntity type );

    /**
     * Returns the fields which correspond to a specified entity \a type.
     *
     * Since QGIS 3.42 the \a includeRangeFieldProxies argument can be used to hide the "start"/"end" fields
     * which are proxies for the date time range field types which are not natively supported in QGIS.
     */
    static QgsFields fieldsForEntityType( Qgis::SensorThingsEntity type, bool includeRangeFieldProxies = true );

    /**
     * Returns the fields which correspond to a specified entity \a baseType, expanded
     * using the specified list of \a expandedTypes.
     *
     * \since QGIS 3.38
     */
    static QgsFields fieldsForExpandedEntityType( Qgis::SensorThingsEntity baseType, const QList< Qgis::SensorThingsEntity > &expandedTypes );

    /**
     * Returns the geometry field for a specified entity \a type.
     */
    static QString geometryFieldForEntityType( Qgis::SensorThingsEntity type );

    /**
     * Returns TRUE if the specified entity \a type can have geometry attached.
     */
    static bool entityTypeHasGeometry( Qgis::SensorThingsEntity type );

    /**
     * Returns the geometry type for if the specified entity \a type.
     *
     * If there are no restrictions on the geometry type an ntity can have Qgis::GeometryType::Unknown will be returned.
     *
     * \since QGIS 3.38
     */
    static Qgis::GeometryType geometryTypeForEntity( Qgis::SensorThingsEntity type );

    /**
     * Returns a filter string which restricts results to those matching the specified
     * \a entityType and \a wkbType.
     */
    static QString filterForWkbType( Qgis::SensorThingsEntity entityType, Qgis::WkbType wkbType );

    /**
     * Returns a filter string which restricts results to those within the specified
     * \a extent.
     *
     * The \a extent should always be specified in EPSG:4326.
     *
     * \since QGIS 3.38
     */
    static QString filterForExtent( const QString &geometryField, const QgsRectangle &extent );

    /**
     * Combines a set of SensorThings API filter operators.
     *
     * See https://docs.ogc.org/is/18-088/18-088.html#requirement-request-data-filter
     *
     * \since QGIS 3.38
     */
    static QString combineFilters( const QStringList &filters );

    /**
     * SensorThings service capabilities
     */
    struct ServiceCapabilities
    {
        //! SensorThings API version
        Qgis::SensorThingsVersion version = Qgis::SensorThingsVersion::Version1_1;
        //! Available SensorThings extensions
        Qgis::SensorThingsExtensions availableExtensions;
        //! Available SensorThings entities
        QSet< Qgis::SensorThingsEntity > availableEntities;
    };

    /**
     * Retrieves general service capabilities for a SensorThings server.
     *
     * \since QGIS 4.2
     */
    static ServiceCapabilities determineServiceCapabilities( const QString &uri, QgsFeedback *feedback = nullptr, const QString &authCfg = QString() );

    /**
     * Returns a list of available geometry types for the server at the specified \a uri
     * and entity \a type.
     *
     * This method will block while network requests are made to the server.
     */
    static QList< Qgis::GeometryType > availableGeometryTypes( const QString &uri, Qgis::SensorThingsEntity type, QgsFeedback *feedback = nullptr, const QString &authCfg = QString() );

    /**
     * Returns a list of permissible expand targets for a given base entity \a type.
     *
     * \since QGIS 3.38
     */
    static QList< Qgis::SensorThingsEntity > expandableTargets( Qgis::SensorThingsEntity type );

    /**
     * Returns the cardinality of the relationship between a base entity type and a related entity type.
     *
     * \param baseType base entity type
     * \param relatedType related entity type
     * \param valid will be set to TRUE if a relationship exists between the entity types, or FALSE if no relationship exists
     *
     * \returns relationship cardinality
     *
     * \since QGIS 3.38
     */
    static Qgis::RelationshipCardinality relationshipCardinality( Qgis::SensorThingsEntity baseType, Qgis::SensorThingsEntity relatedType, bool &valid SIP_OUT );

    /**
     * Returns a list of \a expansions as a valid SensorThings API query string, eg "$expand=Locations($orderby=id desc;$top=3;$expand=Datastreams($top=101))".
     *
     * The base entity type for the query must be specified.
     *
     * \since QGIS 3.38
     */
    static QString asQueryString( Qgis::SensorThingsEntity baseType, const QList< QgsSensorThingsExpansionDefinition > &expansions );
};


/**
 * \ingroup core
 * \brief Encapsulates information about how relationships in a SensorThings API service should be expanded.
 *
 * \since QGIS 3.38
 */
class CORE_EXPORT QgsSensorThingsExpansionDefinition
{
  public:
    /**
     * Constructor for QgsSensorThingsExpansionDefinition, targeting the specified child entity type.
     */
    QgsSensorThingsExpansionDefinition(
      Qgis::SensorThingsEntity childEntity = Qgis::SensorThingsEntity::Invalid,
      const QString &orderBy = QString(),
      Qt::SortOrder sortOrder = Qt::SortOrder::AscendingOrder,
      int limit = QgsSensorThingsUtils::DEFAULT_EXPANSION_LIMIT,
      const QString &filter = QString()
    );

    /**
     * Returns an expansion definition for the specified \a entity type, populated with reasonable
     * defaults which make sense for that entity type.
     */
    static QgsSensorThingsExpansionDefinition defaultDefinitionForEntity( Qgis::SensorThingsEntity entity );

    /**
     * Returns TRUE if the definition is valid.
     */
    bool isValid() const;

    /**
     * Returns the target child entity which should be expanded.
     *
     * \see setChildEntity()
     */
    Qgis::SensorThingsEntity childEntity() const;

    /**
    * Sets the target child \a entity which should be expanded.
    *
    * \see childEntity()
    */
    void setChildEntity( Qgis::SensorThingsEntity entity );

    /**
     * Returns the field name to order the expanded child entities by.
     *
     * \see sortOrder()
     * \see setOrderBy()
     */
    QString orderBy() const;

    /**
     * Sets the \a field name to order the expanded child entities by.
     *
     * \see orderBy()
     * \see setSortOrder()
     */
    void setOrderBy( const QString &field );

    /**
     * Returns the sort order for the expanded child entities.
     *
     * \see orderBy()
     * \see setSortOrder()
     */
    Qt::SortOrder sortOrder() const;

    /**
     * Sets the sort order for the expanded child entities.
     *
     * \see setOrderBy()
     * \see sortOrder()
     */
    void setSortOrder( Qt::SortOrder order );

    /**
     * Returns the limit on the number of child features to fetch.
     *
     * Returns -1 if no limit is defined.
     *
     * \see setLimit()
     */
    int limit() const;

    /**
     * Sets the \a limit on the number of child features to fetch.
     *
     * Set to -1 if no limit is desired.
     *
     * \see limit()
     */
    void setLimit( int limit );

    /**
     * Returns the string filter to filter expanded child entities by.
     *
     * \see setFilter()
     */
    QString filter() const;

    /**
     * Returns the string \a filter to filter expanded child entities by.
     *
     * \see filter()
     */
    void setFilter( const QString &filter );

    /**
     * Returns a string encapsulation of the expansion definition.
     *
     * \see fromString()
     */
    QString toString() const;

    /**
     * Returns a QgsSensorThingsExpansionDefinition from a string representation.
     *
     * \see toString()
     */
    static QgsSensorThingsExpansionDefinition fromString( const QString &string );

    /**
     * Returns the expansion as a valid SensorThings API query string, eg "$expand=Observations($orderby=phenomenonTime desc;$top=10)".
     *
     * The parent entity type for the expansion must be specified.
     *
     * Optionally a list of additional query options can be specified for the expansion.
     */
    QString asQueryString( Qgis::SensorThingsEntity parentEntityType, const QStringList &additionalOptions = QStringList() ) const;

    bool operator==( const QgsSensorThingsExpansionDefinition &other ) const;
    bool operator!=( const QgsSensorThingsExpansionDefinition &other ) const;

#ifdef SIP_RUN
    // clang-format off
    SIP_PYOBJECT __repr__();
    % MethodCode
    if ( !sipCpp->isValid() )
    {
      sipRes = PyUnicode_FromString( "<QgsSensorThingsExpansionDefinition: invalid>" );
    }
    else
    {
      QString innerDefinition;
      if ( !sipCpp->orderBy().isEmpty() )
      {
        innerDefinition = u"by %1 (%2)"_s.arg( sipCpp->orderBy(), sipCpp->sortOrder() == Qt::SortOrder::AscendingOrder ? u"asc"_s : u"desc"_s );
      }
      if ( sipCpp->limit() >= 0 )
      {
        if ( !innerDefinition.isEmpty() )
          innerDefinition = u"%1, limit %2"_s.arg( innerDefinition ).arg( sipCpp->limit() );
        else
          innerDefinition = u"limit %1"_s.arg( sipCpp->limit() );
      }
      if ( !sipCpp->filter().isEmpty() )
      {
        if ( !innerDefinition.isEmpty() )
          innerDefinition = u"%1, filter '%2'"_s.arg( innerDefinition ).arg( sipCpp->filter() );
        else
          innerDefinition = u"filter '%1'"_s.arg( sipCpp->filter() );
      }

      QString str = u"<QgsSensorThingsExpansionDefinition: %1%2>"_s.arg( qgsEnumValueToKey( sipCpp->childEntity() ), innerDefinition.isEmpty() ? QString() : ( u" "_s + innerDefinition ) );
      sipRes = PyUnicode_FromString( str.toUtf8().constData() );
    }
    % End
// clang-format on
#endif

    // clang-format off
    private:
    // clang-format on

    Qgis::SensorThingsEntity mChildEntity
    = Qgis::SensorThingsEntity::Invalid;
    QString mOrderBy;
    Qt::SortOrder mSortOrder = Qt::SortOrder::AscendingOrder;
    int mLimit = QgsSensorThingsUtils::DEFAULT_EXPANSION_LIMIT;
    QString mFilter;
};
Q_DECLARE_METATYPE( QgsSensorThingsExpansionDefinition )

#endif // QGSSENSORTHINGSUTILS_H
