#include <gtest/gtest.h>
#include <vector>

#include "caffe2/operators/string_ops.h"

#include <c10/util/irange.h>

namespace caffe2 {

class StringJoinOpTest : public testing::Test {
 public:
  bool runOp(const Tensor& input) {
    auto* blob = ws_.CreateBlob("X");
    BlobSetTensor(blob, input.Alias());

    OperatorDef def;
    def.set_name("test");
    def.set_type("StringJoin");
    def.add_input("X");
    def.add_output("Y");

    auto op = CreateOperator(def, &ws_);
    return op->Run();
  }

  const std::string* checkAndGetOutput(int outputSize) {
    const auto* output = ws_.GetBlob("Y");
    EXPECT_NE(output, nullptr);
    EXPECT_TRUE(BlobIsTensorType(*output, CPU));
    const auto& outputTensor = output->Get<TensorCPU>();
    EXPECT_EQ(outputTensor.dim(), 1);
    EXPECT_EQ(outputTensor.size(0), outputSize);
    EXPECT_EQ(outputTensor.numel(), outputSize);
    return outputTensor.data<std::string>();
  }

 protected:
  // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)
  Workspace ws_;
};

TEST_F(StringJoinOpTest, testString1DJoin) {
  std::vector<std::string> input = {"a", "xx", "c"};

  auto blob = std::make_unique<Blob>();
  auto* tensor = BlobGetMutableTensor(blob.get(), CPU);
  tensor->Resize(input.size());
  auto* data = tensor->template mutable_data<std::string>();
  for (const auto i : c10::irange(input.size())) {
    // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
    *data++ = input[i];
  }

  EXPECT_TRUE(runOp(*tensor));

  const auto* outputData = checkAndGetOutput(input.size());
  EXPECT_EQ(outputData[0], "a,");
  EXPECT_EQ(outputData[1], "xx,");
  EXPECT_EQ(outputData[2], "c,");
}

TEST_F(StringJoinOpTest, testString2DJoin) {
  std::vector<std::vector<std::string>> input = {{"aa", "bb", "cc"},
                                                 {"dd", "ee", "ff"}};

  auto blob = std::make_unique<Blob>();
  auto* tensor = BlobGetMutableTensor(blob.get(), CPU);
  tensor->Resize(input.size(), input[0].size());
  auto* data = tensor->template mutable_data<std::string>();
  for (const auto i : c10::irange(input.size())) {
    for (const auto j : c10::irange(input[0].size())) {
      // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
      *data++ = input[i][j];
    }
  }

  EXPECT_TRUE(runOp(*tensor));

  const auto* outputData = checkAndGetOutput(input.size());
  EXPECT_EQ(outputData[0], "aa,bb,cc,");
  EXPECT_EQ(outputData[1], "dd,ee,ff,");
}

TEST_F(StringJoinOpTest, testFloat1DJoin) {
  std::vector<float> input = {3.90f, 5.234f, 8.12f};

  auto blob = std::make_unique<Blob>();
  auto* tensor = BlobGetMutableTensor(blob.get(), CPU);
  tensor->Resize(input.size());
  auto* data = tensor->template mutable_data<float>();
  for (const auto i : c10::irange(input.size())) {
    // NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
    *data++ = input[i];
  }

  EXPECT_TRUE(runOp(*tensor));

  const auto* outputData = checkAndGetOutput(input.size());
  EXPECT_EQ(outputData[0], "3.9,");
  EXPECT_EQ(outputData[1], "5.234,");
  EXPECT_EQ(outputData[2], "8.12,");
}

TEST_F(StringJoinOpTest, testFloat2DJoin) {
  std::vector<std::vector<float>> input = {{1.23f, 2.45f, 3.56f},
                                           {4.67f, 5.90f, 6.32f}};

  auto blob = std::make_unique<Blob>();
  auto* tensor = BlobGetMutableTensor(blob.get(), CPU);
  tensor->Resize(input.size(), input[0].size());
  auto* data = tensor->template mutable_data<float>();
  for (const auto i : c10::irange(input.size())) {
    for (const auto j : c10::irange(input[0].size())) {
      // NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
      *data++ = input[i][j];
    }
  }

  EXPECT_TRUE(runOp(*tensor));

  const auto* outputData = checkAndGetOutput(input.size());
  EXPECT_EQ(outputData[0], "1.23,2.45,3.56,");
  EXPECT_EQ(outputData[1], "4.67,5.9,6.32,");
}

TEST_F(StringJoinOpTest, testLong2DJoin) {
  std::vector<std::vector<int64_t>> input = {{100, 200}, {1000, 2000}};

  auto blob = std::make_unique<Blob>();
  auto* tensor = BlobGetMutableTensor(blob.get(), CPU);
  tensor->Resize(input.size(), input[0].size());
  auto* data = tensor->template mutable_data<int64_t>();
  for (const auto i : c10::irange(input.size())) {
    for (const auto j : c10::irange(input[0].size())) {
      // NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
      *data++ = input[i][j];
    }
  }

  EXPECT_TRUE(runOp(*tensor));

  const auto* outputData = checkAndGetOutput(input.size());
  EXPECT_EQ(outputData[0], "100,200,");
  EXPECT_EQ(outputData[1], "1000,2000,");
}
}
