/* 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/testing/test_utils.h"

#include "edge-impulse-sdk/tensorflow/lite/micro/simple_memory_allocator.h"

namespace tflite {
namespace testing {

namespace {
// TODO(b/141330728): Refactor out of test_utils.cc
// The variables below (and the AllocatePersistentBuffer function) are only
// needed for the kernel tests and benchmarks, i.e. where we do not have an
// interpreter object, and the fully featured MicroAllocator.
// Currently, these need to be sufficient for all the kernel_tests. If that
// becomes problematic, we can investigate allowing the arena_size to be
// specified for each call to PopulatContext.
constexpr size_t kArenaSize = 10000;
uint8_t raw_arena_[kArenaSize];
SimpleMemoryAllocator* simple_memory_allocator_ = nullptr;
constexpr size_t kBufferAlignment = 16;

// We store the pointer to the ith scratch buffer to implement the Request/Get
// ScratchBuffer API for the tests. scratch_buffers_[i] will be the ith scratch
// buffer and will still be allocated from within raw_arena_.
constexpr size_t kNumScratchBuffers = 5;
uint8_t* scratch_buffers_[kNumScratchBuffers];
size_t scratch_buffer_count_ = 0;

// Note that the context parameter in this function is only needed to match the
// signature of TfLiteContext::AllocatePersistentBuffer and isn't needed in the
// implementation because we are assuming a single global
// simple_memory_allocator_
TfLiteStatus AllocatePersistentBuffer(TfLiteContext* context, size_t bytes,
                                      void** ptr) {
  TFLITE_DCHECK(simple_memory_allocator_ != nullptr);
  TFLITE_DCHECK(ptr != nullptr);
  *ptr = simple_memory_allocator_->AllocateFromTail(bytes, kBufferAlignment);
  if (*ptr == nullptr) {
    return kTfLiteError;
  }
  return kTfLiteOk;
}

TfLiteStatus RequestScratchBufferInArena(TfLiteContext* context, size_t bytes,
                                         int* buffer_index) {
  TFLITE_DCHECK(simple_memory_allocator_ != nullptr);
  TFLITE_DCHECK(buffer_index != nullptr);

  if (scratch_buffer_count_ == kNumScratchBuffers) {
    TF_LITE_REPORT_ERROR(
        static_cast<ErrorReporter*>(context->impl_),
        "Exceeded the maximum number of scratch tensors allowed (%d).",
        kNumScratchBuffers);
    return kTfLiteError;
  }

  // For tests, we allocate scratch buffers from the tail and keep them around
  // for the lifetime of model. This means that the arena size in the tests will
  // be more than what we would have if the scratch buffers could share memory.
  scratch_buffers_[scratch_buffer_count_] =
      simple_memory_allocator_->AllocateFromTail(bytes, kBufferAlignment);
  TFLITE_DCHECK(scratch_buffers_[scratch_buffer_count_] != nullptr);

  *buffer_index = scratch_buffer_count_++;
  return kTfLiteOk;
}

void* GetScratchBuffer(TfLiteContext* context, int buffer_index) {
  TFLITE_DCHECK(scratch_buffer_count_ <= kNumScratchBuffers);
  if (buffer_index >= scratch_buffer_count_) {
    return nullptr;
  }
  return scratch_buffers_[buffer_index];
}

}  // namespace

uint8_t F2Q(float value, float min, float max) {
  int32_t result = ZeroPointFromMinMax<uint8_t>(min, max) +
                   (value / ScaleFromMinMax<uint8_t>(min, max)) + 0.5f;
  if (result < std::numeric_limits<uint8_t>::min()) {
    result = std::numeric_limits<uint8_t>::min();
  }
  if (result > std::numeric_limits<uint8_t>::max()) {
    result = std::numeric_limits<uint8_t>::max();
  }
  return result;
}

// Converts a float value into a signed eight-bit quantized value.
int8_t F2QS(float value, float min, float max) {
  return F2Q(value, min, max) + std::numeric_limits<int8_t>::min();
}

int32_t F2Q32(float value, float scale) {
  double quantized = value / scale;
  if (quantized > std::numeric_limits<int32_t>::max()) {
    quantized = std::numeric_limits<int32_t>::max();
  } else if (quantized < std::numeric_limits<int32_t>::min()) {
    quantized = std::numeric_limits<int32_t>::min();
  }
  return static_cast<int>(quantized);
}

// TODO(b/141330728): Move this method elsewhere as part clean up.
void PopulateContext(TfLiteTensor* tensors, int tensors_size,
                     ErrorReporter* error_reporter, TfLiteContext* context) {
  simple_memory_allocator_ =
      SimpleMemoryAllocator::Create(error_reporter, raw_arena_, kArenaSize);
  TFLITE_DCHECK(simple_memory_allocator_ != nullptr);
  scratch_buffer_count_ = 0;

  context->tensors_size = tensors_size;
  context->tensors = tensors;
  context->impl_ = static_cast<void*>(error_reporter);
  context->GetExecutionPlan = nullptr;
  context->ResizeTensor = nullptr;
  context->ReportError = ReportOpError;
  context->AddTensors = nullptr;
  context->GetNodeAndRegistration = nullptr;
  context->ReplaceNodeSubsetsWithDelegateKernels = nullptr;
  context->recommended_num_threads = 1;
  context->GetExternalContext = nullptr;
  context->SetExternalContext = nullptr;

  context->AllocatePersistentBuffer = AllocatePersistentBuffer;
  context->RequestScratchBufferInArena = RequestScratchBufferInArena;
  context->GetScratchBuffer = GetScratchBuffer;

  for (int i = 0; i < tensors_size; ++i) {
    if (context->tensors[i].is_variable) {
      ResetVariableTensor(&context->tensors[i]);
    }
  }
}

TfLiteTensor CreateFloatTensor(std::initializer_list<float> data,
                               TfLiteIntArray* dims, bool is_variable) {
  return CreateFloatTensor(data.begin(), dims, is_variable);
}

TfLiteTensor CreateBoolTensor(std::initializer_list<bool> data,
                              TfLiteIntArray* dims, bool is_variable) {
  return CreateBoolTensor(data.begin(), dims, is_variable);
}

TfLiteTensor CreateQuantizedTensor(const uint8_t* data, TfLiteIntArray* dims,
                                   float min, float max, bool is_variable) {
  TfLiteTensor result;
  result.type = kTfLiteUInt8;
  result.data.uint8 = const_cast<uint8_t*>(data);
  result.dims = dims;
  result.params = {ScaleFromMinMax<uint8_t>(min, max),
                   ZeroPointFromMinMax<uint8_t>(min, max)};
  result.allocation_type = kTfLiteMemNone;
  result.bytes = ElementCount(*dims) * sizeof(uint8_t);
  result.is_variable = false;
  return result;
}

TfLiteTensor CreateQuantizedTensor(std::initializer_list<uint8_t> data,
                                   TfLiteIntArray* dims, float min, float max,
                                   bool is_variable) {
  return CreateQuantizedTensor(data.begin(), dims, min, max, is_variable);
}

TfLiteTensor CreateQuantizedTensor(const int8_t* data, TfLiteIntArray* dims,
                                   float min, float max, bool is_variable) {
  TfLiteTensor result;
  result.type = kTfLiteInt8;
  result.data.int8 = const_cast<int8_t*>(data);
  result.dims = dims;
  result.params = {ScaleFromMinMax<int8_t>(min, max),
                   ZeroPointFromMinMax<int8_t>(min, max)};
  result.allocation_type = kTfLiteMemNone;
  result.bytes = ElementCount(*dims) * sizeof(int8_t);
  result.is_variable = is_variable;
  return result;
}

TfLiteTensor CreateQuantizedTensor(std::initializer_list<int8_t> data,
                                   TfLiteIntArray* dims, float min, float max,
                                   bool is_variable) {
  return CreateQuantizedTensor(data.begin(), dims, min, max, is_variable);
}

TfLiteTensor CreateQuantizedTensor(float* data, uint8_t* quantized_data,
                                   TfLiteIntArray* dims, bool is_variable) {
  TfLiteTensor result;
  SymmetricQuantize(data, dims, quantized_data, &result.params.scale);
  result.data.uint8 = quantized_data;
  result.type = kTfLiteUInt8;
  result.dims = dims;
  result.params.zero_point = 128;
  result.allocation_type = kTfLiteMemNone;
  result.bytes = ElementCount(*dims) * sizeof(uint8_t);
  result.is_variable = is_variable;
  return result;
}

TfLiteTensor CreateQuantizedTensor(float* data, int8_t* quantized_data,
                                   TfLiteIntArray* dims, bool is_variable) {
  TfLiteTensor result;
  SignedSymmetricQuantize(data, dims, quantized_data, &result.params.scale);
  result.data.int8 = quantized_data;
  result.type = kTfLiteInt8;
  result.dims = dims;
  result.params.zero_point = 0;
  result.allocation_type = kTfLiteMemNone;
  result.bytes = ElementCount(*dims) * sizeof(int8_t);
  result.is_variable = is_variable;
  return result;
}

TfLiteTensor CreateQuantizedTensor(float* data, int16_t* quantized_data,
                                   TfLiteIntArray* dims, bool is_variable) {
  TfLiteTensor result;
  SignedSymmetricQuantize(data, dims, quantized_data, &result.params.scale);
  result.data.i16 = quantized_data;
  result.type = kTfLiteInt16;
  result.dims = dims;
  result.params.zero_point = 0;
  result.allocation_type = kTfLiteMemNone;
  result.bytes = ElementCount(*dims) * sizeof(int16_t);
  result.is_variable = is_variable;
  return result;
}

TfLiteTensor CreateQuantized32Tensor(const int32_t* data, TfLiteIntArray* dims,
                                     float scale, bool is_variable) {
  TfLiteTensor result;
  result.type = kTfLiteInt32;
  result.data.i32 = const_cast<int32_t*>(data);
  result.dims = dims;
  // Quantized int32 tensors always have a zero point of 0, since the range of
  // int32 values is large, and because zero point costs extra cycles during
  // processing.
  result.params = {scale, 0};
  result.allocation_type = kTfLiteMemNone;
  result.bytes = ElementCount(*dims) * sizeof(int32_t);
  result.is_variable = is_variable;
  return result;
}

TfLiteTensor CreateQuantized32Tensor(std::initializer_list<int32_t> data,
                                     TfLiteIntArray* dims, float scale,
                                     bool is_variable) {
  return CreateQuantized32Tensor(data.begin(), dims, scale, is_variable);
}

}  // namespace testing
}  // namespace tflite
