#include <stdio.h>
#include <pthread.h>

#ifndef TRUE
#define TRUE    1
#define FALSE   0
#endif

/* memory barriers (for newer x86 and x86_64) */
#define mb() asm volatile ("mfence" ::: "memory")
#define rmb() asm volatile ("lfence" ::: "memory")
#define wmb() asm volatile ("sfence" ::: "memory")

pthread_mutexattr_t fast_mutexattr;

typedef struct os_event_struct os_event_struct_t;
typedef os_event_struct_t*     os_event_t;
struct os_event_struct {
    pthread_mutex_t os_mutex;
    unsigned long   is_set;
    unsigned long   signal_count;
    pthread_cond_t  cond_var;
};


os_event_t      event;

unsigned long ut_delay( unsigned long delay );

void os_event_reset();
void os_event_wait();
void os_event_set();

unsigned long ut_rnd_ulint_counter = 65654363;
#define UT_RND1                 151117737
#define UT_RND2                 119785373
unsigned long ut_always_false = FALSE;

volatile unsigned long  lock_word;
unsigned long lock_counter;

unsigned long some_wait;

int thread_main(int thread_num);

int main( int argc, char *argv[] )
{
    int t_num;
    pthread_t thread[2];
    unsigned long old_counter;

    some_wait = 100; /* lock holding period */

    /* init event */
    pthread_mutexattr_init(&fast_mutexattr);
    pthread_mutexattr_settype(&fast_mutexattr, PTHREAD_MUTEX_ADAPTIVE_NP);

    event = (os_event_t) malloc(sizeof(struct os_event_struct));
    pthread_mutex_init(&(event->os_mutex), &fast_mutexattr);

    pthread_cond_init(&(event->cond_var), NULL);

    event->is_set = FALSE;
    event->signal_count = 0;

    /* init flags */
    lock_word = 0;
    lock_counter = 0;

    old_counter = lock_counter;

    /* exec thread */
    for (t_num=0; t_num < 2; t_num++) {
	pthread_create( &thread[t_num], NULL, (void *)thread_main, (void *)t_num );
    }

    while(1) {
	printf("counter: %d\n", lock_counter);
	old_counter = lock_counter;
	sleep(5);
	if (lock_counter == old_counter) {
	    printf("counter: %d\n", lock_counter);
	    printf("signals: %d\n", event->signal_count);
	    printf("freezed..\n");
	    exit(1);
	}
    }
}

int thread_main( int thread_num )
{
    unsigned long from_word, to_word;
    unsigned long rnd, i;

    switch(thread_num) {
	case 0: /* thread_num == 0 */
	    from_word = 0;
	    to_word = 1;
	    break;
	default: /* thread_num == 1*/
	    from_word = 1;
	    to_word = 0;
    }

    while(1) {

    loop:
	i = 0;

	mb(); /* by way of precaution against memory ordering */
	while (lock_word != from_word && i < 20) {
	    rnd = (ut_rnd_ulint_counter = UT_RND1 * ut_rnd_ulint_counter + UT_RND2) % 6;
	    ut_delay(rnd);
	    i++;
	    mb(); /* by way of precaution against memory ordering */
	}

	mb(); /* by way of precaution against memory ordering */
	if (lock_word == from_word) {
	    /* succcess */
	    lock_counter++;

	    /* exec something... */
	    ut_delay(some_wait);

	    /* unlock */
	    lock_word = to_word;
	    os_event_set();
	    continue;
	}

	os_event_reset();

	mb(); /* by way of precaution against memory ordering */
	if (lock_word == from_word) {
	    /* succcess */
	    lock_counter++;

	    /* exec something... */
	    ut_delay(some_wait);

	    /* unlock */
	    lock_word = to_word;
	    os_event_set();
	    continue;
	}
	else {
	    /* failure */
	    os_event_wait();
	    goto loop;
	}
    }
}

void os_event_reset()
{
    pthread_mutex_lock(&(event->os_mutex));

    if (!event->is_set) {
	/* Do nothing */
    } else {
	event->is_set = FALSE;
    }

    pthread_mutex_unlock(&(event->os_mutex));
}

void os_event_wait()
{
    unsigned long old_signal_count;

    pthread_mutex_lock(&(event->os_mutex));

    old_signal_count = event->signal_count;

    for (;;) {
	if (event->is_set == TRUE
	    || event->signal_count != old_signal_count) {

	    pthread_mutex_unlock(&(event->os_mutex));
	    /* Ok, we may return */
	    return;
	}
	pthread_cond_wait(&(event->cond_var), &(event->os_mutex));
    }
}

void os_event_set()
{
    pthread_mutex_lock(&(event->os_mutex));

    if (event->is_set) {
	/* Do nothing */
    } else {
	event->is_set = TRUE;
	event->signal_count += 1;
	pthread_cond_broadcast(&(event->cond_var));
    }

    pthread_mutex_unlock(&(event->os_mutex));
}

unsigned long ut_delay( unsigned long delay )
{
    unsigned long i,j;

    j = 0;

    for (i = 0; i < delay * 50; i++) {
	j += i;
    }

    if (ut_always_false) {
	ut_always_false = j;
    }

    return(j);
}
