/***************************************************************************
  qgstessellator.h
  --------------------------------------
  Date                 : July 2017
  Copyright            : (C) 2017 by Martin Dobias
  Email                : wonder dot sk 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 QGSTESSELLATOR_H
#define QGSTESSELLATOR_H

#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgsrectangle.h"

class QgsPolygon;
class QgsMultiPolygon;
class QgsLineString;

#include <QVector>
#include <memory>

/**
 * \ingroup core
 * \brief Tessellates polygons into triangles.
 *
 * It is expected that client code will create the tessellator object, then repeatedly call
 * addPolygon() method that will generate triangles, and finally call data() to get final vertex data.
 *
 * Optionally provides extrusion by adding triangles that serve as walls when extrusion height is non-zero.
 *
 */
class CORE_EXPORT QgsTessellator
{
  public:
    QgsTessellator();

    /**
     * \brief Creates tessellator with a specified origin point of the world (in map coordinates)
     * \deprecated QGIS 4.0. Use the default QgsTessellator() constructor and individual setters instead.
     */
    Q_DECL_DEPRECATED QgsTessellator(
      double originX, double originY, bool addNormals, bool invertNormals = false, bool addBackFaces = false, bool noZ = false, bool addTextureCoords = false, int facade = 3, float textureRotation = 0.0f
    ) SIP_DEPRECATED;

    /**
     * Creates tessellator with a specified \a bounds of input geometry coordinates.
     * This constructor allows the tessellator to map input coordinates to a desirable range for numerical
     * stability during calculations.
     *
     * If \a noZ is TRUE, then a 2-dimensional tessellation only will be performed and all z coordinates will be ignored.
     *
     * \since QGIS 3.10
     * \deprecated QGIS 4.0. Use the default QgsTessellator() constructor and individual setters instead.
     */
    Q_DECL_DEPRECATED QgsTessellator(
      const QgsRectangle &bounds, bool addNormals, bool invertNormals = false, bool addBackFaces = false, bool noZ = false, bool addTextureCoords = false, int facade = 3, float textureRotation = 0.0f
    ) SIP_DEPRECATED;

    /**
     * Sets the origin point of the map.
     * \since QGIS 4.0
     */
    void setOrigin( const QgsVector3D &origin );

    /**
     * Returns the origin point of the map.
     * \since QGIS 4.0
     */
    QgsVector3D origin() const { return mOrigin; }

    /**
     * Sets scaling and the bounds of the input geometry coordinates.
     * \since QGIS 4.0
     */
    void setBounds( const QgsRectangle &bounds );

    /**
     * Sets whether Z values from the input geometries are ignored (TRUE) or not (FALSE).
     * By default, this is FALSE.
     * \since QGIS 4.0
     */
    void setInputZValueIgnored( bool ignore );

    /**
     * Returns whether Z values from the input geometries are ignored (TRUE) or not (FALSE).
     * \since QGIS 4.0
     */
    bool isZValueIgnored() const { return mInputZValueIgnored; }

    /**
     * Sets which faces should be generated during extrusion.
     * \since QGIS 4.0
     */
    void setExtrusionFaces( Qgis::ExtrusionFaces faces );

    /**
     * Returns which faces are generated during extrusion.
     * \since QGIS 4.0
     */
    Qgis::ExtrusionFaces extrusionFaces() const { return mExtrusionFaces; }

    /**
     * Sets the rotation of texture UV coordinates (in degrees).
     * \deprecated QGIS 4.2. Handled in shaders now. No longer has any effect on the texture coordinates.
     */
    Q_DECL_DEPRECATED void setTextureRotation( float rotation ) SIP_DEPRECATED;

    /**
     * Returns the rotation of texture UV coordinates (in degrees).
     * \deprecated QGIS 4.2. Handled in shaders now. No longer has any effect on the texture coordinates.
     */
    Q_DECL_DEPRECATED float textureRotation() const SIP_DEPRECATED { return 0; }

    /**
     * Sets whether texture UV coordinates should be added to the output data.
     *
     * \see hasTextureUVs()
     * \since QGIS 4.0
     */
    void setAddTextureUVs( bool addTextureUVs );

    /**
     * Returns TRUE if texture UV coordinates are being added to the output data.
     *
     * \see setAddTextureUVs()
     * \since QGIS 4.0
     */
    bool hasTextureUVs() const { return mAddTextureCoords; }

    /**
     * Sets whether normals should be added to the output data.
     *
     * \see hasNormals()
     * \since QGIS 4.0
     */
    void setAddNormals( bool addNormals );

    /**
     * Returns TRUE if normals are being added to the output data.
     *
     * \see setAddNormals()
     * \since QGIS 4.0
     */
    bool hasNormals() const { return mAddNormals; }

    /**
     * Sets whether tangents should be added to the output data.
     *
     * \see hasTangents()
     * \since QGIS 4.2
     */
    void setAddTangents( bool addTangents );

    /**
     * Returns TRUE if tangents are being added to the output data.
     *
     * \see setAddTangents()
     * \since QGIS 4.2
     */
    bool hasTangents() const { return mAddTangents; }

    /**
     * Sets whether back faces should be added to the output data.
     *
     * \see hasBackFacesEnabled()
     * \since QGIS 4.0
     */
    void setBackFacesEnabled( bool addBackFaces );

    /**
     * Returns TRUE if back faces are being added to the output data.
     *
     * \see setBackFacesEnabled()
     * \since QGIS 4.0
     */
    bool hasBackFacesEnabled() const { return mAddBackFaces; }

    /**
     * Sets whether normals should be inverted.
     *
     * \see hasInvertedNormals()
     * \since QGIS 4.0
     */
    void setInvertNormals( bool invertNormals );

    /**
     * Returns TRUE if normals are inverted.
     *
     * \see setInvertNormals()
     * \since QGIS 4.0
     */
    bool hasInvertedNormals() const { return mInvertNormals; }

    /**
     * Sets the triangulation algorithm.
     * \since QGIS 4.0
     */
    void setTriangulationAlgorithm( Qgis::TriangulationAlgorithm algorithm );

    /**
     * Returns the algorithm used for triangulation.
     * \since QGIS 4.0
     */
    Qgis::TriangulationAlgorithm triangulationAlgorithm() const { return mTriangulationAlgorithm; }

    /**
     * Sets whether the "up" direction should be the Z axis on output (TRUE),
     * otherwise the "up" direction will be the Y axis (FALSE). The default
     * value is FALSE (to keep compatibility for existing tessellator use cases).
     * \deprecated QGIS 4.2. Has no effect no, outputs are always z-up.
     */
    Q_DECL_DEPRECATED void setOutputZUp( bool zUp ) SIP_DEPRECATED;

    /**
     * Returns whether the "up" direction should be the Z axis on output (TRUE),
     * otherwise the "up" direction will be the Y axis (FALSE). The default
     * value is FALSE (to keep compatibility for existing tessellator use cases).
     * \deprecated QGIS 4.2. Has no effect no, outputs are always z-up.
     */
    Q_DECL_DEPRECATED bool isOutputZUp() const SIP_DEPRECATED { return true; }

    //! Tessellates a triangle and adds its vertex entries to the output data array
    void addPolygon( const QgsPolygon &polygon, float extrusionHeight );

    /**
     * Returns array of triangle vertex data
     *
     * Vertice coordinates are stored as (x, z, -y)
     *
     * \deprecated QGIS 4.0. Use vertexBuffer() in combination with indexBuffer().
     */
    Q_DECL_DEPRECATED QVector<float> data() const SIP_DEPRECATED;

    /**
     * Returns index buffer for the generated points.
     * \since QGIS 4.0
     */
    QByteArray indexBuffer() const;

    /**
     * Returns vertex buffer for the generated points.
     * \since QGIS 4.0
     */
    QByteArray vertexBuffer() const;

    //! Returns the number of vertices stored in the output data array
    int dataVerticesCount() const;

    //! Returns size of one vertex entry in bytes
    int stride() const { return mStride; }

    /**
     * Returns size of one index entry in bytes
     * \since QGIS 4.0
    */
    int indexStride() const { return sizeof( uint32_t ); }

    /**
     * Returns the triangulation as a multipolygon geometry.
     */
    std::unique_ptr< QgsMultiPolygon > asMultiPolygon() const SIP_SKIP;

    /**
     * Returns minimal Z value of the data (in world coordinates)
     * \since QGIS 3.12
     */
    float zMinimum() const { return mZMin; }

    /**
     * Returns maximal Z value of the data (in world coordinates)
     * \since QGIS 3.12
     */
    float zMaximum() const { return mZMax; }

    /**
     * Returns a descriptive error string if the tessellation failed.
     *
     * \since QGIS 3.34
     */
    QString error() const { return mError; }

    /**
     * Returns unique vertex count.
     * \since QGIS 4.0
     */
    int uniqueVertexCount() const;

  private:
    struct VertexPoint
    {
        QVector3D position;
        QVector3D normal;
        QVector4D tangent;

        inline bool operator==( const VertexPoint &other ) const { return position == other.position && normal == other.normal && tangent == other.tangent; }
    };

    friend size_t qHash( const VertexPoint &key, size_t seed )
    {
      return qHashMulti( seed, key.position.x(), key.position.y(), key.position.z(), key.normal.x(), key.normal.y(), key.normal.z(), key.tangent.x(), key.tangent.y(), key.tangent.z(), key.tangent.w() );
    }

    QVector<uint32_t> mIndexBuffer;

    void updateStride();
    void setExtrusionFacesLegacy( int facade );
    void calculateBaseTransform( const QVector3D &pNormal, QMatrix4x4 *base ) const;
    QVector3D applyTransformWithExtrusion( const QVector3D point, float extrusionHeight, QMatrix4x4 *transformMatrix, const QgsPoint *originOffset );
    void addVertex(
      const QVector3D &point,
      const QVector3D &normal,
      const QVector4D &tangent,
      float extrusionHeight,
      QMatrix4x4 *transformMatrix,
      const QgsPoint *originOffset,
      QHash<VertexPoint, unsigned int> *vertexBuffer,
      const size_t &vertexBufferOffset,
      bool isFloor = false
    );
    void addVertex( const QVector3D &point, const QVector3D &normal, const QVector4D &tangent, float extrusionHeight, QMatrix4x4 *transformMatrix, const QgsPoint *originOffset, bool isFloor = false );
    void makeWalls( const QgsLineString &ring, bool ccw, float extrusionHeight );
    void addExtrusionWallQuad( const QVector3D &pt1, const QVector3D &pt2, float height, float u1, float u2 );
    std::vector<QVector3D> generateConstrainedDelaunayTriangles( const QgsPolygon *polygonNew );
    std::vector<QVector3D> generateEarcutTriangles( const QgsPolygon *polygonNew );

    QgsVector3D mOrigin = QgsVector3D( 0, 0, 0 );
    bool mAddNormals = false;
    bool mAddTangents = false;
    bool mInvertNormals = false;
    bool mAddBackFaces = false;
    bool mAddTextureCoords = false;
    QVector<float> mData;
    int mStride = 3 * sizeof( float );
    bool mInputZValueIgnored = false;
    Qgis::ExtrusionFaces mExtrusionFaces = Qgis::ExtrusionFace::Walls | Qgis::ExtrusionFace::Roof;
    Qgis::TriangulationAlgorithm mTriangulationAlgorithm = Qgis::TriangulationAlgorithm::ConstrainedDelaunay;
    float mScale = 1.0f;
    QString mError;

    float mZMin = std::numeric_limits<float>::max();
    float mZMax = -std::numeric_limits<float>::max();
};


#endif // QGSTESSELLATOR_H
