// 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.
//
#include "point_cloud/point_attribute.h"

#include <unordered_map>

using std::unordered_map;

// Shortcut for typed conditionals.
template <bool B, class T, class F>
using conditional_t = typename std::conditional<B, T, F>::type;

namespace draco {

PointAttribute::PointAttribute()
    : num_unique_entries_(0), identity_mapping_(false) {}

PointAttribute::PointAttribute(const GeometryAttribute &att)
    : GeometryAttribute(att),
      num_unique_entries_(0),
      identity_mapping_(false) {}

void PointAttribute::Reset(size_t num_attribute_values) {
  if (attribute_buffer_ == nullptr) {
    attribute_buffer_ = std::unique_ptr<DataBuffer>(new DataBuffer());
  }
  const int64_t entry_size = DataTypeLength(data_type()) * components_count();
  attribute_buffer_->Update(nullptr, num_attribute_values * entry_size);
  // Assign the new buffer to the parent attribute.
  ResetBuffer(attribute_buffer_.get(), entry_size, 0);
  num_unique_entries_ = num_attribute_values;
}

AttributeValueIndex::ValueType PointAttribute::DeduplicateValues(
    const GeometryAttribute &in_att) {
  return DeduplicateValues(in_att, AttributeValueIndex(0));
}

AttributeValueIndex::ValueType PointAttribute::DeduplicateValues(
    const GeometryAttribute &in_att, AttributeValueIndex in_att_offset) {
  AttributeValueIndex::ValueType unique_vals = 0;
  switch (in_att.data_type()) {
    // Currently we support only float, uint8, and uint16 arguments.
    case DT_FLOAT32:
      unique_vals = DeduplicateTypedValues<float>(in_att, in_att_offset);
      break;
    case DT_INT8:
      unique_vals = DeduplicateTypedValues<int8_t>(in_att, in_att_offset);
      break;
    case DT_UINT8:
    case DT_BOOL:
      unique_vals = DeduplicateTypedValues<uint8_t>(in_att, in_att_offset);
      break;
    case DT_UINT16:
      unique_vals = DeduplicateTypedValues<uint16_t>(in_att, in_att_offset);
      break;
    case DT_INT16:
      unique_vals = DeduplicateTypedValues<int16_t>(in_att, in_att_offset);
      break;
    case DT_UINT32:
      unique_vals = DeduplicateTypedValues<uint32_t>(in_att, in_att_offset);
      break;
    case DT_INT32:
      unique_vals = DeduplicateTypedValues<int32_t>(in_att, in_att_offset);
      break;
    default:
      return -1;  // Unsupported data type.
  }
  if (unique_vals == 0)
    return -1;  // Unexcpected error.
  return unique_vals;
}

// Helper function for calling UnifyDuplicateAttributes<T,components_count_t>
// with the correct template arguments.
// Returns the number of unique attribute values.
template <typename T>
AttributeValueIndex::ValueType PointAttribute::DeduplicateTypedValues(
    const GeometryAttribute &in_att, AttributeValueIndex in_att_offset) {
  // Select the correct method to call based on the number of attribute
  // components.
  switch (in_att.components_count()) {
    case 1:
      return DeduplicateFormattedValues<T, 1>(in_att, in_att_offset);
    case 2:
      return DeduplicateFormattedValues<T, 2>(in_att, in_att_offset);
    case 3:
      return DeduplicateFormattedValues<T, 3>(in_att, in_att_offset);
    case 4:
      return DeduplicateFormattedValues<T, 4>(in_att, in_att_offset);
    default:
      return 0;
  }
}

template <typename T, int components_count_t>
AttributeValueIndex::ValueType PointAttribute::DeduplicateFormattedValues(
    const GeometryAttribute &in_att, AttributeValueIndex in_att_offset) {
  // We want to detect duplicates using a hash map but we cannot hash floating
  // point numbers directly so bit-copy floats to the same sized integers and
  // hash them.

  // First we need to determine which int type to use (1, 2, 4 or 8 bytes).
  // Note, this is done at compile time using std::conditional struct.
  // Conditional is in form <bool-expression, true, false>. If bool-expression
  // is true the "true" branch is used and vice versa. All at compile time.
  typedef conditional_t<sizeof(T) == 1, uint8_t,
                        conditional_t<sizeof(T) == 2, uint16_t,
                                      conditional_t<sizeof(T) == 4, uint32_t,
                                                    /*else*/ uint64_t>>>
      HashType;

  AttributeValueIndex unique_vals(0);
  typedef std::array<T, components_count_t> AttributeValue;
  typedef std::array<HashType, components_count_t> AttributeHashableValue;
  // Hash map storing index of the first attribute with a given value.
  unordered_map<AttributeHashableValue, AttributeValueIndex,
                HashArray<AttributeHashableValue>>
      value_to_index_map;
  AttributeValue att_value;
  AttributeHashableValue hashable_value;
  IndexTypeVector<AttributeValueIndex, AttributeValueIndex> value_map(
      num_unique_entries_);
  for (AttributeValueIndex i(0); i < num_unique_entries_; ++i) {
    const AttributeValueIndex att_pos = i + in_att_offset;
    att_value = in_att.GetValue<T, components_count_t>(att_pos);
    // Convert the value to hashable type. Bit-copy real attributes to integers.
    memcpy(&(hashable_value[0]), &(att_value[0]), sizeof(att_value));

    // Check if the given attribute value has been used before already.
    auto it = value_to_index_map.find(hashable_value);
    if (it != value_to_index_map.end()) {
      // Duplicated value found. Update index mapping.
      value_map[i] = it->second;
    } else {
      // New unique value.
      // Update the hash map with a new entry pointing to the latest unique
      // vertex index.
      value_to_index_map.insert(
          std::pair<AttributeHashableValue, AttributeValueIndex>(hashable_value,
                                                                 unique_vals));
      // Add the unique value to the mesh builder.
      SetAttributeValue(unique_vals, &att_value);
      // Update index mapping.
      value_map[i] = unique_vals;

      ++unique_vals;
    }
  }
  if (unique_vals == num_unique_entries_)
    return unique_vals.value();  // Nothing has changed.
  if (is_mapping_identity()) {
    // Change identity mapping to the explicit one.
    // The number of points is equal to the number of old unique values.
    SetExplicitMapping(num_unique_entries_);
    // Update the explicit map.
    for (int i = 0; i < num_unique_entries_; ++i) {
      SetPointMapEntry(PointIndex(i), value_map[AttributeValueIndex(i)]);
    }
  } else {
    // Update point to value map using the mapping between old and new values.
    for (PointIndex i(0); i < indices_map_.size(); ++i) {
      SetPointMapEntry(i, value_map[indices_map_[i]]);
    }
  }
  num_unique_entries_ = unique_vals.value();
  return num_unique_entries_;
}

}  // namespace draco
