/*
 *******************************************************************************
 *
 *  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    pointAttribute.hpp
 * \author  RIEGL LMS GmbH, Austria
 * \brief   Point attribute description
 * \version 2015-10-14/AW: Initial version
 * \version 2016-11-28/AW: Compression options added (#2423)
 * \version 2016-12-20/AW: New functions to load/save settings from/to JSON
 * \version 2017-03-22/AW: Point attribute scale factor added (#2552)
 * \version 2017-03-28/AW: Documentation of JSON load/save functions updated
 * \version 2017-11-09/AW: New function to suggest buffer data type (#2587)
 * \version 2017-11-24/AW: Constructors declared as "explicit" (#2825)
 * \version 2018-03-09/AW: New attribute property "invalid value" added (#3047)
 * \version 2018-06-22/AW: Attribute length type changed to uint32 (#3117)
 * \version 2018-07-05/AW: Add string conversion operator function
 * \version 2019-01-21/AW: New attribute property "lod settings" added
 * \version 2019-02-15/AW: Fix C++ API wrapper of PointAttribute class
 * \version 2020-02-21/AW: Class 'PointAttribute' is now context-free (#3544)
 * \version 2022-01-26/AW: Add optional point attribute tags (#4128)
 * \version 2022-03-18/AW: Add optional list of named values (#4128)
 * \version 2023-10-04/AW: Add attribute definition string size limits (#4790)
 *
 *******************************************************************************
 */

#ifndef RIEGL_RDB_POINTCLOUD_POINTATTRIBUTE_HPP
#define RIEGL_RDB_POINTCLOUD_POINTATTRIBUTE_HPP

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

#include <map>
#include <limits>
#include <string>
#include <cstdlib>
#include <cstdint>

#include "riegl/rdb/context.hpp"
#include "riegl/rdb/pointcloud/dataTypes.hpp"

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

namespace riegl {
namespace rdb {
namespace pointcloud {

//---< CLASS PointAttribute >---------------------------------------------------
/*!
 * \brief Point attribute description
 *
 * This class describes a point attribute. The database uses this
 * information for internal attribute representation and compression.
 *
 * While the name is a unique identifier, the description holds some
 * text that client programs might display the user in some way. Also
 * the physical unit is not used by the database but can be presented
 * to the user (see PointAttribute::unitSymbol).
 *
 * To avoid point attribute name conflicts, the names (not the titles)
 * shall contain a namespace prefix. Namespace and attribute name are
 * separated by a dot (".", e.g. "riegl.xyz"). The default namespace
 * "riegl" is used if no namespace is given.
 *
 * \remarks
 *
 * If the attribute is a vector (i.e. length > 1), then you might append
 * a zero-based vector element index to the attribute name when inserting,
 * updating or selecting points. Example: use "rgb[0]" to access the red
 * color component (0), the green (1) and blue (2) components are not read
 * or modified in this case.
 *
 * Furthermore, the minimum, maximum and default values are applied to all
 * elements of vectors and the vector length must be in the range [1..100000].
 *
 * PointAttribute::defaultValue is returned when reading a point attribute
 * that has never been written before. The value must be between minimum and
 * maximum (both inclusive).
 *
 * PointAttribute::invalidValue may be used to define a value that represents
 * an invalid/undefined/unknown value. The value must be between minimum and
 * maximum (both inclusive) and must be a multiple of the resolution value.
 * The value may be equal to the default value and you may use "NaN" (not a
 * number) to signal that there is no "invalid value".
 *
 * \note
 *
 * Attribute names may only contain following ASCII characters:
 *
 *   - a-z
 *   - A-Z
 *   - 0-9
 *   - .
 *   - _
 *
 * Attribute title, description and unit symbol may contain any UTF-8 character.
 *
 * \see riegl::rdb::pointcloud::PointAttributes
 */
class PointAttribute
{
public:
    /*!
     * \brief Data Storage Class
     *
     * The storage class gives a hint about how often a point attribute
     * is expected to change. This might be helpful for the database to
     * optimize data structures for speed or file size.
     */
    enum StorageClass
    {
        CONSTANT = 1, //!< value cannot be changed
        VARIABLE = 2, //!< value can change from time to time
        DYNAMIC  = 3  //!< value is likely to be changed often
    };

    /*!
     * \brief Data Compression Options
     *
     * The data of all point attributes is compressed with the settings defined
     * during the database creation (see class CreateSettings). Additionally,
     * the data may be pre-processed using one or more of the following methods:
     */
    enum CompressionOptions
    {
        DEFAULT       = 0, //!< nothing special, just use default compression algorithm
        DELTA         = 1, //!< calculate differences between two consecutive values
        SHUFFLE       = 2, //!< shuffle bytes of point attribute values
        DELTA_SHUFFLE = 3  //!< calculate differences and shuffle bytes
    };

    std::string   name;               //!< unique attribute name (for queries) \see details of class PointAttribute, string size limits: [1, 100]
    std::string   title;              //!< attribute title (for display), string size limits: [1, 100]
    std::string   tags;               //!< attribute tags (comma separated, e.g. "position, transform"), string size limits: [0, 5000]
    std::string   description;        //!< attribute description (for display), string size limits: [0, 5000]
    std::string   unitSymbol;         //!< physical unit symbol (e.g. "m", "rad", "K"), string size limits: [0, 100]
    std::uint32_t length;             //!< number of dimensions/elements (1: scalar, >1: vector, e.g. 3 for point coordinates)
    double        resolution;         //!< expected value resolution
    double        minimumValue;       //!< theoretical minimum value
    double        maximumValue;       //!< theoretical maximum value
    double        defaultValue;       //!< default value (minimum <= default <= maximum)
    double        invalidValue;       //!< invalid value (minimum <= invalid <= maximum, use "not-a-number" if there is no invalid value)
    std::string   namedValues;        //!< list of VALUE=NAME pairs separated by a single line feed character (LF, ASCII 0x0A), string size limits: [0, 1000000]
    std::uint8_t  storageClass;       //!< storage class \see enum StorageClass
    std::uint8_t  compressionOptions; //!< options additional to default compression

    /*!
     * \brief Level of detail settings
     *
     * This field defines the method to be used to generate level of detail
     * data (LOD) for this point attribute. Depending on the LOD mode defined
     * during database creation (see CreateSettings::LodMode), several LOD
     * methods are available. A list of all methods and their settings can
     * be found in file "/manual/riegl_rdb_lod_methods.json" in the RDB SDK.
     *
     * String size limits: [0, 50000]
     */
    std::string lodSettings;

    /*!
     * \brief optional scale factor applied to real numbers (i.e. resolution not equal to 1.0)
     *
     *  - reading points: resultValue = originalValue * scaleFactor
     *  - writing points: storedValue = givenValue / scaleFactor
     */
    double scaleFactor;

    /*!
     * \brief Map of VALUE=NAME pairs
     *
     * \see namedValues
     * \see getNamedValues()
     * \see setNamedValues()
     */
    typedef std::map<long long int, std::string> NamedValuesMap;

public:
    ~PointAttribute();

    /*!
     * \brief Default constructor
     *
     * All properties are set to default values.
     */
    explicit PointAttribute(
        const std::string   &name               = "none",
        const std::string   &title              = "none",
        const std::string   &description        = "",
        const std::string   &unitSymbol         = "",
        const std::uint32_t &length             = 0,
        const double        &resolution         = 1.0,
        const double        &minimumValue       = 0.0,
        const double        &maximumValue       = 0.0,
        const double        &defaultValue       = 0.0,
        const std::uint8_t  &storageClass       = CONSTANT,
        const std::uint8_t  &compressionOptions = DEFAULT,
        const double        &scaleFactor        = 1.0,
        const double        &invalidValue       = std::numeric_limits<double>::quiet_NaN(),
        const std::string   &lodSettings        = "default",
        const std::string   &tags               = "",
        const std::string   &namedValues        = ""
    );

    /// \deprecated since 2.2.3 - use the context-free constructors instead
    explicit PointAttribute(riegl::rdb::Context &context);

    /*!
     * \brief Copy constructor
     *
     * All properties are copied from the given attribute object.
     */
    PointAttribute(const PointAttribute &attribute);

    /*!
     * \brief Assignment operator
     *
     * All properties are copied from the given attribute object.
     */
    PointAttribute& operator=(const PointAttribute &attribute);

    //__________________________________________________________________________
    /*!
     * \brief Copy assignment
     *
     * This function is mainly for backward compatibility reasons. You may
     * also use the copy constructor or copy assignment operator instead.
     */
    void assign(PointAttribute &target) const
    {
        target = *this;
    }

    //__________________________________________________________________________
    /*!
     * \brief Get attribute name and vector index from attribute descriptor
     *
     * This function decodes the given attribute descriptor into attribute name and attribute
     * vector index. The optional vector index is enclosed in square brackets and follows the
     * point attribute name. If no vector index is given, then index -1 is returned. If the
     * vector index cannot be interpreted as a single positive integer value, then an empty
     * name and index -2 is returned.
     *
     * Examples:
     *   descriptor | name  | index
     *  ------------|-------|-------
     *    "xyz"     | "xyz" |  -1
     *    "xyz[0]"  | "xyz" |   0
     *    "rgb[1]"  | "rgb" |   1
     */
    static void decodeDescriptor(
        const std::string &descriptor, //!< [in]  attribute descriptor, e.g. "xyz", "rgb[1]"
        std::string       &name,       //!< [out] attribute name, e.g. "xyz", "rgb"
        std::int32_t      &index       //!< [out] attribute vector index, e.g. -1, 1
    );

    //__________________________________________________________________________
    /*!
     * \brief Get map from named values string
     *
     * This function decodes the `namedValues` string into a map.
     *
     * \see namedValues
     * \see NamedValuesMap
     * \see setNamedValues()
     */
    NamedValuesMap getNamedValues() const;

    //__________________________________________________________________________
    /*!
     * \brief Set named values string from map
     *
     * This function generates the `namedValues` string from a map.
     *
     * \see namedValues
     * \see NamedValuesMap
     * \see getNamedValues()
     */
    void setNamedValues(const NamedValuesMap &map);

    /*!
     * \brief Load settings from JSON string
     *
     * This function parses the given JSON string and applies all available
     * properties - missing properties are silently ignored (i.e. the value
     * remains unchanged). When parsing the JSON string fails, an exception
     * is thrown.
     *
     * Example JSON string:
     *
     *     {
     *         "name": "riegl.reflectance",
     *         "title": "Reflectance",
     *         "tags": "",
     *         "description": "Target surface reflectance",
     *         "unit_symbol": "dB",
     *         "length": 1,
     *         "resolution": 0.01,
     *         "minimum_value": -327.68,
     *         "maximum_value": 327.67,
     *         "default_value": 0.0,
     *         "invalid_value": null,
     *         "named_values": {},
     *         "storage_class": "variable",
     *         "compression_options": "shuffle",
     *         "lod_settings": "default",
     *         "scale_factor": 1.0
     *     }
     */
    void load(const std::string &json);

    /*!
     * \brief Save settings to JSON string
     * \see load()
     */
    std::string save() const;

    /*!
     * \brief Get buffer data type
     *
     * This function suggests a data type that is suitable to
     * construct a buffer for storing values of this attribute.
     *
     * The suggestion is based on the resolution, minimumValue
     * and maximumValue properties, all others are ignored.
     */
    riegl::rdb::pointcloud::DataType dataType() const;

    //! \brief Get attribute name
    operator const std::string&() const { return name; }

private:
    friend class PointAttributeWrapper;
    void *data;
};

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

#endif // RIEGL_RDB_POINTCLOUD_POINTATTRIBUTE_HPP
