/*
 * 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/function.h>
#include <tvm/ffi/rvalue_ref.h>

#include "./testing_object.h"

namespace {

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

TEST(RValueRef, Basic) {
  auto append =
      Function::FromTyped([](RValueRef<Array<int>> ref, int val, bool is_unique) -> Array<int> {
        Array<int> arr = *std::move(ref);
        EXPECT_EQ(arr.unique(), is_unique);
        arr.push_back(val);
        return arr;
      });
  auto a = append(RValueRef(Array<int>({1, 2})), 3, true).cast<Array<int>>();
  EXPECT_EQ(a.size(), 3);
  a = append(RValueRef(std::move(a)), 4, true).cast<Array<int>>();
  EXPECT_EQ(a.size(), 4);
  // pass in lvalue instead, the append still will succeed but array will not be unique
  a = append(a, 5, false).cast<Array<int>>();
  EXPECT_EQ(a.size(), 5);
}

TEST(RValueRef, ParamChecking) {
  // try decution
  // NOLINTNEXTLINE(performance-unnecessary-value-param)
  Function fadd1 = Function::FromTyped([](TInt a) -> int64_t { return a->value + 1; });

  // convert that triggers error
  EXPECT_THROW(
      {
        try {
          fadd1(RValueRef(TInt(1)));
        } catch (const Error& error) {
          EXPECT_EQ(error.kind(), "TypeError");
          EXPECT_EQ(error.message(),
                    "Mismatched type on argument #0 when calling: `(0: test.Int) -> int`. "
                    "Expected `test.Int` but got `ObjectRValueRef`");
          throw;
        }
      },
      ::tvm::ffi::Error);

  Function fadd2 = Function::FromTyped([](RValueRef<Array<int>> a) -> int {
    Array<int> arr = *std::move(a);
    return arr[0] + 1;
  });

  // convert that triggers error
  EXPECT_THROW(
      {
        try {
          fadd2(RValueRef(Array<Any>({1, 2.2})));
        } catch (const Error& error) {
          EXPECT_EQ(error.kind(), "TypeError");
          EXPECT_EQ(
              error.message(),
              "Mismatched type on argument #0 when calling: `(0: RValueRef<Array<int>>) -> int`. "
              "Expected `RValueRef<Array<int>>` but got `RValueRef<Array[index 1: float]>`");
          throw;
        }
      },
      ::tvm::ffi::Error);
  // triggered a rvalue based conversion
  Function func3 = Function::FromTyped([](RValueRef<TPrimExpr> a) -> String {
    TPrimExpr expr = *std::move(a);
    return expr->dtype;
  });
  // EXPECT_EQ(func3(RValueRef(String("int32"))).cast<String>(), "int32");
  // triggered a lvalue based conversion
  // EXPECT_EQ(func3(String("int32")).cast<String>(), "int32");
}
}  // namespace
