// Copyright 2024 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/join.h"

#include "pw_async2/dispatcher.h"
#include "pw_unit_test/framework.h"

namespace {

using ::pw::async2::Context;
using ::pw::async2::Dispatcher;
using ::pw::async2::Join;
using ::pw::async2::Pending;
using ::pw::async2::Poll;
using ::pw::async2::Waker;

// Windows GCC emits a bogs uninitialized error for the
// move constructor below.
PW_MODIFY_DIAGNOSTICS_PUSH();
PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
struct SomeMoveOnlyValue {
  explicit SomeMoveOnlyValue(int result) : result_(result), move_count_(0) {}
  SomeMoveOnlyValue(const SomeMoveOnlyValue&) = delete;
  SomeMoveOnlyValue& operator=(const SomeMoveOnlyValue&) = delete;
  SomeMoveOnlyValue(SomeMoveOnlyValue&& other)
      : result_(other.result_), move_count_(other.move_count_ + 1) {
    other.result_ = -1;
  }
  SomeMoveOnlyValue& operator=(SomeMoveOnlyValue&& other) {
    result_ = other.result_;
    other.result_ = -1;
    move_count_ = other.move_count_ + 1;
    return *this;
  }
  ~SomeMoveOnlyValue() = default;
  int result_;
  int move_count_;
};
PW_MODIFY_DIAGNOSTICS_POP();

struct PendableController {
  PendableController() : poll_count_(0), allow_completion_(false), waker_() {}
  PendableController(PendableController&) = delete;
  PendableController(PendableController&&) = delete;
  PendableController& operator=(const PendableController&) = delete;
  PendableController& operator=(PendableController&&) = delete;
  ~PendableController() = default;

  int poll_count_;
  bool allow_completion_;
  Waker waker_;
};

struct StructWithPendMethod {
  StructWithPendMethod(int result, PendableController& controller)
      : result_(result), controller_(&controller) {}

  StructWithPendMethod(const StructWithPendMethod&) = delete;
  StructWithPendMethod& operator=(const StructWithPendMethod&) = delete;
  StructWithPendMethod(StructWithPendMethod&&) = default;
  StructWithPendMethod& operator=(StructWithPendMethod&&) = default;
  ~StructWithPendMethod() = default;

  Poll<SomeMoveOnlyValue> Pend(Context& cx) {
    ++controller_->poll_count_;
    if (controller_->allow_completion_) {
      return Poll<SomeMoveOnlyValue>(result_);
    }
    PW_ASYNC_STORE_WAKER(
        cx,
        controller_->waker_,
        "StructWithPendMethod is waiting for PendableController's waker");
    return Pending();
  }

  int result_;
  PendableController* controller_;
};

TEST(Join, PendDelegatesToPendables) {
  Dispatcher dispatcher;

  PendableController controller_1;
  PendableController controller_2;
  StructWithPendMethod pendable_1(1, controller_1);
  StructWithPendMethod pendable_2(2, controller_2);
  Join join(std::move(pendable_1), std::move(pendable_2));

  EXPECT_EQ(dispatcher.RunPendableUntilStalled(join), Pending());
  controller_2.allow_completion_ = true;
  std::move(controller_2.waker_).Wake();
  EXPECT_EQ(dispatcher.RunPendableUntilStalled(join), Pending());
  controller_1.allow_completion_ = true;
  std::move(controller_1.waker_).Wake();
  auto&& result = dispatcher.RunPendableUntilStalled(join);
  ASSERT_TRUE(result.IsReady());
  auto&& [v1, v2] = std::move(*result);
  EXPECT_EQ(v1.result_, 1);
  EXPECT_EQ(v2.result_, 2);
  EXPECT_EQ(v1.move_count_, 1);
  EXPECT_EQ(v2.move_count_, 1);
}

TEST(Join, BindsDirectly) {
  Dispatcher dispatcher;
  PendableController controller_1;
  controller_1.allow_completion_ = true;
  PendableController controller_2;
  controller_2.allow_completion_ = true;
  StructWithPendMethod pendable_1(1, controller_1);
  StructWithPendMethod pendable_2(2, controller_2);
  Join join(std::move(pendable_1), std::move(pendable_2));

  auto&& [v1, v2] = dispatcher.RunPendableToCompletion(join);
  EXPECT_EQ(v1.result_, 1);
  EXPECT_EQ(v2.result_, 2);
  EXPECT_EQ(v1.move_count_, 1);
  EXPECT_EQ(v2.move_count_, 1);
}

}  // namespace
