/*
 * 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 <gtest/gtest.h>
#include <tvm/ffi/any.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/memory.h>
#include <tvm/ffi/optional.h>

#include "./testing_object.h"

namespace {

using namespace tvm::ffi;
using namespace tvm::ffi::testing;

TEST(Optional, TInt) {
  Optional<TInt> x;
  Optional<TInt> y = TInt(11);
  static_assert(sizeof(Optional<TInt>) == sizeof(ObjectRef));

  EXPECT_TRUE(!x.has_value());
  EXPECT_EQ(x.value_or(TInt(12))->value, 12);

  EXPECT_TRUE(y.has_value());
  EXPECT_EQ(y.value_or(TInt(12))->value, 11);

  Any z_any = std::move(y);
  EXPECT_TRUE(z_any != nullptr);
  EXPECT_EQ((z_any.cast<TInt>())->value, 11);
  // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
  EXPECT_TRUE(!y.has_value());

  // move from any to optional
  auto y2 = std::move(z_any).cast<Optional<TInt>>();
  EXPECT_EQ(y2.use_count(), 1);
  EXPECT_TRUE(y2.has_value());
  EXPECT_EQ(y2.value_or(TInt(12))->value, 11);
}

TEST(Optional, double) {
  Optional<double> x;
  Optional<double> y = 11.0;
  static_assert(sizeof(Optional<double>) > sizeof(ObjectRef));

  EXPECT_TRUE(!x.has_value());
  EXPECT_EQ(x.value_or(12), 12);
  EXPECT_TRUE(x != 12);

  EXPECT_TRUE(y.has_value());
  EXPECT_EQ(y.value_or(12), 11);
  EXPECT_TRUE(y == 11);
  EXPECT_TRUE(y != 12);
}

TEST(Optional, AnyConvertInt) {
  Optional<int> opt_v0 = 1;
  EXPECT_EQ(opt_v0.value(), 1);
  EXPECT_TRUE(opt_v0.has_value());

  AnyView view0 = opt_v0;
  EXPECT_EQ(view0.cast<int>(), 1);

  Any any1;
  auto opt_v1 = std::move(any1).cast<Optional<int>>();
  EXPECT_TRUE(!opt_v1.has_value());
  Optional<int> opt_v2 = 11;
  Any any2 = std::move(opt_v2);
  EXPECT_EQ(any2.cast<int>(), 11);
}

TEST(Optional, AnyConvertArray) {
  AnyView view0;
  Array<Array<TNumber>> arr_nested = {{}, {TInt(1), TFloat(2)}};
  view0 = arr_nested;

  auto opt_arr = view0.cast<Optional<Array<Array<TNumber>>>>();
  EXPECT_EQ(arr_nested.use_count(), 2);

  auto arr1 = view0.cast<Optional<Array<Array<TNumber>>>>();
  EXPECT_EQ(arr_nested.use_count(), 3);
  EXPECT_EQ(arr1.value()[1][1].as<TFloatObj>()->value, 2);

  Any any1;
  auto arr2 = any1.cast<Optional<Array<Array<TNumber>>>>();
  EXPECT_TRUE(!arr2.has_value());

  EXPECT_THROW(
      {
        try {
          [[maybe_unused]] auto arr2 = view0.cast<Optional<Array<Array<int>>>>();
        } catch (const Error& error) {
          EXPECT_EQ(error.kind(), "TypeError");
          std::string what = error.what();
          std::cout << what << std::endl;
          EXPECT_NE(what.find("to `Optional<Array<Array<int>>>`"), std::string::npos);
          throw;
        }
      },
      ::tvm::ffi::Error);
}

TEST(Optional, OptionalOfOptional) {
  // testcase of optional<optional>
  Optional<Optional<int>> opt_opt_int;
  EXPECT_TRUE(!opt_opt_int.has_value());

  Optional<Optional<int>> opt_opt_int2 = Optional<int>(std::nullopt);
  EXPECT_TRUE(opt_opt_int2.has_value());
  EXPECT_TRUE(!opt_opt_int2.value().has_value());

  // Optional<Optional<ObjectRef>>
  Optional<Optional<TInt>> opt_opt_tint;
  EXPECT_TRUE(!opt_opt_tint.has_value());

  Optional<Optional<TInt>> opt_opt_tint2 = Optional<TInt>(std::nullopt);
  EXPECT_TRUE(opt_opt_tint2.has_value());
  EXPECT_TRUE(!opt_opt_tint2.value().has_value());
  opt_opt_tint2 = std::nullopt;
  EXPECT_TRUE(!opt_opt_tint2.has_value());

  Optional<Optional<TInt>> opt_opt_tint3 = Optional<TInt>(TInt(42));
  EXPECT_TRUE(opt_opt_tint3.has_value());
  EXPECT_TRUE(opt_opt_tint3.value().has_value());
  EXPECT_EQ(opt_opt_tint3.value().value()->value, 42);
}

TEST(Optional, ValueMove) {
  Optional<TInt> y = TInt(11);
  TInt x = std::move(y).value();
  // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
  EXPECT_TRUE(!y.has_value());
  EXPECT_EQ(x->value, 11);

  Optional<TInt> opt_tint = TInt(21);
  EXPECT_TRUE(opt_tint.has_value());
  EXPECT_EQ((*opt_tint)->value, 21);

  TInt moved_tint = *std::move(opt_tint);
  EXPECT_EQ(moved_tint->value, 21);
  // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
  EXPECT_TRUE(!opt_tint.has_value());
}

TEST(Optional, OptionalInArray) {
  // This pattern plus iteration may cause memory leak
  // this is because arr[0] returns a temporary object
  // and further call arr[0].value() may return a reference to
  // the temporary object
  Array<Optional<Array<TInt>>> arr = {Array<TInt>({TInt(0), TInt(1)})};
  int counter = 0;

  for (const auto& x : arr[0].value()) {
    EXPECT_EQ(x->value, counter++);
  }

  Any any = arr;
  auto opt_arr = any.cast<Array<Optional<Array<TInt>>>>();
  EXPECT_EQ(opt_arr[0].value()[0]->value, 0);
}

TEST(Optional, String) {
  Optional<String> opt_str;
  EXPECT_TRUE(!opt_str.has_value());
  EXPECT_EQ(opt_str.value_or("default"), "default");
  EXPECT_TRUE(opt_str != "default");
  EXPECT_TRUE(opt_str != String("default"));
  EXPECT_TRUE(opt_str == std::nullopt);

  opt_str = "hello";
  EXPECT_TRUE(opt_str.has_value());
  EXPECT_EQ(opt_str.value(), "hello");
  EXPECT_TRUE(opt_str == "hello");
  EXPECT_TRUE(opt_str == String("hello"));
  EXPECT_TRUE(opt_str != std::nullopt);
  static_assert(sizeof(Optional<String>) == sizeof(String));
}

TEST(Optional, Bytes) {
  Optional<Bytes> opt_bytes;
  EXPECT_TRUE(!opt_bytes.has_value());
  EXPECT_EQ(opt_bytes.value_or(std::string("default")), "default");

  opt_bytes = std::string("hello");
  EXPECT_TRUE(opt_bytes.has_value());
  EXPECT_EQ(opt_bytes.value().operator std::string(), "hello");
  EXPECT_TRUE(opt_bytes != std::nullopt);
  static_assert(sizeof(Optional<Bytes>) == sizeof(Bytes));
}
}  // namespace
