/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed 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 <android/binder_auto_utils.h>
#include <android/binder_manager.h>
#include <binder/ProcessState.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <aidl/android/aidl/tests/ITestService.h>

using aidl::android::aidl::tests::BackendType;
using aidl::android::aidl::tests::ITestService;
using testing::Eq;

struct AidlTest : testing::Test {
  template <typename T>
  std::shared_ptr<T> getService() {
    android::ProcessState::self()->setThreadPoolMaxThreadCount(1);
    android::ProcessState::self()->startThreadPool();
    ndk::SpAIBinder binder = ndk::SpAIBinder(AServiceManager_waitForService(T::descriptor));
    return T::fromBinder(binder);
  }
  void SetUp() override {
    service = getService<ITestService>();
    auto status = service->getBackendType(&backend);
    ASSERT_TRUE(status.isOk()) << status.getDescription();
  }
  std::shared_ptr<ITestService> service;
  BackendType backend;
};

TEST_F(AidlTest, repeatUtf8String) {
  const std::vector<std::string> utf8_inputs = {
      std::string("Deliver us from evil."),
      std::string(),
      std::string("\0\0", 2),
      // Similarly, the utf8 encodings of the small letter yee and euro sign.
      std::string("\xF0\x90\x90\xB7\xE2\x82\xAC"),
      ITestService::STRING_CONSTANT_UTF8,
  };

  for (const auto& input : utf8_inputs) {
    std::string reply;
    auto status = service->RepeatUtf8CppString(input, &reply);
    ASSERT_TRUE(status.isOk());
    ASSERT_THAT(reply, Eq(input));
  }

  std::optional<std::string> reply;
  auto status = service->RepeatNullableUtf8CppString(std::nullopt, &reply);
  ASSERT_TRUE(status.isOk());
  ASSERT_FALSE(reply.has_value());

  for (const auto& input : utf8_inputs) {
    std::optional<std::string> reply;
    auto status = service->RepeatNullableUtf8CppString(input, &reply);
    ASSERT_TRUE(status.isOk());
    ASSERT_TRUE(reply.has_value());
    ASSERT_THAT(*reply, Eq(input));
  }
}

TEST_F(AidlTest, reverseUtf8StringArray) {
  std::vector<std::string> input = {"a", "", "\xc3\xb8"};
  decltype(input) repeated;
  if (backend == BackendType::JAVA) {
    repeated = decltype(input)(input.size());
  }
  decltype(input) reversed;

  auto status = service->ReverseUtf8CppString(input, &repeated, &reversed);
  ASSERT_TRUE(status.isOk()) << status.getDescription();
  ASSERT_THAT(repeated, Eq(input));

  decltype(input) reversed_input(input);
  std::reverse(reversed_input.begin(), reversed_input.end());
  ASSERT_THAT(reversed, Eq(reversed_input));
}

struct AidlStringArrayTest : public AidlTest {
  void DoTest(::ndk::ScopedAStatus (ITestService::*func)(
      const std::optional<std::vector<std::optional<std::string>>>&,
      std::optional<std::vector<std::optional<std::string>>>*,
      std::optional<std::vector<std::optional<std::string>>>*)) {
    std::optional<std::vector<std::optional<std::string>>> input;
    decltype(input) repeated;
    decltype(input) reversed;

    auto status = (*service.*func)(input, &repeated, &reversed);
    ASSERT_TRUE(status.isOk()) << status.getDescription();

    if (func == &ITestService::ReverseUtf8CppStringList && backend == BackendType::JAVA) {
      // Java cannot clear the input variable to return a null value. It can
      // only ever fill out a list.
      ASSERT_TRUE(repeated.has_value());
    } else {
      ASSERT_FALSE(repeated.has_value());
    }

    ASSERT_FALSE(reversed.has_value());

    input = std::vector<std::optional<std::string>>();
    input->push_back("Deliver us from evil.");
    input->push_back(std::nullopt);
    input->push_back("\xF0\x90\x90\xB7\xE2\x82\xAC");

    // usable size needs to be initialized for Java
    repeated = std::vector<std::optional<std::string>>(input->size());

    status = (*service.*func)(input, &repeated, &reversed);
    ASSERT_TRUE(status.isOk()) << status.getDescription();
    ASSERT_TRUE(reversed.has_value());
    ASSERT_TRUE(repeated.has_value());
    ASSERT_THAT(reversed->size(), Eq(input->size()));
    ASSERT_THAT(repeated->size(), Eq(input->size()));

    for (size_t i = 0; i < input->size(); i++) {
      auto input_str = (*input)[i];
      auto repeated_str = (*repeated)[i];
      auto reversed_str = (*reversed)[(reversed->size() - 1) - i];
      if (!input_str) {
        ASSERT_FALSE(repeated_str.has_value());
        ASSERT_FALSE(reversed_str.has_value());
        // 3 nullptrs to strings.  No need to compare values.
        continue;
      }
      ASSERT_TRUE(repeated_str.has_value());
      ASSERT_TRUE(reversed_str.has_value());

      ASSERT_THAT(*repeated_str, Eq(*input_str));
      ASSERT_THAT(*reversed_str, Eq(*input_str));
    }
  }
};

TEST_F(AidlStringArrayTest, nullableList) {
  DoTest(&ITestService::ReverseUtf8CppStringList);
}

TEST_F(AidlStringArrayTest, nullableArray) {
  DoTest(&ITestService::ReverseNullableUtf8CppString);
}
