// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 "benchmark/benchmark.h"

#include <cstdint>
#include <memory>
#include <vector>

#include "arrow/array.h"
#include "arrow/memory_pool.h"
#include "arrow/testing/gtest_util.h"
#include "arrow/testing/util.h"

#include "arrow/gpu/cuda_api.h"

namespace arrow {
namespace cuda {

constexpr int64_t kGpuNumber = 0;

static void CudaBufferWriterBenchmark(benchmark::State& state, const int64_t total_bytes,
                                      const int64_t chunksize,
                                      const int64_t buffer_size) {
  CudaDeviceManager* manager = nullptr;
  ABORT_NOT_OK(CudaDeviceManager::Instance().Value(&manager));
  std::shared_ptr<CudaContext> context;
  ABORT_NOT_OK(manager->GetContext(kGpuNumber).Value(&context));

  std::shared_ptr<CudaBuffer> device_buffer;
  ABORT_NOT_OK(context->Allocate(total_bytes).Value(&device_buffer));
  CudaBufferWriter writer(device_buffer);

  if (buffer_size > 0) {
    ABORT_NOT_OK(writer.SetBufferSize(buffer_size));
  }

  std::shared_ptr<ResizableBuffer> buffer;
  ASSERT_OK(MakeRandomByteBuffer(total_bytes, default_memory_pool(), &buffer));

  const uint8_t* host_data = buffer->data();
  while (state.KeepRunning()) {
    int64_t bytes_written = 0;
    ABORT_NOT_OK(writer.Seek(0));
    while (bytes_written < total_bytes) {
      int64_t bytes_to_write = std::min(chunksize, total_bytes - bytes_written);
      ABORT_NOT_OK(writer.Write(host_data + bytes_written, bytes_to_write));
      bytes_written += bytes_to_write;
    }
  }
  state.SetBytesProcessed(int64_t(state.iterations()) * total_bytes);
}

static void Writer_Buffered(benchmark::State& state) {
  // 128MB
  const int64_t kTotalBytes = 1 << 27;

  // 8MB
  const int64_t kBufferSize = 1 << 23;

  CudaBufferWriterBenchmark(state, kTotalBytes, state.range(0), kBufferSize);
}

static void Writer_Unbuffered(benchmark::State& state) {
  // 128MB
  const int64_t kTotalBytes = 1 << 27;
  CudaBufferWriterBenchmark(state, kTotalBytes, state.range(0), 0);
}

// Vary chunk write size from 256 bytes to 64K
BENCHMARK(Writer_Buffered)->RangeMultiplier(16)->Range(1 << 8, 1 << 16)->UseRealTime();

BENCHMARK(Writer_Unbuffered)
    ->RangeMultiplier(4)
    ->RangeMultiplier(16)
    ->Range(1 << 8, 1 << 16)
    ->UseRealTime();

}  // namespace cuda
}  // namespace arrow
