#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "lib.h"
#define DMID 10
#include "debug.h"

#ifdef DO_THREADS
#include "thread.h"
#endif 

#include "rwlock.h"
#ifndef MUTEX_TIME
#define MUTEX_TIME 100000
#endif

#ifdef DO_THREADS
static Mutex *m;
#else 
static char *m=NULL;
#endif

#ifndef TRUE
#define FALSE 0 
#define TRUE (!FALSE)
#endif
#include "emsg.h"

static int mutex_i;
static int (*mutex_cb)(int vm, int isget, int seconds);
int rwlock_set_mutex(int (*cb)(int vm, int isget, int seconds), int m)
{
		mutex_i = m;
		mutex_cb = cb;
		return TRUE;
}

int rwlock_mutex_wait(void)
{
	if (mutex_cb) return mutex_cb(mutex_i,TRUE,600);
#ifdef DO_THREADS
	if (m==NULL) lib_abort();	
	if (!th_mutex_wait(m,MUTEX_TIME)) { /* 10 minutes */
		if (!th_mutex_wait(m,10000)) { /* another 10 seconds, incase it came back to life*/
			emsg("Cannot get mutex, fatal problem, dieing lib_lock_mutex\n");
			lib_abort();
		}
	}
#endif
	return TRUE;
}
void rwlock_mutex_release(void)
{
	if (mutex_cb) { mutex_cb(mutex_i,FALSE,600); return; }
#ifdef DO_THREADS
	th_mutex_release(m);
#endif
}

void rwlock_init(void)
{
#ifdef DO_THREADS
	m = th_mutex_new("rwlock");
#endif
}

static Rwlock *locks;
Rwlock *rwlock_get_nowait(char *obj, int exclusive, int line, int *held, o_thread *hid, int *age)
{
	Rwlock *r;
	*age = 0;
	rwlock_mutex_wait();
	for (r=locks;r!=NULL;r=r->next) {
		if (strcmpnc(r->name,obj)==0) {
			*held = r->held;
			*hid = r->hid;
			*age = (int)(time(NULL) - r->start);
			if (exclusive || r->exclusive) {
				rwlock_mutex_release();
				return NULL; 
			}
			r->nref++;
			rwlock_mutex_release();
			return r;
		}
	}
	dmsg("rwlock: got new lock (%s)\n",obj);
	r = calloc(1,sizeof(Rwlock));
	r->next = locks;
	locks = r;
	r->name = strdup(obj);
	r->hid = th_oid();
	r->nref = 1;
	r->start = time(NULL);
	r->held = line;
	r->exclusive = exclusive;
	rwlock_mutex_release();
	return r;
}
Rwlock *rwlock_wait_simple(char *obj, int timeout, int exclusive)
{
	int held;
	o_thread hid;
	int age;
	return rwlock_wait(obj,timeout,exclusive,__LINE__,&held,&hid,&age);
}
Rwlock *rwlock_wait(char *obj, int timeout, int exclusive, int line, int *held, o_thread *hid, int *age)
{
	Rwlock *r=NULL;
	time_t start = time(NULL);

	// Always try at least once.
	r = rwlock_get_nowait(obj,exclusive,line,held,hid,age);
	if (!r) for (;time(NULL)-start<timeout;) {
		r = rwlock_get_nowait(obj,exclusive,line,held,hid,age);
		if (r) break;
		if (timeout==0) break;
		if (th_equal(*hid,th_oid())) {
			break; // We hold it ourselves, so just giveup... break;
		}
		lib_msleep(100);
	}
//imsg("rwlock_wait finished %s (%s)\n",r ? "GotLock":"Not",obj);
	return r;
}
int rwlock_release(Rwlock *me)
{
	Rwlock **last,*r=NULL;
	if (me==NULL) return TRUE;
	rwlock_mutex_wait();
	me->nref--;
	dmsg("rwlock: free lock (%s) %d seconds, line=%d\n",me->name,time(NULL)-me->start,me->held);
//imsg("rwlock_released  (%s) ref=%d\n",me->name,me->nref);
	if (me->nref>0) {
		rwlock_mutex_release();
		return TRUE;	
	}
	last = &locks;
	for (r=locks;r!=NULL;r=r->next) {
		if (r==me) {
			*last = r->next;
			free(r->name);
			free(r);
			break;
		}
		last = &r->next;
	}
	/* this was invalid due to free() may modify 'r' 
	if (r==NULL) {
		lib_abort();
	}
	*/
	rwlock_mutex_release();
	return TRUE;
}

