/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.

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 "edge-impulse-sdk/tensorflow/lite/micro/recording_micro_allocator.h"

#include "edge-impulse-sdk/tensorflow/lite/core/api/error_reporter.h"
#include "edge-impulse-sdk/tensorflow/lite/kernels/internal/compatibility.h"
#include "edge-impulse-sdk/tensorflow/lite/micro/compatibility.h"
#include "edge-impulse-sdk/tensorflow/lite/micro/recording_simple_memory_allocator.h"

namespace tflite {

RecordingMicroAllocator::RecordingMicroAllocator(
    RecordingSimpleMemoryAllocator* recording_memory_allocator,
    ErrorReporter* error_reporter)
    : MicroAllocator(recording_memory_allocator, error_reporter),
      recording_memory_allocator_(recording_memory_allocator) {}

RecordingMicroAllocator* RecordingMicroAllocator::Create(
    uint8_t* tensor_arena, size_t arena_size, ErrorReporter* error_reporter) {
  TFLITE_DCHECK(error_reporter != nullptr);

  RecordingSimpleMemoryAllocator* simple_memory_allocator =
      RecordingSimpleMemoryAllocator::Create(error_reporter, tensor_arena,
                                             arena_size);
  TFLITE_DCHECK(simple_memory_allocator != nullptr);

  uint8_t* allocator_buffer = simple_memory_allocator->AllocateFromTail(
      sizeof(RecordingMicroAllocator), alignof(RecordingMicroAllocator));
  RecordingMicroAllocator* allocator = new (allocator_buffer)
      RecordingMicroAllocator(simple_memory_allocator, error_reporter);
  return allocator;
}

RecordedAllocation RecordingMicroAllocator::GetRecordedAllocation(
    RecordedAllocationType allocation_type) const {
  switch (allocation_type) {
    case RecordedAllocationType::kTfLiteTensorArray:
      return recorded_tflite_tensor_array_data_;
    case RecordedAllocationType::kTfLiteTensorArrayQuantizationData:
      return recorded_tflite_tensor_array_quantization_data_;
    case RecordedAllocationType::kTfLiteTensorVariableBufferData:
      return recorded_tflite_tensor_variable_buffer_data_;
    case RecordedAllocationType::kNodeAndRegistrationArray:
      return recorded_node_and_registration_array_data_;
    case RecordedAllocationType::kOpData:
      return recorded_op_data_;
  }
  TF_LITE_REPORT_ERROR(error_reporter(), "Invalid allocation type supplied: %d",
                       allocation_type);
  return RecordedAllocation();
}

const RecordingSimpleMemoryAllocator*
RecordingMicroAllocator::GetSimpleMemoryAllocator() const {
  return recording_memory_allocator_;
}

void RecordingMicroAllocator::PrintAllocations() const {
  TF_LITE_REPORT_ERROR(
      error_reporter(),
      "[RecordingMicroAllocator] Arena allocation total %d bytes",
      recording_memory_allocator_->GetUsedBytes());
  TF_LITE_REPORT_ERROR(
      error_reporter(),
      "[RecordingMicroAllocator] Arena allocation head %d bytes",
      recording_memory_allocator_->GetHeadUsedBytes());
  TF_LITE_REPORT_ERROR(
      error_reporter(),
      "[RecordingMicroAllocator] Arena allocation tail %d bytes",
      recording_memory_allocator_->GetTailUsedBytes());
  PrintRecordedAllocation(RecordedAllocationType::kTfLiteTensorArray,
                          "TfLiteTensor struct", "tensors");
  PrintRecordedAllocation(
      RecordedAllocationType::kTfLiteTensorArrayQuantizationData,
      "TfLiteTensor quantization data", "allocations");
  PrintRecordedAllocation(
      RecordedAllocationType::kTfLiteTensorVariableBufferData,
      "TfLiteTensor variable buffer data", "allocations");
  PrintRecordedAllocation(RecordedAllocationType::kNodeAndRegistrationArray,
                          "NodeAndRegistration struct",
                          "NodeAndRegistration structs");
  PrintRecordedAllocation(RecordedAllocationType::kOpData,
                          "Operator runtime data", "OpData structs");
}

void RecordingMicroAllocator::PrintRecordedAllocation(
    RecordedAllocationType allocation_type, const char* allocation_name,
    const char* allocation_description) const {
  RecordedAllocation allocation = GetRecordedAllocation(allocation_type);
  TF_LITE_REPORT_ERROR(
      error_reporter(),
      "[RecordingMicroAllocator] '%s' used %d bytes with alignment overhead "
      "(requested %d bytes for %d %s)",
      allocation_name, allocation.used_bytes, allocation.requested_bytes,
      allocation.count, allocation_description);
}

TfLiteStatus RecordingMicroAllocator::AllocateTfLiteTensorArray(
    TfLiteContext* context, const SubGraph* subgraph) {
  SnapshotAllocationUsage(recorded_tflite_tensor_array_data_);

  TfLiteStatus status =
      MicroAllocator::AllocateTfLiteTensorArray(context, subgraph);

  RecordAllocationUsage(recorded_tflite_tensor_array_data_);
  recorded_tflite_tensor_array_data_.count = context->tensors_size;
  return status;
}

TfLiteStatus RecordingMicroAllocator::PopulateTfLiteTensorArrayFromFlatbuffer(
    const Model* model, TfLiteContext* context, const SubGraph* subgraph) {
  SnapshotAllocationUsage(recorded_tflite_tensor_array_quantization_data_);

  TfLiteStatus status = MicroAllocator::PopulateTfLiteTensorArrayFromFlatbuffer(
      model, context, subgraph);

  RecordAllocationUsage(recorded_tflite_tensor_array_quantization_data_);
  return status;
}

TfLiteStatus RecordingMicroAllocator::AllocateNodeAndRegistrations(
    const SubGraph* subgraph, NodeAndRegistration** node_and_registrations) {
  SnapshotAllocationUsage(recorded_node_and_registration_array_data_);

  TfLiteStatus status = MicroAllocator::AllocateNodeAndRegistrations(
      subgraph, node_and_registrations);

  RecordAllocationUsage(recorded_node_and_registration_array_data_);
  recorded_node_and_registration_array_data_.count =
      subgraph->operators()->size();
  return status;
}

TfLiteStatus
RecordingMicroAllocator::PrepareNodeAndRegistrationDataFromFlatbuffer(
    const Model* model, const SubGraph* subgraph,
    const MicroOpResolver& op_resolver,
    NodeAndRegistration* node_and_registrations) {
  SnapshotAllocationUsage(recorded_op_data_);

  TfLiteStatus status =
      MicroAllocator::PrepareNodeAndRegistrationDataFromFlatbuffer(
          model, subgraph, op_resolver, node_and_registrations);

  RecordAllocationUsage(recorded_op_data_);
  return status;
}

TfLiteStatus RecordingMicroAllocator::AllocateVariables(
    TfLiteContext* context, const SubGraph* subgraph) {
  SnapshotAllocationUsage(recorded_tflite_tensor_variable_buffer_data_);

  TfLiteStatus status = MicroAllocator::AllocateVariables(context, subgraph);

  RecordAllocationUsage(recorded_tflite_tensor_variable_buffer_data_);
  return status;
}

void RecordingMicroAllocator::SnapshotAllocationUsage(
    RecordedAllocation& recorded_allocation) {
  recorded_allocation.requested_bytes =
      recording_memory_allocator_->GetRequestedBytes();
  recorded_allocation.used_bytes = recording_memory_allocator_->GetUsedBytes();
  recorded_allocation.count = recording_memory_allocator_->GetAllocatedCount();
}

void RecordingMicroAllocator::RecordAllocationUsage(
    RecordedAllocation& recorded_allocation) {
  recorded_allocation.requested_bytes =
      recording_memory_allocator_->GetRequestedBytes() -
      recorded_allocation.requested_bytes;
  recorded_allocation.used_bytes = recording_memory_allocator_->GetUsedBytes() -
                                   recorded_allocation.used_bytes;
  recorded_allocation.count = recording_memory_allocator_->GetAllocatedCount() -
                              recorded_allocation.count;
}

}  // namespace tflite
