/* 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 "plerror.h"
#include "plgetopt.h"

#include "prinit.h"
#include "prprf.h"
#include "prio.h"
#include "prcvar.h"
#include "prmon.h"
#include "prcmon.h"
#include "prlock.h"
#include "prerror.h"
#include "prinit.h"
#include "prinrval.h"
#include "prthread.h"

static PRLock* ml = NULL;
static PRIntervalTime base;
static PRFileDesc* err = NULL;

typedef struct CMonShared {
  PRInt32 o1, o2;
} CMonShared;

typedef struct MonShared {
  PRMonitor *o1, *o2;
} MonShared;

typedef struct LockShared {
  PRLock *o1, *o2;
  PRCondVar *cv1, *cv2;
} LockShared;

static void LogNow(const char* msg, PRStatus rv) {
  PRIntervalTime now = PR_IntervalNow();
  PR_Lock(ml);
  PR_fprintf(err, "%6ld: %s", (now - base), msg);
  if (PR_FAILURE == rv) {
    PL_FPrintError(err, " ");
  } else {
    PR_fprintf(err, "\n");
  }
  PR_Unlock(ml);
} /* LogNow */

static void Help(void) {
  PR_fprintf(err, "Usage: [-[d][l][m][c]] [-h]\n");
  PR_fprintf(err, "\t-d   debug mode                  (default: FALSE)\n");
  PR_fprintf(err, "\t-l   test with locks             (default: FALSE)\n");
  PR_fprintf(err, "\t-m   tests with monitors         (default: FALSE)\n");
  PR_fprintf(err, "\t-c   tests with cmonitors        (default: FALSE)\n");
  PR_fprintf(err, "\t-h   help\n");
} /* Help */

static void PR_CALLBACK T2CMon(void* arg) {
  PRStatus rv;
  CMonShared* shared = (CMonShared*)arg;

  PR_CEnterMonitor(&shared->o1);
  LogNow("T2 waiting 5 seconds on o1", PR_SUCCESS);
  rv = PR_CWait(&shared->o1, PR_SecondsToInterval(5));
  if (PR_SUCCESS == rv) {
    LogNow("T2 resuming on o1", rv);
  } else {
    LogNow("T2 wait failed on o1", rv);
  }

  rv = PR_CNotify(&shared->o1);
  if (PR_SUCCESS == rv) {
    LogNow("T2 notified o1", rv);
  } else {
    LogNow("T2 notify on o1 failed", rv);
  }

  PR_CExitMonitor(&shared->o1);
} /* T2CMon */

static void PR_CALLBACK T3CMon(void* arg) {
  PRStatus rv;
  CMonShared* shared = (CMonShared*)arg;

  PR_CEnterMonitor(&shared->o2);
  LogNow("T3 waiting 5 seconds on o2", PR_SUCCESS);
  rv = PR_CWait(&shared->o2, PR_SecondsToInterval(5));
  if (PR_SUCCESS == rv) {
    LogNow("T3 resuming on o2", rv);
  } else {
    LogNow("T3 wait failed on o2", rv);
  }
  rv = PR_CNotify(&shared->o2);
  LogNow("T3 notify on o2", rv);
  PR_CExitMonitor(&shared->o2);

} /* T3CMon */

static CMonShared sharedCM;

static void T1CMon(void) {
  PRStatus rv;
  PRThread *t2, *t3;

  PR_fprintf(err, "\n**********************************\n");
  PR_fprintf(err, "         CACHED MONITORS\n");
  PR_fprintf(err, "**********************************\n");

  base = PR_IntervalNow();

  PR_CEnterMonitor(&sharedCM.o1);
  LogNow("T1 waiting 3 seconds on o1", PR_SUCCESS);
  rv = PR_CWait(&sharedCM.o1, PR_SecondsToInterval(3));
  if (PR_SUCCESS == rv) {
    LogNow("T1 resuming on o1", rv);
  } else {
    LogNow("T1 wait on o1 failed", rv);
  }
  PR_CExitMonitor(&sharedCM.o1);

  LogNow("T1 creating T2", PR_SUCCESS);
  t2 = PR_CreateThread(PR_USER_THREAD, T2CMon, &sharedCM, PR_PRIORITY_NORMAL,
                       PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);

  LogNow("T1 creating T3", PR_SUCCESS);
  t3 = PR_CreateThread(PR_USER_THREAD, T3CMon, &sharedCM, PR_PRIORITY_NORMAL,
                       PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);

  PR_CEnterMonitor(&sharedCM.o2);
  LogNow("T1 waiting forever on o2", PR_SUCCESS);
  rv = PR_CWait(&sharedCM.o2, PR_INTERVAL_NO_TIMEOUT);
  if (PR_SUCCESS == rv) {
    LogNow("T1 resuming on o2", rv);
  } else {
    LogNow("T1 wait on o2 failed", rv);
  }
  PR_CExitMonitor(&sharedCM.o2);

  (void)PR_JoinThread(t2);
  (void)PR_JoinThread(t3);

} /* T1CMon */

static void PR_CALLBACK T2Mon(void* arg) {
  PRStatus rv;
  MonShared* shared = (MonShared*)arg;

  PR_EnterMonitor(shared->o1);
  LogNow("T2 waiting 5 seconds on o1", PR_SUCCESS);
  rv = PR_Wait(shared->o1, PR_SecondsToInterval(5));
  if (PR_SUCCESS == rv) {
    LogNow("T2 resuming on o1", rv);
  } else {
    LogNow("T2 wait failed on o1", rv);
  }

  rv = PR_Notify(shared->o1);
  if (PR_SUCCESS == rv) {
    LogNow("T2 notified o1", rv);
  } else {
    LogNow("T2 notify on o1 failed", rv);
  }

  PR_ExitMonitor(shared->o1);
} /* T2Mon */

static void PR_CALLBACK T3Mon(void* arg) {
  PRStatus rv;
  MonShared* shared = (MonShared*)arg;

  PR_EnterMonitor(shared->o2);
  LogNow("T3 waiting 5 seconds on o2", PR_SUCCESS);
  rv = PR_Wait(shared->o2, PR_SecondsToInterval(5));
  if (PR_SUCCESS == rv) {
    LogNow("T3 resuming on o2", rv);
  } else {
    LogNow("T3 wait failed on o2", rv);
  }
  rv = PR_Notify(shared->o2);
  LogNow("T3 notify on o2", rv);
  PR_ExitMonitor(shared->o2);

} /* T3Mon */

static MonShared sharedM;
static void T1Mon(void) {
  PRStatus rv;
  PRThread *t2, *t3;

  PR_fprintf(err, "\n**********************************\n");
  PR_fprintf(err, "            MONITORS\n");
  PR_fprintf(err, "**********************************\n");

  sharedM.o1 = PR_NewMonitor();
  sharedM.o2 = PR_NewMonitor();

  base = PR_IntervalNow();

  PR_EnterMonitor(sharedM.o1);
  LogNow("T1 waiting 3 seconds on o1", PR_SUCCESS);
  rv = PR_Wait(sharedM.o1, PR_SecondsToInterval(3));
  if (PR_SUCCESS == rv) {
    LogNow("T1 resuming on o1", rv);
  } else {
    LogNow("T1 wait on o1 failed", rv);
  }
  PR_ExitMonitor(sharedM.o1);

  LogNow("T1 creating T2", PR_SUCCESS);
  t2 = PR_CreateThread(PR_USER_THREAD, T2Mon, &sharedM, PR_PRIORITY_NORMAL,
                       PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);

  LogNow("T1 creating T3", PR_SUCCESS);
  t3 = PR_CreateThread(PR_USER_THREAD, T3Mon, &sharedM, PR_PRIORITY_NORMAL,
                       PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);

  PR_EnterMonitor(sharedM.o2);
  LogNow("T1 waiting forever on o2", PR_SUCCESS);
  rv = PR_Wait(sharedM.o2, PR_INTERVAL_NO_TIMEOUT);
  if (PR_SUCCESS == rv) {
    LogNow("T1 resuming on o2", rv);
  } else {
    LogNow("T1 wait on o2 failed", rv);
  }
  PR_ExitMonitor(sharedM.o2);

  (void)PR_JoinThread(t2);
  (void)PR_JoinThread(t3);

  PR_DestroyMonitor(sharedM.o1);
  PR_DestroyMonitor(sharedM.o2);

} /* T1Mon */

static void PR_CALLBACK T2Lock(void* arg) {
  PRStatus rv;
  LockShared* shared = (LockShared*)arg;

  PR_Lock(shared->o1);
  LogNow("T2 waiting 5 seconds on o1", PR_SUCCESS);
  rv = PR_WaitCondVar(shared->cv1, PR_SecondsToInterval(5));
  if (PR_SUCCESS == rv) {
    LogNow("T2 resuming on o1", rv);
  } else {
    LogNow("T2 wait failed on o1", rv);
  }

  rv = PR_NotifyCondVar(shared->cv1);
  if (PR_SUCCESS == rv) {
    LogNow("T2 notified o1", rv);
  } else {
    LogNow("T2 notify on o1 failed", rv);
  }

  PR_Unlock(shared->o1);
} /* T2Lock */

static void PR_CALLBACK T3Lock(void* arg) {
  PRStatus rv;
  LockShared* shared = (LockShared*)arg;

  PR_Lock(shared->o2);
  LogNow("T3 waiting 5 seconds on o2", PR_SUCCESS);
  rv = PR_WaitCondVar(shared->cv2, PR_SecondsToInterval(5));
  if (PR_SUCCESS == rv) {
    LogNow("T3 resuming on o2", rv);
  } else {
    LogNow("T3 wait failed on o2", rv);
  }
  rv = PR_NotifyCondVar(shared->cv2);
  LogNow("T3 notify on o2", rv);
  PR_Unlock(shared->o2);

} /* T3Lock */

/*
** Make shared' a static variable for Win16
*/
static LockShared sharedL;

static void T1Lock(void) {
  PRStatus rv;
  PRThread *t2, *t3;
  sharedL.o1 = PR_NewLock();
  sharedL.o2 = PR_NewLock();
  sharedL.cv1 = PR_NewCondVar(sharedL.o1);
  sharedL.cv2 = PR_NewCondVar(sharedL.o2);

  PR_fprintf(err, "\n**********************************\n");
  PR_fprintf(err, "             LOCKS\n");
  PR_fprintf(err, "**********************************\n");

  base = PR_IntervalNow();

  PR_Lock(sharedL.o1);
  LogNow("T1 waiting 3 seconds on o1", PR_SUCCESS);
  rv = PR_WaitCondVar(sharedL.cv1, PR_SecondsToInterval(3));
  if (PR_SUCCESS == rv) {
    LogNow("T1 resuming on o1", rv);
  } else {
    LogNow("T1 wait on o1 failed", rv);
  }
  PR_Unlock(sharedL.o1);

  LogNow("T1 creating T2", PR_SUCCESS);
  t2 = PR_CreateThread(PR_USER_THREAD, T2Lock, &sharedL, PR_PRIORITY_NORMAL,
                       PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);

  LogNow("T1 creating T3", PR_SUCCESS);
  t3 = PR_CreateThread(PR_USER_THREAD, T3Lock, &sharedL, PR_PRIORITY_NORMAL,
                       PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);

  PR_Lock(sharedL.o2);
  LogNow("T1 waiting forever on o2", PR_SUCCESS);
  rv = PR_WaitCondVar(sharedL.cv2, PR_INTERVAL_NO_TIMEOUT);
  if (PR_SUCCESS == rv) {
    LogNow("T1 resuming on o2", rv);
  } else {
    LogNow("T1 wait on o2 failed", rv);
  }
  PR_Unlock(sharedL.o2);

  (void)PR_JoinThread(t2);
  (void)PR_JoinThread(t3);

  PR_DestroyLock(sharedL.o1);
  PR_DestroyLock(sharedL.o2);
  PR_DestroyCondVar(sharedL.cv1);
  PR_DestroyCondVar(sharedL.cv2);
} /* T1Lock */

static PRIntn PR_CALLBACK RealMain(PRIntn argc, char** argv) {
  PLOptStatus os;
  PLOptState* opt = PL_CreateOptState(argc, argv, "dhlmc");
  PRBool locks = PR_FALSE, monitors = PR_FALSE, cmonitors = PR_FALSE;

  err = PR_GetSpecialFD(PR_StandardError);

  while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
    if (PL_OPT_BAD == os) {
      continue;
    }
    switch (opt->option) {
      case 'd': /* debug mode (noop) */
        break;
      case 'l': /* locks */
        locks = PR_TRUE;
        break;
      case 'm': /* monitors */
        monitors = PR_TRUE;
        break;
      case 'c': /* cached monitors */
        cmonitors = PR_TRUE;
        break;
      case 'h': /* needs guidance */
      default:
        Help();
        return 2;
    }
  }
  PL_DestroyOptState(opt);

  ml = PR_NewLock();
  if (locks) {
    T1Lock();
  }
  if (monitors) {
    T1Mon();
  }
  if (cmonitors) {
    T1CMon();
  }

  PR_DestroyLock(ml);

  PR_fprintf(err, "Done!\n");
  return 0;
} /* main */

int main(int argc, char** argv) {
  PRIntn rv;

  rv = PR_Initialize(RealMain, argc, argv, 0);
  return rv;
} /* main */
/* xnotify.c */
