// Copyright 2021 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_work_queue/work_queue.h"

#include "pw_function/function.h"
#include "pw_log/log.h"
#include "pw_sync/thread_notification.h"
#include "pw_thread/thread.h"
#include "pw_unit_test/framework.h"
#include "pw_work_queue/test_thread.h"

namespace pw::work_queue {
namespace {

TEST(WorkQueue, PingPongOneRequestType) {
  struct {
    int counter = 0;
    sync::ThreadNotification worker_ping;
  } context;

  WorkQueueWithBuffer<10> work_queue;

  // Start the worker thread.
  thread::Thread work_thread(test::WorkQueueThreadOptions(), work_queue);

  // Pick a number bigger than the circular buffer to ensure we loop around.
  const int kPingPongs = 300;

  for (int i = 0; i < kPingPongs; ++i) {
    // Ping: throw work at the queue that will increment our counter.
    EXPECT_EQ(OkStatus(), work_queue.PushWork([&context] {
      context.counter++;
      PW_LOG_INFO("Send pong...");
      context.worker_ping.release();
    }));

    // Throw a distraction in the queue.
    EXPECT_EQ(OkStatus(), work_queue.PushWork([] {
      PW_LOG_INFO("I'm a random task in the work queue; nothing to see here!");
    }));

    // Pong: wait for the callback to notify us from the worker thread.
    context.worker_ping.acquire();
  }

  // Wait for the worker thread to terminate.
  work_queue.RequestStop();
  work_thread.join();

  EXPECT_EQ(context.counter, kPingPongs);
}

TEST(WorkQueue, PingPongTwoRequestTypesWithExtraRequests) {
  struct {
    int counter = 0;
    sync::ThreadNotification worker_ping;
  } context_a, context_b;

  WorkQueueWithBuffer<10> work_queue;

  // Start the worker thread.
  thread::Thread work_thread(test::WorkQueueThreadOptions(), work_queue);

  // Pick a number bigger than the circular buffer to ensure we loop around.
  const int kPingPongs = 300;

  // Run a bunch of work items in the queue.
  for (int i = 0; i < kPingPongs; ++i) {
    // Other requests...
    EXPECT_EQ(OkStatus(),
              work_queue.PushWork([] { PW_LOG_INFO("Chopping onions"); }));

    // Ping A: throw work at the queue that will increment our counter.
    EXPECT_EQ(OkStatus(), work_queue.PushWork([&context_a] {
      context_a.counter++;
      context_a.worker_ping.release();
    }));

    // Other requests...
    EXPECT_EQ(OkStatus(),
              work_queue.PushWork([] { PW_LOG_INFO("Dicing carrots"); }));
    EXPECT_EQ(OkStatus(),
              work_queue.PushWork([] { PW_LOG_INFO("Blanching spinach"); }));

    // Ping B: throw work at the queue that will increment our counter.
    EXPECT_EQ(OkStatus(), work_queue.PushWork([&context_b] {
      context_b.counter++;
      context_b.worker_ping.release();
    }));

    // Other requests...
    EXPECT_EQ(OkStatus(),
              work_queue.PushWork([] { PW_LOG_INFO("Peeling potatoes"); }));

    // Pong A & B: wait for the callbacks to notify us from the worker thread.
    context_a.worker_ping.acquire();
    context_b.worker_ping.acquire();
  }

  // Wait for the worker thread to terminate.
  work_queue.RequestStop();
  work_thread.join();

  EXPECT_EQ(context_a.counter, kPingPongs);
  EXPECT_EQ(context_b.counter, kPingPongs);
}

// TODO(ewout): Add unit tests for the metrics once they have been restructured.

}  // namespace
}  // namespace pw::work_queue
