/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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;

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