// Copyright 2023 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef IREE_HAL_CTS_FILE_TEST_H_
#define IREE_HAL_CTS_FILE_TEST_H_

#include <cstdint>
#include <vector>

#include "iree/base/api.h"
#include "iree/hal/api.h"
#include "iree/hal/cts/cts_test_base.h"
#include "iree/testing/gtest.h"
#include "iree/testing/status_matchers.h"

namespace iree::hal::cts {

using ::testing::ContainerEq;

namespace {
constexpr iree_device_size_t kMinimumAlignment = 128;
}  // namespace

class FileTest : public CTSTestBase<> {
 protected:
  void CreatePatternedDeviceBuffer(iree_device_size_t buffer_size,
                                   uint8_t pattern,
                                   iree_hal_buffer_t** out_buffer) {
    iree_hal_buffer_params_t params = {0};
    params.type = IREE_HAL_MEMORY_TYPE_OPTIMAL_FOR_DEVICE;
    params.usage = IREE_HAL_BUFFER_USAGE_TRANSFER;
    params.min_alignment = kMinimumAlignment;
    iree_hal_buffer_t* device_buffer = NULL;
    IREE_CHECK_OK(iree_hal_allocator_allocate_buffer(
        iree_hal_device_allocator(device_), params, buffer_size,
        &device_buffer));

    iree_hal_transfer_command_t transfer_command;
    memset(&transfer_command, 0, sizeof(transfer_command));
    transfer_command.type = IREE_HAL_TRANSFER_COMMAND_TYPE_FILL;
    transfer_command.fill.target_buffer = device_buffer;
    transfer_command.fill.target_offset = 0;
    transfer_command.fill.length = buffer_size;
    transfer_command.fill.pattern = &pattern;
    transfer_command.fill.pattern_length = sizeof(pattern);
    iree_hal_command_buffer_t* command_buffer = NULL;
    IREE_CHECK_OK(iree_hal_create_transfer_command_buffer(
        device_, IREE_HAL_COMMAND_BUFFER_MODE_ONE_SHOT,
        IREE_HAL_QUEUE_AFFINITY_ANY, 1, &transfer_command, &command_buffer));
    IREE_CHECK_OK(SubmitCommandBufferAndWait(command_buffer));
    iree_hal_command_buffer_release(command_buffer);

    *out_buffer = device_buffer;
  }

  void CreatePatternedMemoryFile(iree_hal_memory_access_t access,
                                 iree_device_size_t file_size, uint8_t pattern,
                                 iree_hal_file_t** out_file) {
    void* file_contents = NULL;
    IREE_CHECK_OK(iree_allocator_malloc_aligned(iree_allocator_system(),
                                                file_size, kMinimumAlignment, 0,
                                                (void**)&file_contents));
    memset(file_contents, pattern, file_size);

    iree_io_file_handle_release_callback_t release_callback = {
        +[](void* user_data, iree_io_file_handle_primitive_t handle_primitive) {
          iree_allocator_free_aligned(iree_allocator_system(), user_data);
        },
        file_contents,
    };
    iree_io_file_handle_t* handle = NULL;
    IREE_CHECK_OK(iree_io_file_handle_wrap_host_allocation(
        IREE_IO_FILE_ACCESS_READ | IREE_IO_FILE_ACCESS_WRITE,
        iree_make_byte_span(file_contents, file_size), release_callback,
        iree_allocator_system(), &handle));
    IREE_CHECK_OK(iree_hal_file_import(
        device_, IREE_HAL_QUEUE_AFFINITY_ANY, access, handle,
        IREE_HAL_EXTERNAL_FILE_FLAG_NONE, out_file));
    iree_io_file_handle_release(handle);
  }
};

// Reads the entire file into a buffer and check the contents match.
TEST_F(FileTest, ReadEntireFile) {
  iree_device_size_t file_size = 128;
  iree_hal_file_t* file = NULL;
  CreatePatternedMemoryFile(IREE_HAL_MEMORY_ACCESS_READ, file_size, 0xDEu,
                            &file);
  iree_hal_buffer_t* buffer = NULL;
  CreatePatternedDeviceBuffer(file_size, 0xCD, &buffer);

  iree_hal_semaphore_t* semaphore = NULL;
  IREE_ASSERT_OK(iree_hal_semaphore_create(
      device_, 0ull, IREE_HAL_SEMAPHORE_FLAG_NONE, &semaphore));
  iree_hal_fence_t* wait_fence = NULL;
  IREE_ASSERT_OK(iree_hal_fence_create_at(
      semaphore, 1ull, iree_allocator_system(), &wait_fence));
  iree_hal_fence_t* signal_fence = NULL;
  IREE_ASSERT_OK(iree_hal_fence_create_at(
      semaphore, 2ull, iree_allocator_system(), &signal_fence));

  // NOTE: synchronously executing here so start with the wait signaled.
  // We should be able to make this async in the future.
  IREE_ASSERT_OK(iree_hal_fence_signal(wait_fence));

  IREE_ASSERT_OK(iree_hal_device_queue_read(
      device_, IREE_HAL_QUEUE_AFFINITY_ANY,
      iree_hal_fence_semaphore_list(wait_fence),
      iree_hal_fence_semaphore_list(signal_fence), /*source_file=*/file,
      /*source_offset=*/0, /*target_buffer=*/buffer, /*target_offset=*/0,
      /*length=*/file_size, IREE_HAL_READ_FLAG_NONE));

  IREE_ASSERT_OK(iree_hal_fence_wait(signal_fence, iree_infinite_timeout()));
  iree_hal_fence_release(wait_fence);
  iree_hal_fence_release(signal_fence);
  iree_hal_semaphore_release(semaphore);

  std::vector<uint8_t> reference_buffer(file_size);
  memset(reference_buffer.data(), 0xDEu, file_size);
  std::vector<uint8_t> actual_data(file_size);
  IREE_ASSERT_OK(iree_hal_device_transfer_d2h(
      device_, buffer, /*source_offset=*/0,
      /*target_buffer=*/actual_data.data(),
      /*data_length=*/file_size, IREE_HAL_TRANSFER_BUFFER_FLAG_DEFAULT,
      iree_infinite_timeout()));
  EXPECT_THAT(actual_data, ContainerEq(reference_buffer));

  iree_hal_buffer_release(buffer);
  iree_hal_file_release(file);
}

}  // namespace iree::hal::cts

#endif  // IREE_HAL_CTS_FILE_TEST_H_
