/* 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/. */


/*
 *
 * RWLock tests
 *
 *	Several threads are created to access and modify data arrays using
 * 	PRRWLocks for synchronization. Two data arrays, array_A and array_B, are
 *	initialized with random data and a third array, array_C, is initialized
 *	with the sum of the first 2 arrays.
 *
 *	Each one of the threads acquires a read lock to verify that the sum of
 *	the arrays A and B is equal to array C, and acquires a write lock to
 *	consistently update arrays A and B so that their is equal to array C.
 *		
 */
 
#include "nspr.h"
#include "plgetopt.h"
#include "prrwlock.h"

static int _debug_on;
static void rwtest(void *args);
static PRInt32 *array_A,*array_B,*array_C;
static void update_array(void);
static void check_array(void);

typedef struct thread_args {
	PRRWLock	*rwlock;
	PRInt32		loop_cnt;
} thread_args;

PRFileDesc  *output;
PRFileDesc  *errhandle;

#define	DEFAULT_THREAD_CNT	4
#define	DEFAULT_LOOP_CNT	100
#define	TEST_ARRAY_SIZE		100

int main(int argc, char **argv)
{
    PRInt32 cnt;
	PRStatus rc;
	PRInt32 i;

	PRInt32 thread_cnt = DEFAULT_THREAD_CNT;
	PRInt32 loop_cnt = DEFAULT_LOOP_CNT;
	PRThread **threads;
	thread_args *params;
	PRRWLock	*rwlock1;

	PLOptStatus os;
	PLOptState *opt = PL_CreateOptState(argc, argv, "dt:c:");

	while (PL_OPT_EOL != (os = PL_GetNextOpt(opt)))
    {
		if (PL_OPT_BAD == os) continue;
        switch (opt->option)
        {
        case 'd':  /* debug mode */
			_debug_on = 1;
            break;
        case 't':  /* thread count */
            thread_cnt = atoi(opt->value);
            break;
        case 'c':  /* loop count */
            loop_cnt = atoi(opt->value);
            break;
         default:
            break;
        }
    }
	PL_DestroyOptState(opt);

	PR_SetConcurrency(4);

    output = PR_GetSpecialFD(PR_StandardOutput);
    errhandle = PR_GetSpecialFD(PR_StandardError);

	rwlock1 = PR_NewRWLock(0,"Lock 1");
	if (rwlock1 == NULL) {
		PR_fprintf(errhandle, "PR_NewRWLock failed - error %d\n",
								PR_GetError());
		return 1;
	}

	threads = (PRThread**) PR_CALLOC(sizeof(PRThread*) * thread_cnt);
	params = (thread_args *) PR_CALLOC(sizeof(thread_args) * thread_cnt);

	/*
	 * allocate and initialize data arrays
	 */
	array_A =(PRInt32 *) PR_MALLOC(sizeof(PRInt32) * TEST_ARRAY_SIZE);
	array_B =(PRInt32 *) PR_MALLOC(sizeof(PRInt32) * TEST_ARRAY_SIZE);
	array_C =(PRInt32 *) PR_MALLOC(sizeof(PRInt32) * TEST_ARRAY_SIZE);
	cnt = 0;
	for (i=0; i < TEST_ARRAY_SIZE;i++) {
		array_A[i] = cnt++;
		array_B[i] = cnt++;
		array_C[i] = array_A[i] + array_B[i];
	}

	if (_debug_on)
		PR_fprintf(output,"%s: thread_cnt = %d loop_cnt = %d\n", argv[0],
							thread_cnt, loop_cnt);
	for(cnt = 0; cnt < thread_cnt; cnt++) {
		PRThreadScope scope;

		params[cnt].rwlock = rwlock1;
		params[cnt].loop_cnt = loop_cnt;

		/*
		 * create LOCAL and GLOBAL threads alternately
		 */
		if (cnt & 1)
			scope = PR_LOCAL_THREAD;
		else
			scope = PR_GLOBAL_THREAD;

		threads[cnt] = PR_CreateThread(PR_USER_THREAD,
						  rwtest, &params[cnt],
						  PR_PRIORITY_NORMAL,
						  scope,
						  PR_JOINABLE_THREAD,
						  0);
		if (threads[cnt] == NULL) {
			PR_fprintf(errhandle, "PR_CreateThread failed - error %d\n",
								PR_GetError());
			PR_ProcessExit(2);
		}
		if (_debug_on)
			PR_fprintf(output,"%s: created thread = %p\n", argv[0],
										threads[cnt]);
	}

	for(cnt = 0; cnt < thread_cnt; cnt++) {
    	rc = PR_JoinThread(threads[cnt]);
		PR_ASSERT(rc == PR_SUCCESS);

	}

	PR_DELETE(threads);
	PR_DELETE(params);

	PR_DELETE(array_A);	
	PR_DELETE(array_B);	
	PR_DELETE(array_C);	

	PR_DestroyRWLock(rwlock1);

	
	printf("PASS\n");
	return 0;
}

static void rwtest(void *args)
{
    PRInt32 index;
	thread_args *arg = (thread_args *) args;


	for (index = 0; index < arg->loop_cnt; index++) {

		/*
		 * verify sum, update arrays and verify sum again
		 */

		PR_RWLock_Rlock(arg->rwlock);
		check_array();
		PR_RWLock_Unlock(arg->rwlock);

		PR_RWLock_Wlock(arg->rwlock);
		update_array();
		PR_RWLock_Unlock(arg->rwlock);

		PR_RWLock_Rlock(arg->rwlock);
		check_array();
		PR_RWLock_Unlock(arg->rwlock);
	}
	if (_debug_on)
		PR_fprintf(output,
		"Thread[0x%x] lock = 0x%x exiting\n",
				PR_GetCurrentThread(), arg->rwlock);

}

static void check_array(void)
{
PRInt32 i;

	for (i=0; i < TEST_ARRAY_SIZE;i++)
		if (array_C[i] != (array_A[i] + array_B[i])) {
			PR_fprintf(output, "Error - data check failed\n");
			PR_ProcessExit(1);
		}
}

static void update_array(void)
{
PRInt32 i;

	for (i=0; i < TEST_ARRAY_SIZE;i++) {
		array_A[i] += i;
		array_B[i] -= i;
	}
}