// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_FML_DART_DART_CONVERTER_H_
#define FLUTTER_FML_DART_DART_CONVERTER_H_

#include <memory>
#include <vector>

#include "flutter/fml/mapping.h"
#include "third_party/dart/runtime/include/dart_api.h"
#include "third_party/tonic/converter/dart_converter.h"

namespace tonic {

using DartConverterMapping = std::unique_ptr<fml::Mapping>;

template <>
struct DartConverter<DartConverterMapping> {
  static Dart_Handle ToDart(const DartConverterMapping& val) {
    if (!val) {
      return Dart_Null();
    }

    auto dart_list_handle = Dart_NewListOfTypeFilled(
        ToDartTypeHandle<size_t>(),                 // type
        CreateZeroInitializedDartObject<size_t>(),  // sentinel
        val->GetSize()                              // size
    );

    if (Dart_IsError(dart_list_handle)) {
      FML_LOG(ERROR) << "Error while attempting to allocate a list: "
                     << Dart_GetError(dart_list_handle);
      return dart_list_handle;
    }

    if (val->GetSize() == 0) {
      // Nothing to copy. Just return the zero sized list.
      return dart_list_handle;
    }

    auto result = Dart_ListSetAsBytes(dart_list_handle,   // list
                                      0,                  // offset
                                      val->GetMapping(),  // native array,
                                      val->GetSize()      // length
    );

    if (Dart_IsError(result)) {
      FML_LOG(ERROR) << "Error while attempting to create a Dart list: "
                     << Dart_GetError(result);
      return result;
    }

    return dart_list_handle;
  }

  static void SetReturnValue(Dart_NativeArguments args,
                             const DartConverterMapping& val) {
    Dart_SetReturnValue(args, ToDart(val));
  }

  static DartConverterMapping FromDart(Dart_Handle dart_list) {
    if (Dart_IsNull(dart_list)) {
      return nullptr;
    }

    if (Dart_IsError(dart_list)) {
      FML_LOG(ERROR) << "Cannot convert an error handle to a list: "
                     << Dart_GetError(dart_list);
      return nullptr;
    }

    if (!Dart_IsList(dart_list)) {
      FML_LOG(ERROR) << "Dart handle was not a list.";
      return nullptr;
    }

    intptr_t length = 0;
    auto handle = Dart_ListLength(dart_list, &length);

    if (Dart_IsError(handle)) {
      FML_LOG(ERROR) << "Could not get the length of the Dart list: "
                     << Dart_GetError(handle);
      return nullptr;
    }

    if (length == 0) {
      // Return a valid zero sized mapping.
      return std::make_unique<fml::NonOwnedMapping>(nullptr, 0);
    }

    auto mapping_buffer = ::malloc(length);

    if (!mapping_buffer) {
      FML_LOG(ERROR)
          << "Out of memory while attempting to allocate a mapping of size: "
          << length;
      return nullptr;
    }

    auto mapping = std::make_unique<fml::NonOwnedMapping>(
        static_cast<const uint8_t*>(mapping_buffer), length,
        [](const uint8_t* data, size_t size) {
          ::free(const_cast<uint8_t*>(data));
        });

    handle = Dart_ListGetAsBytes(
        dart_list,                              // list
        0,                                      // offset
        static_cast<uint8_t*>(mapping_buffer),  // native array
        length                                  // length
    );

    if (Dart_IsError(handle)) {
      FML_LOG(ERROR) << "Could not copy Dart list to native buffer: "
                     << Dart_GetError(handle);
      return nullptr;
    }

    return mapping;
  }
};

}  // namespace tonic

#endif  // FLUTTER_FML_DART_DART_CONVERTER_H_
