// Copyright 2023 The Pigweed Authors
//
// 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
//
//     https://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 "pw_async2/poll.h"

#include <type_traits>

#include "gtest/gtest.h"
#include "pw_result/result.h"

namespace pw::async2 {
namespace {

class MoveOnly {
 public:
  MoveOnly() = delete;
  MoveOnly(int value) : value_(value) {}
  MoveOnly(const MoveOnly&) = delete;
  MoveOnly& operator=(const MoveOnly&) = delete;
  MoveOnly(MoveOnly&&) = default;
  MoveOnly& operator=(MoveOnly&&) = default;
  int value() const { return value_; }

 private:
  int value_;
};

struct Immovable {
 public:
  Immovable() = delete;
  Immovable(MoveOnly&& value) : value_(std::move(value)) {}
  Immovable(const Immovable&) = delete;
  Immovable& operator=(const Immovable&) = delete;
  Immovable(Immovable&&) = delete;
  Immovable& operator=(Immovable&&) = delete;
  int value() const { return value_.value(); }

 private:
  MoveOnly value_;
};

TEST(Poll, ConstructsReadyInPlace) {
  Poll<Immovable> mr(std::in_place, MoveOnly(5));
  EXPECT_TRUE(mr.IsReady());
  EXPECT_EQ(mr->value(), 5);
}

TEST(Poll, ConstructsReadyFromValueType) {
  Poll<MoveOnly> mr = MoveOnly(5);
  EXPECT_TRUE(mr.IsReady());
  EXPECT_EQ(mr->value(), 5);
}

TEST(Poll, ConstructsFromValueConvertibleToValueType) {
  Poll<Immovable> mr(MoveOnly(5));
  EXPECT_TRUE(mr.IsReady());
  EXPECT_EQ(mr->value(), 5);
}

TEST(Poll, ConstructsFromPollWithValueConvertibleToValueType) {
  Poll<MoveOnly> move_poll(MoveOnly(5));
  Poll<Immovable> no_move_poll(std::move(move_poll));
  EXPECT_TRUE(no_move_poll.IsReady());
  EXPECT_EQ(no_move_poll->value(), 5);
}

TEST(Poll, ConstructsPendingFromPendingType) {
  Poll<MoveOnly> mr(Pending());
  EXPECT_FALSE(mr.IsReady());
}

TEST(Poll, ConstructorInfersValueType) {
  auto res = Poll("hello");
  static_assert(std::is_same_v<decltype(res), Poll<const char*>>);
  EXPECT_TRUE(res.IsReady());
  EXPECT_STREQ(res.value(), "hello");
}

TEST(Poll, ReadinessOnReadyValueReturnsReadyWithoutValue) {
  Poll<int> v = Ready(5);
  Poll<> readiness = v.Readiness();
  EXPECT_TRUE(readiness.IsReady());
}

TEST(Poll, ReadinessOnPendingValueReturnsPendingWithoutValue) {
  Poll<int> v = Pending();
  Poll<> readiness = v.Readiness();
  EXPECT_TRUE(readiness.IsPending());
}

TEST(Poll, ReadyToString) {
  char buffer[128] = {};
  Poll<> v = Ready();
  EXPECT_EQ(5u, ToString(v, buffer).size());
  EXPECT_STREQ("Ready", buffer);
}

TEST(Poll, ReadyValueToString) {
  char buffer[128] = {};
  Poll<uint16_t> v = 5;
  EXPECT_EQ(8u, ToString(v, buffer).size());
  EXPECT_STREQ("Ready(5)", buffer);
}

TEST(Poll, PendingToString) {
  char buffer[128] = {};
  Poll<uint16_t> v = Pending();
  EXPECT_EQ(7u, ToString(v, buffer).size());
  EXPECT_STREQ("Pending", buffer);
}

TEST(PendingFunction, ReturnsValueConvertibleToPendingPoll) {
  Poll<MoveOnly> mr = Pending();
  EXPECT_FALSE(mr.IsReady());
}

TEST(ReadyFunction, CalledWithNoArgumentsReturnsPollWithReadyType) {
  Poll<> mr = Ready();
  EXPECT_TRUE(mr.IsReady());
  [[maybe_unused]] ReadyType& ready_value = mr.value();
}

TEST(ReadyFunction, ConstructsReadyInPlace) {
  Poll<Immovable> mr = Ready<Immovable>(std::in_place, MoveOnly(5));
  EXPECT_TRUE(mr.IsReady());
  EXPECT_EQ(mr->value(), 5);
}

TEST(ReadyFunction, ConstructsReadyFromValueType) {
  Poll<MoveOnly> mr = Ready(MoveOnly(5));
  EXPECT_TRUE(mr.IsReady());
  EXPECT_EQ(mr->value(), 5);
}

Poll<Result<int>> EndToEndTest(int input) {
  if (input == 0) {
    // Check that returning plain ``Status`` works.
    return Status::PermissionDenied();
  }
  if (input == 1) {
    // Check that returning ``Pending`` works.
    return Pending();
  }
  if (input == 2) {
    // Check that returning ``Result<int>`` works.
    Result<int> v = 2;
    return v;
  }
  if (input == 3) {
    // Check that returning plain ``int`` works.
    return 3;
  }
  if (input == 4) {
    // Check that returning ``Poll<int>`` works.
    return Ready(4);
  }
  if (input == 5) {
    // Check that returning ``Poll<Status>`` works.
    return Ready(Status::DataLoss());
  }
  return Status::Unknown();
}

TEST(EndToEndTest, ReturnsStatus) {
  Poll<Result<int>> result = EndToEndTest(0);
  ASSERT_TRUE(result.IsReady());
  EXPECT_EQ(result->status(), Status::PermissionDenied());
}

TEST(EndToEndTest, ReturnsPending) {
  Poll<Result<int>> result = EndToEndTest(1);
  EXPECT_FALSE(result.IsReady());
}

TEST(EndToEndTest, ReturnsValue) {
  Poll<Result<int>> result = EndToEndTest(3);
  ASSERT_TRUE(result.IsReady());
  ASSERT_TRUE(result->ok());
  EXPECT_EQ(**result, 3);
}

TEST(EndToEndTest, ReturnsReady) {
  Poll<Result<int>> result = EndToEndTest(4);
  ASSERT_TRUE(result.IsReady());
  ASSERT_TRUE(result->ok());
  EXPECT_EQ(**result, 4);
}

TEST(EndToEndTest, ReturnsPollStatus) {
  Poll<Result<int>> result = EndToEndTest(5);
  ASSERT_TRUE(result.IsReady());
  EXPECT_EQ(result->status(), Status::DataLoss());
}

}  // namespace
}  // namespace pw::async2
