// Copyright 2016 The Draco Authors.
//
// 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.
//
#ifndef DRACO_ATTRIBUTES_POINT_ATTRIBUTE_H_
#define DRACO_ATTRIBUTES_POINT_ATTRIBUTE_H_

#include <memory>

#include "draco/attributes/attribute_transform_data.h"
#include "draco/attributes/geometry_attribute.h"
#include "draco/core/draco_index_type_vector.h"
#include "draco/core/hash_utils.h"
#include "draco/core/macros.h"
#include "draco/draco_features.h"

namespace draco {

// Class for storing point specific data about each attribute. In general,
// multiple points stored in a point cloud can share the same attribute value
// and this class provides the necessary mapping between point ids and attribute
// value ids.
class PointAttribute : public GeometryAttribute {
 public:
  PointAttribute();
  explicit PointAttribute(const GeometryAttribute &att);

  // Make sure the move constructor is defined (needed for better performance
  // when new attributes are added to PointCloud).
  PointAttribute(PointAttribute &&attribute) = default;
  PointAttribute &operator=(PointAttribute &&attribute) = default;

  // Initializes a point attribute. By default the attribute will be set to
  // identity mapping between point indices and attribute values. To set custom
  // mapping use SetExplicitMapping() function.
  void Init(Type attribute_type, int8_t num_components, DataType data_type,
            bool normalized, size_t num_attribute_values);

  // Copies attribute data from the provided |src_att| attribute.
  void CopyFrom(const PointAttribute &src_att);

  // Prepares the attribute storage for the specified number of entries.
  bool Reset(size_t num_attribute_values);

  size_t size() const { return num_unique_entries_; }
  AttributeValueIndex mapped_index(PointIndex point_index) const {
    if (identity_mapping_) {
      return AttributeValueIndex(point_index.value());
    }
    return indices_map_[point_index];
  }
  DataBuffer *buffer() const { return attribute_buffer_.get(); }
  bool is_mapping_identity() const { return identity_mapping_; }
  size_t indices_map_size() const {
    if (is_mapping_identity()) {
      return 0;
    }
    return indices_map_.size();
  }

  const uint8_t *GetAddressOfMappedIndex(PointIndex point_index) const {
    return GetAddress(mapped_index(point_index));
  }

  // Sets the new number of unique attribute entries for the attribute. The
  // function resizes the attribute storage to hold |num_attribute_values|
  // entries.
  // All previous entries with AttributeValueIndex < |num_attribute_values|
  // are preserved. Caller needs to ensure that the PointAttribute is still
  // valid after the resizing operation (that is, each point is mapped to a
  // valid attribute value).
  void Resize(size_t new_num_unique_entries);

  // Functions for setting the type of mapping between point indices and
  // attribute entry ids.
  // This function sets the mapping to implicit, where point indices are equal
  // to attribute entry indices.
  void SetIdentityMapping() {
    identity_mapping_ = true;
    indices_map_.clear();
  }
  // This function sets the mapping to be explicitly using the indices_map_
  // array that needs to be initialized by the caller.
  void SetExplicitMapping(size_t num_points) {
    identity_mapping_ = false;
    indices_map_.resize(num_points, kInvalidAttributeValueIndex);
  }

  // Set an explicit map entry for a specific point index.
  void SetPointMapEntry(PointIndex point_index,
                        AttributeValueIndex entry_index) {
    DRACO_DCHECK(!identity_mapping_);
    indices_map_[point_index] = entry_index;
  }

  // Same as GeometryAttribute::GetValue(), but using point id as the input.
  // Mapping to attribute value index is performed automatically.
  void GetMappedValue(PointIndex point_index, void *out_data) const {
    return GetValue(mapped_index(point_index), out_data);
  }

#ifdef DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED
  // Deduplicate |in_att| values into |this| attribute. |in_att| can be equal
  // to |this|.
  // Returns -1 if the deduplication failed.
  AttributeValueIndex::ValueType DeduplicateValues(
      const GeometryAttribute &in_att);

  // Same as above but the values read from |in_att| are sampled with the
  // provided offset |in_att_offset|.
  AttributeValueIndex::ValueType DeduplicateValues(
      const GeometryAttribute &in_att, AttributeValueIndex in_att_offset);
#endif

  // Set attribute transform data for the attribute. The data is used to store
  // the type and parameters of the transform that is applied on the attribute
  // data (optional).
  void SetAttributeTransformData(
      std::unique_ptr<AttributeTransformData> transform_data) {
    attribute_transform_data_ = std::move(transform_data);
  }
  const AttributeTransformData *GetAttributeTransformData() const {
    return attribute_transform_data_.get();
  }

 private:
#ifdef DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED
  template <typename T>
  AttributeValueIndex::ValueType DeduplicateTypedValues(
      const GeometryAttribute &in_att, AttributeValueIndex in_att_offset);
  template <typename T, int COMPONENTS_COUNT>
  AttributeValueIndex::ValueType DeduplicateFormattedValues(
      const GeometryAttribute &in_att, AttributeValueIndex in_att_offset);
#endif

  // Data storage for attribute values. GeometryAttribute itself doesn't own its
  // buffer so we need to allocate it here.
  std::unique_ptr<DataBuffer> attribute_buffer_;

  // Mapping between point ids and attribute value ids.
  IndexTypeVector<PointIndex, AttributeValueIndex> indices_map_;
  AttributeValueIndex::ValueType num_unique_entries_;
  // Flag when the mapping between point ids and attribute values is identity.
  bool identity_mapping_;

  // If an attribute contains transformed data (e.g. quantized), we can specify
  // the attribute transform here and use it to transform the attribute back to
  // its original format.
  std::unique_ptr<AttributeTransformData> attribute_transform_data_;

  friend struct PointAttributeHasher;
};

// Hash functor for the PointAttribute class.
struct PointAttributeHasher {
  size_t operator()(const PointAttribute &attribute) const {
    GeometryAttributeHasher base_hasher;
    size_t hash = base_hasher(attribute);
    hash = HashCombine(attribute.identity_mapping_, hash);
    hash = HashCombine(attribute.num_unique_entries_, hash);
    hash = HashCombine(attribute.indices_map_.size(), hash);
    if (!attribute.indices_map_.empty()) {
      const uint64_t indices_hash = FingerprintString(
          reinterpret_cast<const char *>(attribute.indices_map_.data()),
          attribute.indices_map_.size());
      hash = HashCombine(indices_hash, hash);
    }
    if (attribute.attribute_buffer_ != nullptr) {
      const uint64_t buffer_hash = FingerprintString(
          reinterpret_cast<const char *>(attribute.attribute_buffer_->data()),
          attribute.attribute_buffer_->data_size());
      hash = HashCombine(buffer_hash, hash);
    }
    return hash;
  }
};

}  // namespace draco

#endif  // DRACO_ATTRIBUTES_POINT_ATTRIBUTE_H_
