/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: sw=2 ts=4 et :
 */
#include "TestDemon.h"

#include <stdlib.h>

#include "IPDLUnitTests.h"  // fail etc.
#if defined(OS_POSIX)
#  include <sys/time.h>
#  include <unistd.h>
#else
#  include <time.h>
#  include <windows.h>
#endif

namespace mozilla {
namespace _ipdltest {

const int kMaxStackHeight = 4;

static LazyLogModule sLogModule("demon");

#define DEMON_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__))

static int gStackHeight = 0;
static bool gFlushStack = false;

static int Choose(int count) {
#if defined(OS_POSIX)
  return random() % count;
#else
  return rand() % count;
#endif
}

//-----------------------------------------------------------------------------
// parent

TestDemonParent::TestDemonParent() : mDone(false), mIncoming(), mOutgoing() {
  MOZ_COUNT_CTOR(TestDemonParent);
}

TestDemonParent::~TestDemonParent() { MOZ_COUNT_DTOR(TestDemonParent); }

void TestDemonParent::Main() {
  if (!getenv("MOZ_TEST_IPC_DEMON")) {
    QuitParent();
    return;
  }
#if defined(OS_POSIX)
  srandom(time(nullptr));
#else
  srand(time(nullptr));
#endif

  DEMON_LOG("Start demon");

  if (!SendStart()) fail("sending Start");

  RunUnlimitedSequence();
}

#ifdef DEBUG
bool TestDemonParent::ShouldContinueFromReplyTimeout() {
  return Choose(2) == 0;
}

bool TestDemonParent::ArtificialTimeout() { return Choose(5) == 0; }

void TestDemonParent::ArtificialSleep() {
  if (Choose(2) == 0) {
    // Sleep for anywhere from 0 to 100 milliseconds.
    unsigned micros = Choose(100) * 1000;
#  ifdef OS_POSIX
    usleep(micros);
#  else
    Sleep(micros / 1000);
#  endif
  }
}
#endif

mozilla::ipc::IPCResult TestDemonParent::RecvAsyncMessage(const int& n) {
  DEMON_LOG("Start RecvAsync [%d]", n);

  MOZ_ASSERT(n == mIncoming[0]);
  mIncoming[0]++;

  RunLimitedSequence();

  DEMON_LOG("End RecvAsync [%d]", n);
  return IPC_OK();
}

mozilla::ipc::IPCResult TestDemonParent::RecvHiPrioSyncMessage() {
  DEMON_LOG("Start RecvHiPrioSyncMessage");
  RunLimitedSequence();
  DEMON_LOG("End RecvHiPrioSyncMessage");
  return IPC_OK();
}

mozilla::ipc::IPCResult TestDemonParent::RecvSyncMessage(const int& n) {
  DEMON_LOG("Start RecvSync [%d]", n);

  MOZ_ASSERT(n == mIncoming[0]);
  mIncoming[0]++;

  RunLimitedSequence(ASYNC_ONLY);

  DEMON_LOG("End RecvSync [%d]", n);
  return IPC_OK();
}

mozilla::ipc::IPCResult TestDemonParent::RecvUrgentAsyncMessage(const int& n) {
  DEMON_LOG("Start RecvUrgentAsyncMessage [%d]", n);

  MOZ_ASSERT(n == mIncoming[2]);
  mIncoming[2]++;

  RunLimitedSequence(ASYNC_ONLY);

  DEMON_LOG("End RecvUrgentAsyncMessage [%d]", n);
  return IPC_OK();
}

mozilla::ipc::IPCResult TestDemonParent::RecvUrgentSyncMessage(const int& n) {
  DEMON_LOG("Start RecvUrgentSyncMessage [%d]", n);

  MOZ_ASSERT(n == mIncoming[2]);
  mIncoming[2]++;

  RunLimitedSequence(ASYNC_ONLY);

  DEMON_LOG("End RecvUrgentSyncMessage [%d]", n);
  return IPC_OK();
}

void TestDemonParent::RunUnlimitedSequence() {
  if (mDone) {
    return;
  }

  gFlushStack = false;
  DoAction();

  MessageLoop::current()->PostTask(NewNonOwningRunnableMethod(
      "_ipdltest::TestDemonParent::RunUnlimitedSequence", this,
      &TestDemonParent::RunUnlimitedSequence));
}

void TestDemonParent::RunLimitedSequence(int flags) {
  if (gStackHeight >= kMaxStackHeight) {
    return;
  }
  gStackHeight++;

  int count = Choose(20);
  for (int i = 0; i < count; i++) {
    if (!DoAction(flags)) {
      gFlushStack = true;
    }
    if (gFlushStack) {
      gStackHeight--;
      return;
    }
  }

  gStackHeight--;
}

static bool AllowAsync(int outgoing, int incoming) {
  return incoming >= outgoing - 5;
}

bool TestDemonParent::DoAction(int flags) {
  if (flags & ASYNC_ONLY) {
    if (AllowAsync(mOutgoing[0], mIncoming[0])) {
      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
      return SendAsyncMessage(mOutgoing[0]++);
    } else {
      return true;
    }
  } else {
    switch (Choose(3)) {
      case 0:
        if (AllowAsync(mOutgoing[0], mIncoming[0])) {
          DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
          return SendAsyncMessage(mOutgoing[0]++);
        } else {
          return true;
        }

      case 1: {
        DEMON_LOG("Start SendHiPrioSyncMessage");
        bool r = SendHiPrioSyncMessage();
        DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
        return r;
      }

      case 2:
        DEMON_LOG("Cancel");
        GetIPCChannel()->CancelCurrentTransaction();
        return true;
    }
  }
  MOZ_CRASH();
  return false;
}

//-----------------------------------------------------------------------------
// child

TestDemonChild::TestDemonChild() : mIncoming(), mOutgoing() {
  MOZ_COUNT_CTOR(TestDemonChild);
}

TestDemonChild::~TestDemonChild() { MOZ_COUNT_DTOR(TestDemonChild); }

mozilla::ipc::IPCResult TestDemonChild::RecvStart() {
#ifdef OS_POSIX
  srandom(time(nullptr));
#else
  srand(time(nullptr));
#endif

  DEMON_LOG("RecvStart");

  RunUnlimitedSequence();
  return IPC_OK();
}

#ifdef DEBUG
void TestDemonChild::ArtificialSleep() {
  if (Choose(2) == 0) {
    // Sleep for anywhere from 0 to 100 milliseconds.
    unsigned micros = Choose(100) * 1000;
#  ifdef OS_POSIX
    usleep(micros);
#  else
    Sleep(micros / 1000);
#  endif
  }
}
#endif

mozilla::ipc::IPCResult TestDemonChild::RecvAsyncMessage(const int& n) {
  DEMON_LOG("Start RecvAsyncMessage [%d]", n);

  MOZ_ASSERT(n == mIncoming[0]);
  mIncoming[0]++;

  RunLimitedSequence();

  DEMON_LOG("End RecvAsyncMessage [%d]", n);
  return IPC_OK();
}

mozilla::ipc::IPCResult TestDemonChild::RecvHiPrioSyncMessage() {
  DEMON_LOG("Start RecvHiPrioSyncMessage");
  RunLimitedSequence();
  DEMON_LOG("End RecvHiPrioSyncMessage");
  return IPC_OK();
}

void TestDemonChild::RunUnlimitedSequence() {
  gFlushStack = false;
  DoAction();

  MessageLoop::current()->PostTask(NewNonOwningRunnableMethod(
      "_ipdltest::TestDemonChild::RunUnlimitedSequence", this,
      &TestDemonChild::RunUnlimitedSequence));
}

void TestDemonChild::RunLimitedSequence() {
  if (gStackHeight >= kMaxStackHeight) {
    return;
  }
  gStackHeight++;

  int count = Choose(20);
  for (int i = 0; i < count; i++) {
    if (!DoAction()) {
      gFlushStack = true;
    }
    if (gFlushStack) {
      gStackHeight--;
      return;
    }
  }

  gStackHeight--;
}

bool TestDemonChild::DoAction() {
  switch (Choose(6)) {
    case 0:
      if (AllowAsync(mOutgoing[0], mIncoming[0])) {
        DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
        return SendAsyncMessage(mOutgoing[0]++);
      } else {
        return true;
      }

    case 1: {
      DEMON_LOG("Start SendHiPrioSyncMessage");
      bool r = SendHiPrioSyncMessage();
      DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
      return r;
    }

    case 2: {
      DEMON_LOG("Start SendSyncMessage [%d]", mOutgoing[0]);
      bool r = SendSyncMessage(mOutgoing[0]++);
      switch (GetIPCChannel()->LastSendError()) {
        case SyncSendError::PreviousTimeout:
        case SyncSendError::SendingCPOWWhileDispatchingSync:
        case SyncSendError::SendingCPOWWhileDispatchingUrgent:
        case SyncSendError::NotConnectedBeforeSend:
        case SyncSendError::CancelledBeforeSend:
          mOutgoing[0]--;
          break;
        default:
          break;
      }
      DEMON_LOG("End SendSyncMessage result=%d", r);
      return r;
    }

    case 3:
      DEMON_LOG("SendUrgentAsyncMessage [%d]", mOutgoing[2]);
      return SendUrgentAsyncMessage(mOutgoing[2]++);

    case 4: {
      DEMON_LOG("Start SendUrgentSyncMessage [%d]", mOutgoing[2]);
      bool r = SendUrgentSyncMessage(mOutgoing[2]++);
      switch (GetIPCChannel()->LastSendError()) {
        case SyncSendError::PreviousTimeout:
        case SyncSendError::SendingCPOWWhileDispatchingSync:
        case SyncSendError::SendingCPOWWhileDispatchingUrgent:
        case SyncSendError::NotConnectedBeforeSend:
        case SyncSendError::CancelledBeforeSend:
          mOutgoing[2]--;
          break;
        default:
          break;
      }
      DEMON_LOG("End SendUrgentSyncMessage result=%d", r);
      return r;
    }

    case 5:
      DEMON_LOG("Cancel");
      GetIPCChannel()->CancelCurrentTransaction();
      return true;
  }
  MOZ_CRASH();
  return false;
}

}  // namespace _ipdltest
}  // namespace mozilla
