/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/BoundedMPSCQueue.h"
#include <thread>
#include <chrono>

using namespace mozilla;

struct NativeStack {
  void* mPCs[32];
  void* mSPs[32];
  size_t mCount;
  size_t mTid;
};

void StackWalkCallback(void* aPC, void* aSP, NativeStack* nativeStack) {
  nativeStack->mSPs[nativeStack->mCount] = aSP;
  nativeStack->mPCs[nativeStack->mCount] = aPC;
  nativeStack->mCount++;
}

void FillNativeStack(NativeStack* aStack) {
  StackWalkCallback((void*)0x1234, (void*)0x9876, aStack);
  StackWalkCallback((void*)0x3456, (void*)0x5432, aStack);
  StackWalkCallback((void*)0x7890, (void*)0x1098, aStack);
  StackWalkCallback((void*)0x1234, (void*)0x7654, aStack);
  StackWalkCallback((void*)0x5678, (void*)0x3210, aStack);
  StackWalkCallback((void*)0x9012, (void*)0x9876, aStack);
  StackWalkCallback((void*)0x1334, (void*)0x9786, aStack);
  StackWalkCallback((void*)0x3546, (void*)0x5342, aStack);
  StackWalkCallback((void*)0x7809, (void*)0x0198, aStack);
  StackWalkCallback((void*)0x4123, (void*)0x7645, aStack);
  StackWalkCallback((void*)0x5768, (void*)0x3120, aStack);
  StackWalkCallback((void*)0x9102, (void*)0x9867, aStack);
  StackWalkCallback((void*)0x1243, (void*)0x8976, aStack);
  StackWalkCallback((void*)0x6345, (void*)0x4325, aStack);
  StackWalkCallback((void*)0x8790, (void*)0x1908, aStack);
  StackWalkCallback((void*)0x134, (void*)0x654, aStack);
  StackWalkCallback((void*)0x567, (void*)0x320, aStack);
  StackWalkCallback((void*)0x901, (void*)0x976, aStack);
}

template <size_t Capacity>
void BasicAPITestWithStack(BoundedMPSCQueue<NativeStack, Capacity>& aQueue,
                           size_t aCap) {
  MOZ_RELEASE_ASSERT(aQueue.Capacity() == aCap);

  NativeStack s = {.mCount = 0};
  FillNativeStack(&s);
  MOZ_RELEASE_ASSERT(s.mCount == 18);

  int store = -1;
  for (size_t i = 0; i < aCap; ++i) {
    store = aQueue.Send(s);
    MOZ_RELEASE_ASSERT(store > 0);
  }

  int retrieve = -1;
  for (size_t i = 0; i < aCap; ++i) {
    NativeStack sr{};
    retrieve = aQueue.Recv(&sr);

    MOZ_RELEASE_ASSERT(retrieve > 0);
    MOZ_RELEASE_ASSERT(&s != &sr);
    MOZ_RELEASE_ASSERT(s.mCount == sr.mCount);

    for (size_t i = 0; i < s.mCount; ++i) {
      MOZ_RELEASE_ASSERT(s.mPCs[i] == sr.mPCs[i]);
      MOZ_RELEASE_ASSERT(s.mSPs[i] == sr.mSPs[i]);
    }
  }
}

template <size_t Capacity>
void BasicAPITestMP(BoundedMPSCQueue<NativeStack, Capacity>& aQueue,
                    size_t aThreads) {
  MOZ_RELEASE_ASSERT(aQueue.Capacity() == 15);

  std::thread consumer([&aQueue, aThreads] {
    size_t received = 0;
    NativeStack v{};
    do {
      int deq = aQueue.Recv(&v);
      if (deq > 0) {
        received++;
      }
      std::this_thread::sleep_for(std::chrono::microseconds(10));
    } while (received < aThreads);
  });

  std::thread producers[aThreads];
  for (size_t t = 0; t < aThreads; ++t) {
    producers[t] = std::thread([&aQueue, t] {
      NativeStack s = {.mCount = 0, .mTid = t};
      FillNativeStack(&s);
      MOZ_RELEASE_ASSERT(s.mCount == 18);

      int sent = 0;
      // wrap in a do { } while () because Send() will return 0 on message being
      // dropped so we want to retry
      do {
        std::this_thread::sleep_for(std::chrono::microseconds(5));
        sent = aQueue.Send(s);
      } while (sent == 0);
    });
  }

  for (size_t t = 0; t < aThreads; ++t) {
    producers[t].join();
  }
  consumer.join();
}

template <size_t Capacity>
bool testBasicApi() {
  BoundedMPSCQueue<NativeStack, Capacity> s;
  BasicAPITestWithStack(s, Capacity);
  return true;
}

template <size_t... Capacity>
void testBasicApiCapacities() {
  [[maybe_unused]] std::initializer_list<bool> _ = {
      testBasicApi<Capacity>()...};
}

int main() {
  testBasicApiCapacities<1, 5, 7, 10, 15>();

  {
    NativeStack e{};
    BoundedMPSCQueue<NativeStack, 2> deq;

    // Dequeue with nothing should return 0 and not fail later
    int retrieve = deq.Recv(&e);
    MOZ_RELEASE_ASSERT(retrieve == 0);

    NativeStack real = {.mCount = 0};
    FillNativeStack(&real);
    MOZ_RELEASE_ASSERT(real.mCount == 18);

    int store = deq.Send(real);
    MOZ_RELEASE_ASSERT(store > 0);
    store = deq.Send(real);
    MOZ_RELEASE_ASSERT(store > 0);

    // should be full we should get 0
    store = deq.Send(real);
    MOZ_RELEASE_ASSERT(store == 0);

    // try to dequeue
    NativeStack e1{};
    retrieve = deq.Recv(&e1);
    MOZ_RELEASE_ASSERT(retrieve > 0);
    MOZ_RELEASE_ASSERT(e1.mCount == 18);

    NativeStack e2{};
    retrieve = deq.Recv(&e2);
    MOZ_RELEASE_ASSERT(retrieve > 0);
    MOZ_RELEASE_ASSERT(e2.mCount == 18);

    retrieve = deq.Recv(&e);
    MOZ_RELEASE_ASSERT(retrieve == 0);
  }

  size_t nbThreads[] = {8, 16, 64, 128, 512, 1024};
  for (auto threads : nbThreads) {
    BoundedMPSCQueue<NativeStack, 15> s;
    BasicAPITestMP(s, threads);
  }

  return 0;
}
