/*
 *******************************************************************************
 *
 *  Copyright 2025 RIEGL Laser Measurement Systems
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  SPDX-License-Identifier: Apache-2.0
 *
 *******************************************************************************
 */
/*!
 *******************************************************************************
 *
 * \file    pointAttributes.hpp
 * \author  RIEGL LMS GmbH, Austria
 * \brief   Manage point attributes
 * \version 2015-10-14/AW: Initial version
 * \version 2016-01-04/AW: Added function to duplicate point attribute data
 * \version 2016-01-11/AW: Added function to discard point attribute data
 * \version 2016-11-29/AW: Added function to define a RIEGL point attribute
 * \version 2017-03-23/AW: Added function to modify point attribute details
 * \version 2017-11-09/AW: Added function to merge point attribute details
 * \version 2017-11-24/AW: Constructors declared as "explicit" (#2825)
 * \version 2019-04-15/AW: Added function to query point attribute group (#3342)
 * \version 2020-12-02/AW: put() accepts name, title, description & unit (#3750)
 * \version 2022-09-20/AW: Added function to query attributes of filter (#4349)
 *
 *******************************************************************************
 */

#ifndef RIEGL_RDB_POINTCLOUD_POINTATTRIBUTES_HPP
#define RIEGL_RDB_POINTCLOUD_POINTATTRIBUTES_HPP

//---< INCLUDES >---------------------------------------------------------------

#include <memory>
#include <string>
#include <vector>
#include <cstdlib>

#include "riegl/rdb/pointcloud/dataTypes.hpp"
#include "riegl/rdb/pointcloud/pointcloudData.hpp"
#include "riegl/rdb/pointcloud/pointAttribute.hpp"

//---< NAMESPACE >--------------------------------------------------------------

namespace riegl {
namespace rdb {

class Pointcloud; // forward declaration

namespace pointcloud {

//---< CLASS PointAttributes >--------------------------------------------------
/*!
 * \brief Manage point attributes
 *
 * This class allows to manage point attributes (dimensions).
 *
 * When creating a new database, each point has at least two attributes:
 *
 *  - an unique point identifier (ID)
 *  - the primary attribute specified in CreateSettings::primaryAttribute
 *
 * Both attributes cannot be deleted or modified in any way.
 *
 * The point identifier attribute is always named "id" and is an unsigned 64
 * bit integer number (see DataTypes::UINT64, pointIDType() and pointIDName()).
 *
 * \see riegl::rdb::Pointcloud::pointAttribute()
 */
class PointAttributes
{
public:
    /*!
     * \brief Constructor
     * \note  You cannot create new PointAttributes objects directly,
     *        use riegl::rdb::Pointcloud::pointAttribute() instead.
     */
    explicit PointAttributes(riegl::rdb::PointcloudData* pointcloud);

    /*!
     * \brief Query attribute names
     *
     * This function returns the names of the attributes defined in the
     * database in the order defined by the point attribute group table
     * (details see group() function).
     *
     * \see group()
     */
    std::vector<std::string> list() const;

    /*!
     * \brief Query default attribute names
     * \returns a list of built-in RIEGL default attribute names
     * \note the order of attribute names is undefined
     */
    static std::vector<std::string> listDefault(riegl::rdb::Context &context);

    /*!
     * \brief Query attribute names of filter
     * \returns a list of attribute names used in the filter string
     *
     * __Examples:__
     *
     *     filter: (riegl.reflectance > 0)
     *     result: [riegl.reflectance]
     *
     *     filter: (riegl.id < 100) && (riegl.class == 2)
     *     result: [riegl.id, riegl.class]
     *
     *     filter: (riegl.target_index == riegl.target_count)
     *     result: [riegl.target_index, riegl.target_count]
     *
     *     filter: (riegl.xyz[0] > 0.0) && (riegl.xyz[1] > 0.0)
     *     result: [riegl.xyz]
     */
    static std::vector<std::string> listFiltered(
        const std::string   &filter, //!< [in] filter string
        riegl::rdb::Context &context
    );

    /*!
     * \brief Check if attribute exists
     * \returns true if an attribute with given name exists
     */
    bool exists(
        const std::string &name //!< [in] attribute name
    ) const;

    /*!
     * \brief Add new attribute
     *
     * \note All attribute name related functions are case sensitive
     *       (i.e. "xyz" and "XYZ" are different attributes).
     * \note Attribute names are unique. If an attribute with the same
     *       name already exists in the database, this function fails.
     */
    void add(
        const PointAttribute &attribute //!< [in] attribute information to be added
    );

    /*!
     * \brief Add RIEGL attribute
     *
     * If the given name refers to an built-in RIEGL default point attribute,
     * this function adds the point attribute to the database. Otherwise this
     * function fails.
     *
     * \note All attribute name related functions are case sensitive
     *       (i.e. "xyz" and "XYZ" are different attributes).
     * \note Attribute names are unique. If an attribute with the same
     *       name already exists in the database, this function fails.
     */
    void add(
        const std::string &name //!< [in] name of a RIEGL point attribute
    );

    /*!
     * \brief Query attribute details
     *
     * If the given attribute name could not be found, the function fails.
     */
    PointAttribute get(
        const std::string &name //!< [in] attribute name
    ) const;

    /*!
     * \brief Query attribute group and index
     *
     * Since RDB version 2.2 the point attributes are grouped and sorted for
     * a better overview. The grouping and order is defined by a table which
     * is stored in the meta data entry "riegl.point_attribute_groups". Each
     * database is filled with a default table with RIEGL point attributes.
     *
     * The list() function returns the names of the attributes actually used
     * in the order defined by the table. Function group() returns the group
     * name as well as the table index of a point attribute (index starts at
     * 1 for the first attribute in the first group and goes up to N for the
     * last attribute in the last group).
     *
     * So unless you want to change the order of the point attributes or add
     * your own, it is not necessary to access or change the attribute table.
     *
     * \see list()
     */
    void group(
        const std::string &name,  //!< [in] attribute name
        std::string       &group, //!< [out] attribute group name
        std::uint32_t     &index  //!< [out] attribute index
    ) const;

    /*!
     * \brief Modify attribute details
     *
     * This function allows to modify certain point attribute properties.
     * Thereto the function looks for a point attribute of the given name,
     * compares the given properties with those stored in the database and
     * finally updates the modified properties in the database.
     *
     * \note Only the modification of `name`, `title`, `description`, `tags`,
     *       `unitSymbol`, `scaleFactor` and `lodSettings` is supported.
     *       If a point attribute shall be renamed, the `name` property
     *       must contain the old and the new name separated by " -> ".
     *       Example: To rename `riegl.xyz` to `riegl.xyz_socs`, set
     *       `attribute.name` to `riegl.xyz -> riegl.xyz_socs`.
     */
    void put(
        const PointAttribute &attribute //!< [in] attribute information to be modified
    );

    /*!
     * \brief Query default attribute details
     *
     * This function is similar to get() but instead of returning the attribute
     * details defined in the database, it returns the details of the built-in
     * RIEGL default point attribute.
     *
     * If the given attribute name could not be found, the function fails.
     */
    static PointAttribute getDefault(
        riegl::rdb::Context &context, //!< [in] library context
        const std::string   &name     //!< [in] attribute name
    );

    /*!
     * \brief Query default attribute group and index
     *
     * This function works like group() but returns the group and index from
     * the built-in RIEGL default attribute table.
     */
    static void groupDefault(
        riegl::rdb::Context &context, //!< [in] library context
        const std::string   &name,    //!< [in] attribute name
        std::string         &group,   //!< [out] attribute group name
        std::uint32_t       &index    //!< [out] attribute index
    );

    /*!
     * \brief Merge attribute details
     *
     * This function returns the details (description) of a point attribute by
     * analyzing two or more point clouds. If the attribute details are not the
     * same in all point clouds (e.g. a typo was fixed or the minimum, maximum,
     * resolution or default value has changed), merged details that cover all
     * variants are returned. However, some changes can not be merged, e.g.
     * changes to the vector length, the unit symbol or changes to the minimum,
     * maximum and resolution values that would require unsupported data types
     * (see riegl::rdb::pointcloud::DataType). In this case an exception with
     * error code riegl::rdb::Error::PointAttributeNotMergeable is thrown.
     * It is not required that the point attribute exists in all point clouds.
     *
     * This function might be helpful when merging point clouds, i.e. when
     * reading points from multiple databases and storing them in a single
     * database. In this case one needs to define the point attributes in the
     * target database. If the source databases were generated by different
     * software applications (or different versions of the same application),
     * the details of the same attribute may slightly differ (see above), which
     * makes the definition of the attribute in the target database quite hard.
     * So instead of defining the attributes by using a hard-coded table, one
     * can use this function to generate the attribute description at runtime.
     *
     * __Examples (simplified, fake attributes):__
     *
     *   | Example 1      | Attribute   | Description      | Resolution | Minimum | Maximum |
     *   | -------------- | ----------- | ---------------- |----------: | ------: | ------: |
     *   | Point cloud A  | amplitude   | signal amplitude |       0.01 |    0.00 |  655.35 |
     *   | Point cloud B  | amplitude   | signal amplitude |       0.01 | -327.68 |  327.67 |
     *   | Merged details | amplitude   | signal amplitude |       0.01 | -327.68 |  655.35 |
     *
     *   | Example 2      | Attribute   | Description         | Resolution | Minimum | Maximum |
     *   | -------------- | ----------- | ------------------- | ---------: | ------: | ------: |
     *   | Point cloud A  | temperature | coarse temperature  |       0.50 | -100.00 |  500.00 |
     *   | Point cloud B  | temperature | precise temperature |       0.01 | -100.00 | 1000.00 |
     *   | Merged details | temperature | precise temperature |       0.01 | -100.00 | 1000.00 |
     *
     * In both examples the value range (minimum/maximum) of the attribute has
     * changed, so the result range is the combination (disjunction) of both
     * ranges.
     *
     * In the second example the resolution is different too. The example
     * shows that always the higher resolution (lower value) is returned.
     *
     * If the description text changes, then the result is the text that is
     * stored in the younger database (the database that was created last).
     * In example 2, "Point cloud B" is the younger database.
     */
    static PointAttribute getMerged(
        riegl::rdb::Context                        &context,     //!< [in] library context
        const std::vector<riegl::rdb::Pointcloud*> &pointclouds, //!< [in] list of point clouds
        const std::string                          &name         //!< [in] attribute name
    );

    /*!
     * \brief Merge attribute details
     *
     * Same as the overloaded version above, but instead of merging one point
     * attribute, this function merges all point attributes of all given point
     * clouds.
     */
    static std::vector<PointAttribute> getMerged(
        riegl::rdb::Context                        &context,    //!< [in] library context
        const std::vector<riegl::rdb::Pointcloud*> &pointclouds //!< [in] list of point clouds
    );

    /*!
     * \brief Delete attribute
     *
     * If the given attribute name could not be found, this function does not fail.
     *
     * \note The primary point attribute (see CreateSettings::primaryAttribute)
     *       and the unique point identifier (ID) cannot be deleted.
     */
    void remove(
        const std::string &name //!< [in] attribute name
    );

    /*!
     * \brief Duplicate attribute data
     *
     * Use this function to duplicate the current point attribute *data* (but
     * not the point attribute description). This can e.g. be used to make a
     * backup of point attribute data.
     *
     * \note The source and target attributes must exist and be compatible (i.e.
     *       attribute minimum, maximum, resolution and length are identical).
     *
     * \note The primary point attribute and the point ID attribute cannot be
     *       used as target attribute.
     */
    void duplicate(
        const std::string &source, //!< [in] source attribute name
        const std::string &target  //!< [in] target attribute name
    );

    /*!
     * \brief Discard attribute data
     *
     * Use this function to delete the current point attribute *data* (but
     * not the point attribute description). This is equivalent to a newly
     * created point attribute, i.e. all points will have the default value.
     *
     * \note The primary point attribute (see CreateSettings::primaryAttribute)
     *       and the unique point identifier (ID) data cannot be discarded.
     */
    void discard(
        const std::string &name //!< [in] attribute name
    );

public:
    typedef std::uint64_t PointID;       //!< Point identifier data type
    static  std::string   pointIDName(); //!< Point identifier attribute name
    static  std::string   pointIDUnit(); //!< Point identifier attribute unit
    static  DataType      pointIDType(); //!< Point identifier data type enum

    std::string primaryAttributeName() const; //!< Name of primary point attribute

private:
    riegl::rdb::PointcloudData *data;
};

}}} // namespace riegl::rdb::pointcloud

#endif // RIEGL_RDB_POINTCLOUD_POINTATTRIBUTES_HPP
