// 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 "draco/compression/point_cloud/point_cloud_decoder.h"

#include "draco/metadata/metadata_decoder.h"

namespace draco {

PointCloudDecoder::PointCloudDecoder()
    : point_cloud_(nullptr),
      buffer_(nullptr),
      version_major_(0),
      version_minor_(0),
      options_(nullptr) {}

Status PointCloudDecoder::DecodeHeader(DecoderBuffer *buffer,
                                       DracoHeader *out_header) {
  constexpr char kIoErrorMsg[] = "Failed to parse Draco header.";
  if (!buffer->Decode(out_header->draco_string, 5)) {
    return Status(Status::IO_ERROR, kIoErrorMsg);
  }
  if (memcmp(out_header->draco_string, "DRACO", 5) != 0) {
    return Status(Status::DRACO_ERROR, "Not a Draco file.");
  }
  if (!buffer->Decode(&(out_header->version_major))) {
    return Status(Status::IO_ERROR, kIoErrorMsg);
  }
  if (!buffer->Decode(&(out_header->version_minor))) {
    return Status(Status::IO_ERROR, kIoErrorMsg);
  }
  if (!buffer->Decode(&(out_header->encoder_type))) {
    return Status(Status::IO_ERROR, kIoErrorMsg);
  }
  if (!buffer->Decode(&(out_header->encoder_method))) {
    return Status(Status::IO_ERROR, kIoErrorMsg);
  }
  if (!buffer->Decode(&(out_header->flags))) {
    return Status(Status::IO_ERROR, kIoErrorMsg);
  }
  return OkStatus();
}

Status PointCloudDecoder::DecodeMetadata() {
  std::unique_ptr<GeometryMetadata> metadata =
      std::unique_ptr<GeometryMetadata>(new GeometryMetadata());
  MetadataDecoder metadata_decoder;
  if (!metadata_decoder.DecodeGeometryMetadata(buffer_, metadata.get())) {
    return Status(Status::DRACO_ERROR, "Failed to decode metadata.");
  }
  point_cloud_->AddMetadata(std::move(metadata));
  return OkStatus();
}

Status PointCloudDecoder::Decode(const DecoderOptions &options,
                                 DecoderBuffer *in_buffer,
                                 PointCloud *out_point_cloud) {
  options_ = &options;
  buffer_ = in_buffer;
  point_cloud_ = out_point_cloud;
  DracoHeader header;
  DRACO_RETURN_IF_ERROR(DecodeHeader(buffer_, &header))
  // Sanity check that we are really using the right decoder (mostly for cases
  // where the Decode method was called manually outside of our main API.
  if (header.encoder_type != GetGeometryType()) {
    return Status(Status::DRACO_ERROR,
                  "Using incompatible decoder for the input geometry.");
  }
  // TODO(ostava): We should check the method as well, but currently decoders
  // don't expose the decoding method id.
  version_major_ = header.version_major;
  version_minor_ = header.version_minor;

  const uint8_t max_supported_major_version =
      header.encoder_type == POINT_CLOUD ? kDracoPointCloudBitstreamVersionMajor
                                         : kDracoMeshBitstreamVersionMajor;
  const uint8_t max_supported_minor_version =
      header.encoder_type == POINT_CLOUD ? kDracoPointCloudBitstreamVersionMinor
                                         : kDracoMeshBitstreamVersionMinor;
  // Check for version compatibility.
  if (version_major_ < 1 || version_major_ > max_supported_major_version) {
    return Status(Status::UNKNOWN_VERSION, "Unknown major version.");
  }
  if (version_major_ == max_supported_major_version &&
      version_minor_ > max_supported_minor_version) {
    return Status(Status::UNKNOWN_VERSION, "Unknown minor version.");
  }
  buffer_->set_bitstream_version(
      DRACO_BITSTREAM_VERSION(version_major_, version_minor_));

  if (bitstream_version() >= DRACO_BITSTREAM_VERSION(1, 3) &&
      (header.flags & METADATA_FLAG_MASK)) {
    DRACO_RETURN_IF_ERROR(DecodeMetadata())
  }
  if (!InitializeDecoder()) {
    return Status(Status::DRACO_ERROR, "Failed to initialize the decoder.");
  }
  if (!DecodeGeometryData()) {
    return Status(Status::DRACO_ERROR, "Failed to decode geometry data.");
  }
  if (!DecodePointAttributes()) {
    return Status(Status::DRACO_ERROR, "Failed to decode point attributes.");
  }
  return OkStatus();
}

bool PointCloudDecoder::DecodePointAttributes() {
  uint8_t num_attributes_decoders;
  if (!buffer_->Decode(&num_attributes_decoders)) {
    return false;
  }
  // Create all attribute decoders. This is implementation specific and the
  // derived classes can use any data encoded in the
  // PointCloudEncoder::EncodeAttributesEncoderIdentifier() call.
  for (int i = 0; i < num_attributes_decoders; ++i) {
    if (!CreateAttributesDecoder(i)) {
      return false;
    }
  }

  // Initialize all attributes decoders. No data is decoded here.
  for (auto &att_dec : attributes_decoders_) {
    if (!att_dec->Init(this, point_cloud_)) {
      return false;
    }
  }

  // Decode any data needed by the attribute decoders.
  for (int i = 0; i < num_attributes_decoders; ++i) {
    if (!attributes_decoders_[i]->DecodeAttributesDecoderData(buffer_)) {
      return false;
    }
  }

  // Create map between attribute and decoder ids.
  for (int i = 0; i < num_attributes_decoders; ++i) {
    const int32_t num_attributes = attributes_decoders_[i]->GetNumAttributes();
    for (int j = 0; j < num_attributes; ++j) {
      int att_id = attributes_decoders_[i]->GetAttributeId(j);
      if (att_id >= attribute_to_decoder_map_.size()) {
        attribute_to_decoder_map_.resize(att_id + 1);
      }
      attribute_to_decoder_map_[att_id] = i;
    }
  }

  // Decode the actual attributes using the created attribute decoders.
  if (!DecodeAllAttributes()) {
    return false;
  }

  if (!OnAttributesDecoded()) {
    return false;
  }
  return true;
}

bool PointCloudDecoder::DecodeAllAttributes() {
  for (auto &att_dec : attributes_decoders_) {
    if (!att_dec->DecodeAttributes(buffer_)) {
      return false;
    }
  }
  return true;
}

const PointAttribute *PointCloudDecoder::GetPortableAttribute(
    int32_t parent_att_id) {
  if (parent_att_id < 0 || parent_att_id >= point_cloud_->num_attributes()) {
    return nullptr;
  }
  const int32_t parent_att_decoder_id =
      attribute_to_decoder_map_[parent_att_id];
  return attributes_decoders_[parent_att_decoder_id]->GetPortableAttribute(
      parent_att_id);
}

}  // namespace draco
