/*
 *******************************************************************************
 *
 *  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.cpp
 * \author  RIEGL LMS GmbH, Austria
 * \brief   Point attribute description (C++ wrapper code)
 * \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-11-09/AW: New function to suggest buffer data type (#2587)
 * \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 2019-01-21/AW: New attribute property "lod settings" added
 * \version 2019-02-15/AW: Fix C++ API wrapper of PointAttribute class
 * \version 2019-05-16/AW: Fix C++ API wrapper of PointAttribute class (#3366)
 * \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 2025-01-21/AW: Fix multi-threaded access of point attributes (#5472)
 *
 *******************************************************************************
 */

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

#include <memory>
#include <cctype>
#include <cstdlib>
#include <sstream>
#include <algorithm>

#include "riegl/rdb.h"
#include "riegl/rdb.hpp"

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

namespace riegl {
namespace rdb {
namespace pointcloud {

//______________________________________________________________________________
/*!
 * \brief C API wrapper class for PointAttribute
 * \note For C++ wrapper internal use only.
 */
class PointAttributeWrapper
{
public:
    PointAttributeWrapper(): hnd(nullptr)
    {
        // the actual point attribute is created on demand, see class View
    }

    ~PointAttributeWrapper()
    {
        if (hnd) // then delete the point attribute and ignore errors
        {
            ErrorImpl::check(rdb_pointcloud_point_attribute_delete_cf(&hnd), true);
        }
    }

    static void assign(PointAttribute &target, const PointAttribute &source)
    {
        load(target, save(source));
    }

    static void read(PointAttribute &attribute)
    {
        const View self(attribute, false);
        //
        RDBStringWrapper localName;
        RDBStringWrapper localTitle;
        RDBStringWrapper localTags;
        RDBStringWrapper localDescription;
        RDBStringWrapper localUnitSymbol;
        RDBStringWrapper localNamedValues;
        RDBStringWrapper localLodSettings;
        //
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_name_cf(
            self.hnd, &localName.str
        ));                                                                     attribute.name = localName;
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_title_cf(
            self.hnd, &localTitle.str
        ));                                                                     attribute.title = localTitle;
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_tags_cf(
            self.hnd, &localTags.str
        ));                                                                     attribute.tags = localTags;
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_description_cf(
            self.hnd, &localDescription.str
        ));                                                                     attribute.description = localDescription;
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_unit_symbol_cf(
            self.hnd, &localUnitSymbol.str
        ));                                                                     attribute.unitSymbol = localUnitSymbol;
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_length_u32_cf(
            self.hnd, &attribute.length
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_resolution_cf(
            self.hnd, &attribute.resolution
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_minimum_value_cf(
            self.hnd, &attribute.minimumValue
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_maximum_value_cf(
            self.hnd, &attribute.maximumValue
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_default_value_cf(
            self.hnd, &attribute.defaultValue
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_invalid_value_cf(
            self.hnd, &attribute.invalidValue
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_named_values_cf(
            self.hnd, &localNamedValues.str
        ));                                                                     attribute.namedValues = localNamedValues;
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_storage_class_cf(
            self.hnd, &attribute.storageClass
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_compression_options_cf(
            self.hnd, &attribute.compressionOptions
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_lod_settings_cf(
            self.hnd, &localLodSettings.str
        ));                                                                     attribute.lodSettings = localLodSettings;
        ErrorImpl::check(rdb_pointcloud_point_attribute_get_scale_factor_cf(
            self.hnd, &attribute.scaleFactor
        ));
    }

    static void post(const PointAttribute &attribute)
    {
        const View self(attribute, false);
        //
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_name_cf(
            self.hnd, attribute.name.c_str()
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_title_cf(
            self.hnd, attribute.title.c_str()
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_tags_cf(
            self.hnd, attribute.tags.c_str()
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_description_cf(
            self.hnd, attribute.description.c_str()
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_unit_symbol_cf(
            self.hnd, attribute.unitSymbol.c_str()
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_length_u32_cf(
            self.hnd, attribute.length
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_resolution_cf(
            self.hnd, attribute.resolution
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_minimum_value_cf(
            self.hnd, attribute.minimumValue
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_maximum_value_cf(
            self.hnd, attribute.maximumValue
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_default_value_cf(
            self.hnd, attribute.defaultValue
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_invalid_value_cf(
            self.hnd, attribute.invalidValue
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_named_values_cf(
            self.hnd, attribute.namedValues.c_str()
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_storage_class_cf(
            self.hnd, attribute.storageClass
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_compression_options_cf(
            self.hnd, attribute.compressionOptions
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_lod_settings_cf(
            self.hnd, attribute.lodSettings.c_str()
        ));
        ErrorImpl::check(rdb_pointcloud_point_attribute_set_scale_factor_cf(
            self.hnd, attribute.scaleFactor
        ));
    }

    static void load(PointAttribute &attribute, const std::string &json)
    {
        const View self(attribute, true);
        ErrorImpl::check(rdb_pointcloud_point_attribute_json_load_cf(
            self.hnd, json.c_str()
        ));
        read(attribute);
    }

    static std::string save(const PointAttribute &attribute)
    {
        const View self(attribute, false);
        post(attribute);
        RDBStringWrapper result;
        ErrorImpl::check(rdb_pointcloud_point_attribute_json_save_cf(
            self.hnd, &result.str
        ));
        return result;
    }

    static riegl::rdb::pointcloud::DataType dataType(const PointAttribute &attribute)
    {
        const View self(attribute, false);
        post(attribute);
        std::uint32_t result(NONE);
        ErrorImpl::check(rdb_pointcloud_point_attribute_data_type_cf(
            self.hnd, &result
        ));
        return static_cast<riegl::rdb::pointcloud::DataType>(result);
    }

private:
    friend class PointAttribute;
    RDBPointcloudPointAttribute *hnd;

public:
    class View
    {
    public:
        RDBPointcloudPointAttribute* &hnd;

        View(const PointAttribute &attribute, const bool persistent):
            hnd(static_cast<PointAttributeWrapper*>(attribute.data)->hnd),
            tmp_hnd(nullptr),
            persistent(persistent)
        {
            lock = std::make_shared<Lock>(&hnd);

            if (!hnd) // then create a temporary point attribute
            {
                ErrorImpl::check(rdb_pointcloud_point_attribute_new_cf(&tmp_hnd));
                hnd = tmp_hnd;
            }
        }

        ~View()
        {
            if (tmp_hnd && !persistent) // then delete the temporary point attribute
            {
                ErrorImpl::check(rdb_pointcloud_point_attribute_delete_cf(&tmp_hnd), true);
                hnd = nullptr;
            }

            lock.reset();
        }

    private:
        struct Lock
        {
            const void* const obj;

            Lock(const void* const hnd): obj(hnd)
            {
                ErrorImpl::check(rdb_object_lock_cf(obj));
            }

            ~Lock()
            {
                ErrorImpl::check(rdb_object_unlock_cf(obj), true);
            }
        };

        std::shared_ptr<Lock>        lock;
        RDBPointcloudPointAttribute* tmp_hnd;
        const bool                   persistent;
    };
};

//---< PointAttribute::PUBLIC >-------------------------------------------------

PointAttribute::~PointAttribute()
{
    if (data)
    {
        delete static_cast<PointAttributeWrapper*>(data);
    }
}

PointAttribute::PointAttribute(
    const std::string   &name,
    const std::string   &title,
    const std::string   &description,
    const std::string   &unitSymbol,
    const std::uint32_t &length,
    const double        &resolution,
    const double        &minimumValue,
    const double        &maximumValue,
    const double        &defaultValue,
    const std::uint8_t  &storageClass,
    const std::uint8_t  &compressionOptions,
    const double        &scaleFactor,
    const double        &invalidValue,
    const std::string   &lodSettings,
    const std::string   &tags,
    const std::string   &namedValues
):
    name              (name),
    title             (title),
    tags              (tags),
    description       (description),
    unitSymbol        (unitSymbol),
    length            (length),
    resolution        (resolution),
    minimumValue      (minimumValue),
    maximumValue      (maximumValue),
    defaultValue      (defaultValue),
    invalidValue      (invalidValue),
    namedValues       (namedValues),
    storageClass      (storageClass),
    compressionOptions(compressionOptions),
    lodSettings       (lodSettings),
    scaleFactor       (scaleFactor),
    data(new PointAttributeWrapper)
{
}

PointAttribute::PointAttribute(riegl::rdb::Context &context):
    data(new PointAttributeWrapper)
{
    (void)context;
    PointAttributeWrapper::read(*this);
}

PointAttribute::PointAttribute(const PointAttribute &attribute):
    data(new PointAttributeWrapper)
{
    *this = attribute;
}

PointAttribute& PointAttribute::operator=(const PointAttribute &attribute)
{
    PointAttributeWrapper::assign(*this, attribute);
    return *this;
}

void PointAttribute::decodeDescriptor(
    const std::string &descriptor,
    std::string       &name,
    std::int32_t      &index
)
{
    name.resize(descriptor.size() + 10, 0);
    name.resize(std::size_t(rdb_pointcloud_point_attribute_decode_descriptor(
        descriptor.c_str(),
        name.c_str(),
        &index
    )));
}

PointAttribute::NamedValuesMap PointAttribute::getNamedValues() const
{
    NamedValuesMap result;
    std::string akey, aname;
    std::istringstream stream(namedValues + '\n');
    while (std::getline(std::getline(stream, akey, '='), aname, '\n'))
    {
        const auto lhs(std::find_if_not(aname. begin(), aname. end(), ::isspace));
        const auto rhs(std::find_if_not(aname.rbegin(), aname.rend(), ::isspace).base());
        result[std::stoll(akey)] = (lhs != aname.end()) ? std::string(lhs, rhs) : std::string();
    }
    return result;
}

void PointAttribute::setNamedValues(const NamedValuesMap &map)
{
    std::ostringstream result;
    for (auto it = map.begin(); it != map.end(); ++it)
    {
        result << it->first << "=" << it->second << "\n";
    }
    namedValues = result.str();
}

void PointAttribute::load(const std::string &json)
{
    PointAttributeWrapper::load(*this, json);
}

std::string PointAttribute::save() const
{
    return PointAttributeWrapper::save(*this);
}

riegl::rdb::pointcloud::DataType PointAttribute::dataType() const
{
    return PointAttributeWrapper::dataType(*this);
}

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