/*
 *******************************************************************************
 *
 *  Copyright 2026 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    pointcloud.hpp
 * \author  RIEGL LMS GmbH, Austria
 * \brief   Main point cloud database class
 * \version 2015-10-14/AW: Initial version
 * \version 2016-11-04/AW: Allow to read from multiple nodes at once (#2368)
 * \version 2016-11-08/AW: New "fill query" (class QueryFill) added (#1926)
 * \version 2016-11-14/AW: New "invert query" (class QueryInvert) added (#2406)
 * \version 2016-11-16/AW: New function to output database statistics added
 * \version 2017-03-21/AW: Database management interface added (#2550)
 * \version 2017-08-22/AW: Added function to query database UUID (#2720)
 * \version 2017-11-09/AW: Friend class 'PointAttributes' added
 * \version 2017-11-24/AW: Constructors declared as "explicit" (#2825)
 * \version 2018-05-28/AW: New variant of create() that accepts a schema (#3109)
 * \version 2018-09-20/AW: Fix order of included headers
 * \version 2020-02-24/AW: New function to check if a database is empty (#3566)
 * \version 2020-06-29/AW: Database changelog interface added (#3614)
 * \version 2021-05-07/AW: Class Pointcloud is default constructible (#3887)
 *
 *******************************************************************************
 */

#include "riegl/rdb.hpp"
#ifndef RIEGL_RDB_POINTCLOUD_HPP
#define RIEGL_RDB_POINTCLOUD_HPP

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

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

#include "riegl/rdb/pointcloud/changelog.hpp"
#include "riegl/rdb/pointcloud/dataTypes.hpp"
#include "riegl/rdb/pointcloud/createSettings.hpp"
#include "riegl/rdb/pointcloud/openSettings.hpp"
#include "riegl/rdb/pointcloud/management.hpp"
#include "riegl/rdb/pointcloud/metaData.hpp"
#include "riegl/rdb/pointcloud/pointAttribute.hpp"
#include "riegl/rdb/pointcloud/pointAttributes.hpp"
#include "riegl/rdb/pointcloud/transaction.hpp"
#include "riegl/rdb/pointcloud/transactions.hpp"
#include "riegl/rdb/pointcloud/queryInsert.hpp"
#include "riegl/rdb/pointcloud/queryUpdate.hpp"
#include "riegl/rdb/pointcloud/querySelect.hpp"
#include "riegl/rdb/pointcloud/queryRemove.hpp"
#include "riegl/rdb/pointcloud/queryStat.hpp"
#include "riegl/rdb/pointcloud/queryFill.hpp"
#include "riegl/rdb/pointcloud/queryInvert.hpp"

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

namespace riegl {
namespace rdb {

//---< CLASS Pointcloud >-------------------------------------------------------
/*!
 * \brief Main point cloud database class
 *
 * Use this class to create or open a point cloud database and insert, update
 * or query points.
 *
 * \note All functions of this class throw an Exception of class
 *       riegl::rdb::Error in case of troubles.
 */
class Pointcloud
{
//______________________________________________________________________________
//
//! \name Constructors and destructor
//! \{
public:
    /*!
     * \brief Create Pointcloud instance
     *
     * This creates a new Pointcloud object instance. To actually access or
     * create a point cloud database file, you must call open() or create().
     *
     * Each Pointcloud instance may only be accessed from one thread/process at
     * a time. Opening the same database using different Pointcloud instances
     * (in the same or different thread or process) is allowed.
     *
     * \note The implicit copy-constructor and assignment-operators will yield
     *       to multiple instances pointing to the same database file. This
     *       is okay, as long as you do not intend to use both instances in
     *       different threads simultaneously.
     *
     * \see Pointcloud::create()
     * \see Pointcloud::open()
     */
    explicit Pointcloud(Context context = Context());

    /*!
     * \brief Destroy Pointcloud instance
     *
     * \note If there are multiple Pointcloud instances pointing to the same
     *       file (see constructor notes), then the database file will be
     *       closed when the last Pointcloud instance is deleted.
     */
    ~Pointcloud();
//! \}
//______________________________________________________________________________
//
//! \name Database Access
//! \{
public:
    /*!
     * \brief Create new database
     *
     * This function creates a new (empty) database. If the given
     * database already exists, it will be overwritten (unless it is
     * opened by other clients, in which case the creation will fail).
     * The target folder must exist - it is not created automatically.
     * If the database could not be created, an exception is thrown.
     */
    void create(
        const std::string                &location, //!< [in] database location (filename)
        const pointcloud::CreateSettings &settings  //!< [in] database creation settings
    );

    /*!
     * \brief Create new database
     *
     * \copydoc create()
     *
     * Additionally, all required point attributes and metadata entries as defined
     * by the schema are added to the database. The schema is given in JSON format,
     * details see riegl::rdb::pointcloud::Management::validate(). If 'optionals'
     * is 'true', then also the optional point attributes and metadata entries
     * are added to the database.
     *
     * \note
     *   Only RIEGL default point attributes and metadata entries are supported.
     *   Metadata entries are added but have no value (empty string).
     *
     * \note
     *   If the schema defines a primary point attribute, then it overrides
     *   the primary point attribute defined in the 'settings' parameter.
     *
     * \see riegl::rdb::pointcloud::Management::validate()
     */
    void create(
        const std::string                &location,       //!< [in] database location (filename)
        const pointcloud::CreateSettings &settings,       //!< [in] database creation settings
        const std::string                &schema,         //!< [in] database schema (JSON format)
        const bool                        optionals=false //!< [in] true: include optional items
    );

    /*!
     * \brief Open existing database
     *
     * Open existing database location.
     * If the given database does not exist, an exception is thrown.
     */
    void open(
        const std::string              &location, //!< [in] database location (filename)
        const pointcloud::OpenSettings &settings  //!< [in] database open settings
    );

    /*!
     * \brief Close database
     *
     * Close database file and release all internal resources.
     * This function fails if there are pending transactions.
     *
     * \note Keep in mind to close all queries __before__
     *       you close or delete the database instance!
     */
    void close();

    /*!
     * \brief Check if a database is open
     * \returns true if a database is open
     */
    bool isOpen() const;

    /*!
     * \brief Check if a database is empty
     * \returns true if the database contains no points or no database is open
     */
    bool isEmpty() const;

    /*!
     * \brief Get database file's UUID
     * \returns the Universally Unique Identifier of the database file
     *
     * \note Database files created with rdblib prior version 2.1.2 do
     *       not have a UUID. In this case the "nil" UUID is returned.
     *       If no database is opened, an empty string is returned.
     */
    std::string getUUID() const;

    /*!
     * \brief File statistics and debugging information
     *
     * This function returns statistics and debugging information about
     * the database file which is intended for factory usage only (i.e.
     * the format may change at any time and the content is undocumented).
     */
    std::string inspect(const std::uint8_t format);

    /*!
     * \brief Clear internal data cache
     *
     * This function clears (flushes) the internal data cache and reduces
     * memory consumption as much as possible.
     */
    void clearCache();
//! \}
//______________________________________________________________________________
//
//! \name Database Management
//! \{
public:
    /*!
     * \brief Basic point cloud management interface
     * \see riegl::rdb::pointcloud::Management
     */
          pointcloud::Management &management();
    const pointcloud::Management &management() const;                           //!< \copydoc management()

    /*!
     * \brief Manage point cloud changelog
     * \see riegl::rdb::pointcloud::Changelog
     */
          pointcloud::Changelog &changelog();
    const pointcloud::Changelog &changelog() const;                             //!< \copydoc changelog()

    /*!
     * \brief Manage point cloud meta data
     * \see riegl::rdb::pointcloud::MetaData
     */
          pointcloud::MetaData &metaData();
    const pointcloud::MetaData &metaData() const;                               //!< \copydoc metaData()

    /*!
     * \brief Manage point attributes
     * \see riegl::rdb::pointcloud::PointAttributes
     */
          pointcloud::PointAttributes &pointAttribute();
    const pointcloud::PointAttributes &pointAttribute() const;                  //!< \copydoc pointAttribute()

    /*!
     * \brief Manage point cloud transactions
     * \see riegl::rdb::pointcloud::Transactions
     */
          pointcloud::Transactions &transaction();
    const pointcloud::Transactions &transaction() const;                        //!< \copydoc transaction()
//! \}
//______________________________________________________________________________
//
//! \name Point Queries
//! \{
public:
    /*!
     * \brief Insert points
     *
     * This function creates a new query object that can be used to
     * insert (new) points into the database.
     *
     * \see riegl::rdb::pointcloud::QueryInsert
     */
    pointcloud::QueryInsert insert();

    /*!
     * \brief Update points
     *
     * This function creates a new query object that can be used to
     * update (modify) attributes of existing points.
     *
     * \see riegl::rdb::pointcloud::QueryUpdate
     */
    pointcloud::QueryUpdate update();

    /*!
     * \brief Select points
     *
     * This function creates a new query object that can be used to
     * select (read) attributes of existing points.<br>
     * The optional filter expression can be used to select particular
     * points - if no filter is given, all points will be loaded.<br>
     *
     * __Filter expression operators:__
     *
     *   | operator | meaning                                  |
     *   | :------: | ---------------------------------------- |
     *   |    ==    | equality (left == right)                 |
     *   |    !=    | inequality (left != right)               |
     *   |    <     | less than (left < right)                 |
     *   |    <=    | less than or equal to (left <= right)    |
     *   |    >     | greater than (left > right)              |
     *   |    >=    | greater than or equal to (left >= right) |
     *   |    &&    | conjunction (left && right)              |
     *   |    \|\|  | disjunction (left \|\| right)            |
     *   |    %     | unsigned modulo (left % right)           |
     *
     * __Filter expression syntax:__
     *
     *   - _constant_ = _integer_ | _double_ | _variable_ > ':' > ('minimum' | 'maximum' | 'default' | 'invalid')
     *   - _variable_ = [a-zA-Z] > ([0-9a-zA-Z] | '_' | '.')*
     *   - _operand_ = _constant_ | _variable_ | _expression_
     *   - _expression_ = (_operand_)? > _operator_ > _operand_
     *
     * __Filter expression examples:__
     *
     *   - "" (empty string means "all points")
     *   - "amplitude > 5.0"
     *   - "(reflectance > 0.0) && (selected == 1)"
     *   - "point_count != point_count:invalid"
     *   - "(id % 2) == 0"
     *
     * __Stored filters:__
     *
     *   Filters can also be stored in the metadata as `riegl.stored_filters`.
     *   All activated filters of this list will be applied in addition to the
     *   filter specified by the application (conjunction). To temporarily ignore
     *   (override) all stored filters, insert `"!!"` at the beginning or end of
     *   the filter string (e.g. `"!! riegl.id == 1"` or `"riegl.id == 1 !!"`).
     *
     * \note The resolution of a point attribute is used as a tolerance when comparing
     *       attribute values with constants (i.e. constants are rounded to the nearest
     *       multiple of the attribute resolution before comparison).
     *
     * \see riegl::rdb::pointcloud::QuerySelect
     */
    pointcloud::QuerySelect select(
        const std::string &filter = std::string() //!< [in] optional point filter expression
    );

    /*!
     * \brief Select points by index node
     *
     * \copydetails select()
     *
     * Instead of loading all points of the point cloud this variant just loads
     * the points contained in a single node. See notes about LOD for details.
     */
    pointcloud::QuerySelect select(
        const pointcloud::GraphNode::ID &node,    //!< [in] ID of index graph node
        const std::string &filter = std::string() //!< [in] optional point filter expression
    );

    /*!
     * \brief Select points by index nodes
     *
     * \copydetails select()
     *
     * Instead of loading all points of the point cloud this variant just loads
     * the points contained in the given nodes. See notes about LOD for details.
     */
    pointcloud::QuerySelect select(
        const std::vector<pointcloud::GraphNode::ID> &nodes, //!< [in] IDs of index graph nodes
        const std::string &filter = std::string()            //!< [in] optional point filter expression
    );

    /*!
     * \brief Fill points
     *
     * This function creates a new query object that can be used to
     * set (modify) attributes of existing points.
     *
     * Please have a look at select() for details about the filter expression.
     *
     * \see riegl::rdb::pointcloud::QueryFill
     */
    pointcloud::QueryFill fill(
        const std::string &filter = std::string() //!< [in] optional point filter expression
    );

    /*!
     * \brief Fill points by index node
     *
     * \copydetails fill()
     *
     * Instead of modifying all points of the point cloud this variant just modifies
     * the points contained in a single node. See notes about LOD for details.
     */
    pointcloud::QueryFill fill(
        const pointcloud::GraphNode::ID &node,    //!< [in] ID of index graph node
        const std::string &filter = std::string() //!< [in] optional point filter expression
    );

    /*!
     * \brief Fill points by index nodes
     *
     * \copydetails fill()
     *
     * Instead of modifying all points of the point cloud this variant just modifies
     * the points contained in the given nodes. See notes about LOD for details.
     */
    pointcloud::QueryFill fill(
        const std::vector<pointcloud::GraphNode::ID> &nodes, //!< [in] IDs of index graph nodes
        const std::string &filter = std::string()            //!< [in] optional point filter expression
    );

    /*!
     * \brief Invert points
     *
     * This function creates a new query object that can be used to
     * invert attributes of existing points.
     *
     * Please have a look at select() for details about the filter expression.
     *
     * \see riegl::rdb::pointcloud::QueryInvert
     */
    pointcloud::QueryInvert invert(
        const std::string &filter = std::string() //!< [in] optional point filter expression
    );

    /*!
     * \brief Invert points by index node
     *
     * \copydetails invert()
     *
     * Instead of inverting all points of the point cloud this variant just inverts
     * the points contained in a single node. See notes about LOD for details.
     */
    pointcloud::QueryInvert invert(
        const pointcloud::GraphNode::ID &node,    //!< [in] ID of index graph node
        const std::string &filter = std::string() //!< [in] optional point filter expression
    );

    /*!
     * \brief Invert points by index nodes
     *
     * \copydetails invert()
     *
     * Instead of inverting all points of the point cloud this variant just inverts
     * the points contained in the given nodes. See notes about LOD for details.
     */
    pointcloud::QueryInvert invert(
        const std::vector<pointcloud::GraphNode::ID> &nodes, //!< [in] IDs of index graph nodes
        const std::string &filter = std::string()            //!< [in] optional point filter expression
    );

    /*!
     * \brief Remove points
     *
     * This function creates a new query object that can be used to
     * remove (delete) existing points.
     *
     * \see riegl::rdb::pointcloud::QueryRemove
     */
    pointcloud::QueryRemove remove();

    /*!
     * \brief Query point statistics
     *
     * This function creates a new query object that can be used to
     * get point attribute statistics like minimum and maximum value.
     *
     * \see riegl::rdb::pointcloud::QueryStat
     */
    pointcloud::QueryStat stat();
//! \}
//______________________________________________________________________________
//
private:
    friend class riegl::rdb::pointcloud::PointAttributes;
    std::shared_ptr<riegl::rdb::PointcloudData> data;

#ifdef RIEGL_RDB_POINTCLOUD_IMPLEMENTATION_DETAILS
    RIEGL_RDB_POINTCLOUD_IMPLEMENTATION_DETAILS
#endif
};

}} // namespace riegl::rdb

#endif // RIEGL_RDB_POINTCLOUD_HPP
