/* ``Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */


#ifndef __WIN32__
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "testcase_driver.h"
#include "allocator_test.h"

#ifdef __WIN32__
#undef HAVE_VSNPRINTF
#define HAVE_VSNPRINTF 1
#define vsnprintf _vsnprintf
#endif

#ifndef HAVE_VSNPRINTF
#define HAVE_VSNPRINTF 0
#endif

#define NO_OF_ALLOC_SEQS 6
#define NO_OF_THREADS (18)
#define NO_OF_BLOCKS 10
#define NO_OF_OPS_PER_BL 200
#define SBC_THRESHOLD 8192

#define BLOCK_ID(TN, BN) ((TN) << 4 | (BN))

#define ERR_BUF_SZ 4096
static char err_buf[ERR_BUF_SZ];
static volatile int tc_failed;
static int dead_thread_no;
static erts_mutex tc_mutex;
static erts_cond tc_cond;

static void exit_thread(int t_no, int do_lock)
{
    if (do_lock)
	THR_MTX_LOCK(tc_mutex);

    while (dead_thread_no >= 0)
	THR_COND_WAIT(tc_cond, tc_mutex);
    dead_thread_no = t_no;

    THR_COND_BCAST(tc_cond);
    THR_MTX_UNLOCK(tc_mutex);
    THR_EXIT(NULL);
}

static void fail(int t_no, char *frmt, ...)
{
    char buf[10];
    size_t bufsz = sizeof(buf);
    va_list va;

    THR_MTX_LOCK(tc_mutex);

    va_start(va, frmt);
#if HAVE_VSNPRINTF
    vsnprintf(err_buf, ERR_BUF_SZ, frmt, va);
#else
    vsprintf(err_buf, frmt, va);
#endif
    va_end(va);

    tc_failed = 1;

    if (enif_getenv("ERL_ABORT_ON_FAILURE", buf, &bufsz) == 0
	&& strcmp("true", buf) == 0) {
	fprintf(stderr, "Testcase \"%s\" failed: %s\n",
		testcase_name(), err_buf);
	abort();
    }

    exit_thread(t_no, 0);
}

static Allctr_t *alloc_not_ts = NULL;
static Allctr_t *alloc_ts_1 = NULL;
static Allctr_t *alloc_ts_2 = NULL;

static void stop_allocators(void)
{
    if (alloc_not_ts) {
	STOP_ALC(alloc_not_ts);
	alloc_not_ts = NULL;
    }
    if (alloc_ts_1) {
	STOP_ALC(alloc_ts_1);
	alloc_ts_1 = NULL;
    }
    if (alloc_ts_2) {
	STOP_ALC(alloc_ts_2);
	alloc_ts_2 = NULL;
    }
}


void *thread_func(void *arg);

typedef struct {
    Allctr_t *a;
    int t_no;
    int no_ops_per_bl;
} ThreadData;


char *
testcase_name(void)
{
    return "threads";
}

void
testcase_cleanup(TestCaseState_t *tcs)
{
    stop_allocators();
}

void
testcase_run(TestCaseState_t *tcs)
{
    struct {
	erts_thread tid;
	ThreadData arg;
    } threads[NO_OF_THREADS+1] = {{0}};
    int no_threads;
    int i;
    char sbct_buf[10];
    char *argv_org[] = {"-tasaobf", "-tmmsbc5000", "-tmmmbc5000", "-tsbct",
			&sbct_buf[0], NULL};
    char *argv[sizeof(argv_org)/sizeof(argv_org[0])];

    if (!IS_THREADS_ENABLED)
	testcase_skipped(tcs, "Threads not enabled");

    alloc_not_ts = NULL;
    alloc_ts_1 = NULL;
    alloc_ts_2 = NULL;

    err_buf[0] = '\0';

    sprintf(sbct_buf, "%d", SBC_THRESHOLD/1024);
    
    memcpy((void *) argv, argv_org, sizeof(argv_org));
    alloc_not_ts = START_ALC("threads_not_ts", 0, argv);
    ASSERT(tcs, alloc_not_ts);
    memcpy((void *) argv, argv_org, sizeof(argv_org));
    alloc_ts_1 = START_ALC("threads_ts_1", 1, argv);
    ASSERT(tcs, alloc_ts_1);
    memcpy((void *) argv, argv_org, sizeof(argv_org));
    alloc_ts_2 = START_ALC("threads_ts_2", 1, argv);
    ASSERT(tcs, alloc_ts_2);

    ASSERT(tcs, !IS_ALLOC_THREAD_SAFE(alloc_not_ts));
    ASSERT(tcs, IS_ALLOC_THREAD_SAFE(alloc_ts_1));
    ASSERT(tcs, IS_ALLOC_THREAD_SAFE(alloc_ts_2));

    tc_mutex = THR_MTX_CREATE();
    tc_cond = THR_COND_CREATE();

    THR_MTX_LOCK(tc_mutex);

    dead_thread_no = -1;
    no_threads = 0;

    for(i = 1; i <= NO_OF_THREADS; i++) {
	char *alc;

	threads[i].arg.no_ops_per_bl = NO_OF_OPS_PER_BL;

	if (i == 1) {
	    alc = "threads_not_ts";
	    threads[i].arg.no_ops_per_bl *= 2;
	    threads[i].arg.a = alloc_not_ts;
	}
	else if (i % 2 == 0) {
	    alc = "threads_ts_1";
	    threads[i].arg.a = alloc_ts_1;
	}
	else {
	    alc = "threads_ts_2";
	    threads[i].arg.a = alloc_ts_2;
	}
	threads[i].arg.t_no = i;

	threads[i].tid = THR_CREATE(thread_func, (void *) &threads[i].arg);
	if (threads[i].tid) {
	    testcase_printf(tcs, "Successfully created thread %d "
			    "using %s_alloc\n", i, alc);
	    no_threads++;
	}
	else {
	    tc_failed = 1;
	    sprintf(err_buf, "Failed to create thread %d\n", i);
	    break;
	}

    }

    while (no_threads) {
	THR_COND_WAIT(tc_cond, tc_mutex);
	if (dead_thread_no >= 0) {
	    no_threads--;
	    THR_JOIN(threads[dead_thread_no].tid);
	    testcase_printf(tcs, "Thread %d died\n", dead_thread_no);
	    dead_thread_no = -1;
	    THR_COND_BCAST(tc_cond);
	}
    }

    THR_MTX_UNLOCK(tc_mutex);
    THR_MTX_DESTROY(tc_mutex);
    THR_COND_DESTROY(tc_cond);

    stop_allocators();

    if (tc_failed)
	testcase_failed(tcs, "%s", err_buf);
}

Ulong alloc_seq_1[] = {
  17,
  SBC_THRESHOLD*2,
  SBC_THRESHOLD*20,
  SBC_THRESHOLD*2,
  17,
  0
};

Ulong alloc_seq_2[] = {
  SBC_THRESHOLD*20,
  SBC_THRESHOLD*2,
  17,
  SBC_THRESHOLD*2,
  SBC_THRESHOLD*20,
  0
};

Ulong alloc_seq_3[] = {
  1,
  SBC_THRESHOLD/10,
  SBC_THRESHOLD/9,
  SBC_THRESHOLD/8,
  SBC_THRESHOLD/7,
  SBC_THRESHOLD/6,
  SBC_THRESHOLD/5,
  SBC_THRESHOLD/4,
  SBC_THRESHOLD/3,
  SBC_THRESHOLD/2,
  SBC_THRESHOLD/1,
  SBC_THRESHOLD*1,
  SBC_THRESHOLD*2,
  SBC_THRESHOLD*3,
  SBC_THRESHOLD*4,
  SBC_THRESHOLD*5,
  SBC_THRESHOLD*6,
  SBC_THRESHOLD*7,
  SBC_THRESHOLD*8,
  SBC_THRESHOLD*9,
  SBC_THRESHOLD*10,
  SBC_THRESHOLD*9,
  SBC_THRESHOLD*8,
  SBC_THRESHOLD*7,
  SBC_THRESHOLD*6,
  SBC_THRESHOLD*5,
  SBC_THRESHOLD*4,
  SBC_THRESHOLD*3,
  SBC_THRESHOLD*2,
  SBC_THRESHOLD*1,
  SBC_THRESHOLD/2,
  SBC_THRESHOLD/3,
  SBC_THRESHOLD/4,
  SBC_THRESHOLD/5,
  SBC_THRESHOLD/6,
  SBC_THRESHOLD/7,
  SBC_THRESHOLD/8,
  SBC_THRESHOLD/9,
  SBC_THRESHOLD/10,
  1,
  0
};

Ulong alloc_seq_4[] = {
  SBC_THRESHOLD*2,
  SBC_THRESHOLD*3,
  SBC_THRESHOLD*7,
  SBC_THRESHOLD*8,
  SBC_THRESHOLD*5,
  SBC_THRESHOLD*6,
  SBC_THRESHOLD*1,
  SBC_THRESHOLD*10,
  SBC_THRESHOLD*4,
  SBC_THRESHOLD*2,
  0
};

Ulong alloc_seq_5[] = {
  SBC_THRESHOLD/2,
  SBC_THRESHOLD/3,
  SBC_THRESHOLD/7,
  SBC_THRESHOLD/8,
  SBC_THRESHOLD/5,
  SBC_THRESHOLD/6,
  SBC_THRESHOLD/1,
  SBC_THRESHOLD/10,
  SBC_THRESHOLD/4,
  SBC_THRESHOLD/2,
  SBC_THRESHOLD/3,
  SBC_THRESHOLD/7,
  SBC_THRESHOLD/8,
  SBC_THRESHOLD/5,
  SBC_THRESHOLD/6,
  SBC_THRESHOLD/1,
  SBC_THRESHOLD/10,
  SBC_THRESHOLD/4,
  SBC_THRESHOLD/2,
  0
};

Ulong alloc_seq_6[] = {
    1, 50, 100, 50, 23, 46, 2345, 23, 54, 2, 0
};

Ulong *alloc_seqs[NO_OF_ALLOC_SEQS] = {
  alloc_seq_1,
  alloc_seq_2,
  alloc_seq_3,
  alloc_seq_4,
  alloc_seq_5,
  alloc_seq_6
};

typedef struct {
    unsigned char *p;
    Ulong  s;
    int   i;
    Ulong *as;
} block;

#define CHECK_BLOCK_DATA(T, P, S, D) \
  check_block_data(__FILE__, __LINE__, (T), (P), (S), (D))

static void
check_block_data(char *file, int line, int t_no,
		 unsigned char *p, Ulong sz, int d)
{
    Ulong i;
    for (i = 0; i < sz; i++)
	if (p[i] != (unsigned char) d)
	    fail(t_no, "%s:%d: Thread no %d found clobbered data! "
		 "found id=%d; expected id=%d\n",
		 file, line, t_no, (int) p[i], d);
}

static void
alloc_op(int t_no, Allctr_t *a, block *bp, int id, int clean_up)
{
    if (tc_failed)
	exit_thread(t_no, 1);

    if(bp->p)
	CHECK_BLOCK_DATA(t_no, bp->p, bp->s, id);

    if(bp->as[bp->i] == 0 || clean_up) {
	FREE(a, bp->p);
	bp->p = NULL;
	bp->s = 0;
	bp->i = 0; /* start from the beginning again */
	return;
    }

    if(!bp->p) {
	bp->s = bp->as[bp->i];
	bp->p = (unsigned char *) ALLOC(a, bp->s);
	if(!bp->p)
	    fail(t_no, "ALLOC(%lu) failed [id=%d])\n", bp->s, id);
	memset((void *) bp->p, (unsigned char)id, (size_t) bp->s);
    }
    else {
	unsigned char *p = (unsigned char *) REALLOC(a, bp->p, bp->as[bp->i]);
	if(!p)
	    fail(t_no, "REALLOC(0x%lx, %lu) failed [id=%d]\n",
		 (Ulong) bp->p, bp->as[bp->i], id);

	if(bp->s < bp->as[bp->i]) {
	    CHECK_BLOCK_DATA(t_no, p, bp->s, id);
	    memset((void *) p, (unsigned char)id, (size_t) bp->as[bp->i]);
	}
	else
	    CHECK_BLOCK_DATA(t_no, p, bp->as[bp->i], id);

	bp->s = bp->as[bp->i];
	bp->p = p;
    }

    bp->i++;
}


void *
thread_func(void *arg)
{
    int i, j;
    ThreadData *td = ((ThreadData *) arg);
    block bs[NO_OF_BLOCKS];

    for(i = 0; i < NO_OF_BLOCKS; i++) {
	bs[i].p = NULL;
	bs[i].s = 0;
	bs[i].i = 0;
	bs[i].as = alloc_seqs[i % NO_OF_ALLOC_SEQS];
    }

    for(i = 0; i < td->no_ops_per_bl; i++) {

	for(j = 0; j < NO_OF_BLOCKS; j++)
	    alloc_op(td->t_no, td->a, &bs[j], BLOCK_ID(td->t_no, j), 0);
    }

    for(j = 0; j < NO_OF_BLOCKS; j++)
	alloc_op(td->t_no, td->a, &bs[j], BLOCK_ID(td->t_no, j), 1);

    exit_thread(td->t_no, 1);
    return NULL;
}

ERL_NIF_INIT(threads, testcase_nif_funcs, testcase_nif_init,
	     NULL, NULL, NULL);
