/* Copyright (c) 2000,2001 Sinby.com
   All Rights Reserved

   This project is covered by the GNU General Public License. */

/* $Id: jthread.c,v 1.4 2002/05/17 07:25:34 ryos Exp $ */

#include "debug.h"
#include "jthread.h"

#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/diag.h>
#include <sys/time.h>
#include <signal.h>

#define MARGIN sizeof(int)
#define MAGIC_TOP	0xFEEDBAC0
#define MAGIC_END	0xFEEDBAC1

struct jthread_system jthread_system;

static inline void
JTHREAD_LINK_EN_QUEUE(jthread_t tid,void (*func)(void *), int daemon, void *cookie)
{
	//tid->thread_handle is already set
	//tid->thread is already set
	tid->status = THREAD_STAT_ALIVE;
	tid->func = func;
	tid->daemon = daemon;
	tid->cookie = cookie;
	tid->stopCount = 0;
	//diag_printf("enqueue tid:0x%x tid->thread_handle:0x%x self:0x%x\n", tid, tid->thread_handle , cyg_thread_self() );

	cyg_mutex_lock(&jthread_system.threadLock);
	tid->next = jthread_system.terminal.next;
	tid->prev = (jthread_t)&jthread_system.terminal;

	jthread_system.terminal.next->prev = tid;
	jthread_system.terminal.next = tid;

	jthread_system.threadN++;
	if ( daemon ) {
		jthread_system.daemonN++;
	}
	cyg_mutex_unlock(&jthread_system.threadLock);
}

static inline void
JTHREAD_LINK_DE_QUEUE(jthread_t death_thread)
{
	cyg_mutex_lock(&jthread_system.threadLock);

#ifdef DEBUG
	{
		jthread_t tid;
		int i;
		int found;

		found = 0;
		tid = jthread_system.terminal.next;
		for( i = 0 ; i < jthread_system.threadN ; i++ , tid = tid->next) {
			if ( tid == death_thread ) {
				found++;
			}
		}
		assert(found == 1);
		assert(tid == (jthread_t)&jthread_system.anchor);
	}
#endif
	death_thread->next->prev = death_thread->prev;
	death_thread->prev->next = death_thread->next;

	jthread_system.threadN--;
	if ( death_thread->daemon ) {
		jthread_system.daemonN--;
	}

	cyg_mutex_unlock(&jthread_system.threadLock);
}


static inline void
JTHREAD_CHANGE_STATUS(jthread_t tid,unsigned long old_status,unsigned long event,unsigned long new_status) 
{
	if ( tid->status != old_status ) {
		diag_printf("tid 0x%x status 0x%x old_status 0x%x event 0x%x new status 0x%x\n",tid,tid->status,old_status,event,new_status);
		assert(tid->status == old_status);
	}
#ifdef DEBUG
	tid->status_log <<= THREAD_STAT_SIZE;
	tid->status_log |= (THREAD_STAT_MASK & old_status);
	tid->status_log <<= THREAD_EVENT_SIZE;
	tid->status_log |= (THREAD_EVENT_MASK & event);
#endif
	tid->status = new_status;
}

void 
jthread_suspendall(void)
{
	cyg_scheduler_lock();
}

void
jthread_unsuspendall(void)
{
	cyg_scheduler_unlock();
}

void 
jthread_spinon(void *arg)
{
	cyg_scheduler_lock();
}

void 
jthread_spinoff(void *arg)
{
	cyg_scheduler_unlock();
}

int
jthread_extract_stack(jthread_t tid, void **from, unsigned *len)
{
	
#if defined(STACK_GROWS_UP)
#error FixMe
#endif
	CYG_ASSERT( tid != NULL , "tid is NULL");

	*from =  (void *)cyg_thread_get_stack_base(tid->thread_handle);
	*len  =  cyg_thread_get_stack_size(tid->thread_handle);
	return 1;
}

int
jthread_on_current_stack(void *_bp)
{
	cyg_handle_t thread_handle;
	cyg_addrword_t from,to,bp;
	int len;

	thread_handle = cyg_thread_self();
	
	from =  cyg_thread_get_stack_base(thread_handle);
	len  =  cyg_thread_get_stack_size(thread_handle);
	to = from + len;
	bp = (cyg_addrword_t)_bp;

	return (from <= bp ) && (bp < to);
}       

int
jthread_stackcheck(int need)
{
	int rc;

	rc = jthread_on_current_stack(((void *)&rc) - need);

	return rc;
}

#define REDZONE 1024
void*
jthread_stacklimit(void)
{
	cyg_handle_t thread_handle;
	cyg_addrword_t from;

	thread_handle = cyg_thread_self();

	from =  cyg_thread_get_stack_base(thread_handle);
	
	return (void *)(from + REDZONE);
}

void* 
jthread_getcookie(jthread_t tid)
{
	CYG_ASSERT( tid != NULL , "tid is NULL");
	return tid->cookie;
}

void
jthread_walkLiveThreads(void (*func)(void *java_thread))
{
	jthread_t tid;
	int i;

	cyg_scheduler_lock();
	tid = jthread_system.terminal.next;
	for( i = 0 ; i < jthread_system.threadN ; i++ , tid = tid->next) {
		func(tid->cookie);
	}
	cyg_scheduler_unlock();
}

int
jthread_frames(jthread_t tid)
{
	return 0;
}


void 
jthread_init(int preemptive,
	int max_priority, int min_priority,
	void *(*_allocator)(size_t), 
	void (*_deallocator)(void*),
	void (*_destructorl)(void*),
	void (*_onstop)(void),
	void (*_ondeadlock)(void))
{
	jthread_t tid;
	cyg_addrword_t stack_base;

	jthread_system.max_priority = max_priority;
	jthread_system.min_priority = min_priority;
	jthread_system.allocator = _allocator;
	jthread_system.deallocator = _deallocator;
	jthread_system.destructorl = _destructorl;
	jthread_system.onstop = _onstop;
	jthread_system.ondeadlock = _ondeadlock;

	jthread_system.runOnExit = NULL;

	cyg_mutex_init(&jthread_system.threadLock);
	jthread_system.terminal.next = (jthread_t)&jthread_system.anchor;
	jthread_system.terminal.prev = NULL;

	jthread_system.anchor.next = NULL;
	jthread_system.anchor.prev = (jthread_t)&jthread_system.terminal;

	/* INIT Native Thread */
	tid = (jthread_t)jthread_system.allocator(sizeof(struct jthread));

	if ( tid == NULL ) {
		CYG_FAIL("cannot allocate");
		goto err_allocate;
	}

	tid->thread_handle = cyg_thread_self();
	stack_base = cyg_thread_get_stack_base(tid->thread_handle);
	
	if ( stack_base >= ((cyg_addrword_t)&stack_base)) {
		CYG_FAIL("stack over flow");
		goto err_stack_over_flow;
	}
	*((int *)stack_base) = MAGIC_END;

	JTHREAD_LINK_EN_QUEUE(tid,NULL,0,NULL);

	return;

err_stack_over_flow:
err_allocate:

	return;
}

void
jthread_atexit(void (*func)(void))
{
	jthread_system.runOnExit = func;
}

void 
jthread_disable_stop(void)
{
	jthread_t tid;

	tid = jthread_current();
	if ( tid != NULL ) {
		tid->stopCount++;
		if ( tid->status == THREAD_STAT_ALIVE ) {
			JTHREAD_CHANGE_STATUS(tid,THREAD_STAT_ALIVE,THREAD_EVENT_DISABLE_STOP,THREAD_STAT_ALIVE_DONTSTOP);
		}
	} else {
#ifdef DEBUG
		diag_printf("jthread_disable_stop NULL\n");
#endif
	}
}

void 
jthread_enable_stop(void)
{
	jthread_t tid;
	unsigned int status;

	tid = jthread_current();
	if ( tid == NULL ) {
#ifdef DEBUG
		diag_printf("jthread_enable_stop NULL\n");
#endif
		goto skip;
	}
	tid->stopCount--;
	status = tid->status;

	if ( status == THREAD_STAT_IWANTTO_STOP ) {
		tid->status = THREAD_STAT_DEAD;
		jthread_system.onstop();
		jthread_exit();
	} else if ( status == THREAD_STAT_ALIVE_DONTSTOP ) {
		if ( tid->stopCount == 0 ) {
			JTHREAD_CHANGE_STATUS(tid,THREAD_STAT_ALIVE_DONTSTOP,THREAD_EVENT_ENABLE_STOP,THREAD_STAT_ALIVE);
		}
	} else {
		assert("status fatal error!!");
	}
skip:
}

void
jthread_stop(jthread_t tid)
{
	unsigned int status;

	CYG_ASSERT( tid != NULL , "tid is NULL");

	assert(tid != jthread_current());
	status = tid->status;

	switch ( status ) {
	case THREAD_STAT_ALIVE:
		JTHREAD_CHANGE_STATUS(tid,THREAD_STAT_ALIVE,THREAD_EVENT_SUDDEN_DEATH,THREAD_STAT_DEAD);
		break;
	case THREAD_STAT_ALIVE_DONTSTOP:
		JTHREAD_CHANGE_STATUS(tid,THREAD_STAT_ALIVE_DONTSTOP,THREAD_EVENT_SUDDEN_DEATH,THREAD_STAT_IWANTTO_STOP);
		break;
	case THREAD_STAT_IWANTTO_STOP:
		// nothing to do
		break;
	case THREAD_STAT_DEAD:
		// nothing to do
		break;
	}

}


void
jthread_interrupt(jthread_t tid)
{
	CYG_ASSERT( tid != NULL , "tid is NULL");

	cyg_scheduler_lock();
	cyg_thread_release(tid->thread_handle);
	cyg_scheduler_unlock();
}

jthread_t
jthread_create(unsigned int priority, void (*func)(void *), int daemon, void *cookie, size_t cThreadStackSize)
{
	jthread_t tid;
	cyg_addrword_t stack_base;

	tid = (jthread_t)jthread_system.allocator(sizeof(struct jthread) + MARGIN + cThreadStackSize + MARGIN);
	if ( tid == NULL ) {
		goto err_allocate;
	}

	stack_base = (cyg_addrword_t)(tid+1);
	*((int *)stack_base) = MAGIC_TOP;
	stack_base += MARGIN;
	*((int *)(stack_base + cThreadStackSize)) = MAGIC_END;

#define MAKE_NAME(xfunc) "java thread"

	cyg_thread_create(CHANGE_PRIORITY_JAVA2CYG(priority),
		(cyg_thread_entry_t *)func,
		(cyg_addrword_t)NULL, // args
		(char *)MAKE_NAME(func),// name
		(void *)stack_base,
		cThreadStackSize,
		&tid->thread_handle,
		&tid->thread);

	if ( tid->thread_handle == (cyg_handle_t)NULL ) {
		goto err_cyg_thread_create;
	}

	JTHREAD_LINK_EN_QUEUE(tid,func,daemon,cookie);

	cyg_thread_resume(tid->thread_handle);

	return tid;

err_cyg_thread_create:
	jthread_system.deallocator(tid);
err_allocate:

	return 0;
}

void 	
jthread_yield(void)
{
	cyg_thread_yield();
}

void
jthread_sleep(jlong millisec)
{
	// 1tick = 1/100 sec in eCos
	cyg_thread_delay(MILLISEC_TO_TICK(millisec));
}

jthread_t 
jthread_createfirst(size_t mainThreadStackSize,unsigned char priority,void* cookie)
{
	jthread_t tid;

	tid = jthread_current();
	CYG_ASSERT( tid != NULL , "tid is NULL");

	tid->status = THREAD_STAT_ALIVE;
	tid->cookie = cookie;

	cyg_thread_set_priority(tid->thread_handle,CHANGE_PRIORITY_JAVA2CYG(priority));

	return tid;
}

int
jthread_alive(jthread_t tid)
{
	int status;

	CYG_ASSERT( tid != NULL , "tid is NULL");

	cyg_scheduler_lock();
	if (( tid == NULL ) || 
		( tid->status == THREAD_STAT_DEAD )) {

		status = false;
	} else {
		status = true;
	}
	cyg_scheduler_unlock();

	return status;
}

void
jthread_setpriority(jthread_t tid, int priority)
{
	CYG_ASSERT( tid != NULL , "tid is NULL");

	cyg_thread_set_priority(tid->thread_handle,CHANGE_PRIORITY_JAVA2CYG(priority));
}

void
jthread_exit(void)
{
	jthread_t tid;

	tid = jthread_current();
	CYG_ASSERT( tid != NULL , "tid is NULL");

	JTHREAD_LINK_DE_QUEUE(tid);
	tid->status = THREAD_STAT_DEAD;

	cyg_scheduler_lock();

	if (( jthread_system.threadN == jthread_system.daemonN ) && !tid->daemon ){
		jthread_t iter;
		int i;

		if ( jthread_system.runOnExit ) {
			jthread_system.runOnExit();
		}

		iter = jthread_system.terminal.next;
		for( i = 0 ; i < jthread_system.threadN ; i++ , iter = iter->next) {
			if ( jthread_system.destructorl ) {
				(*jthread_system.destructorl)(iter->cookie);
			}
			jthread_stop(iter);
		}
		assert(iter == (jthread_t)&jthread_system.anchor);

	} else {
		if ( jthread_system.destructorl ) {
			(*jthread_system.destructorl)(tid->cookie);
		}
	}

	cyg_scheduler_unlock();

	cyg_thread_exit();

	while(1) {
		assert(!"This better not return");
	}
}

void jthread_exit_when_done(void)
{
	while( jthread_system.threadN > 1 ) {
		cyg_thread_yield();
	}
	jthread_exit();
}

void    
jthread_destroy(jthread_t tid)
{
	CYG_ASSERT( tid != NULL , "tid is NULL");

	cyg_thread_delete(tid->thread_handle);
	jthread_system.deallocator(tid);
}

#if 0
jthread_t 
find_my_jthread(cyg_handle_t thread_handle)
{
	jthread_t iter;
	int i;
	int found;
#ifdef USE_THREAD_COOKIE_FOR_KAFFE
	cyg_ucount32 index;
	static int jthread_index = -1;

	if ( jthread_index == -1 ) {
		jthread_index = cyg_thread_new_data_index();
		diag_printf("jthread_index is %d\n",jthread_index);
		cyg_thread_free_data_index(jthread_index);
	} else {
		iter = cyg_thread_get_data(jthread_index);
		if ( iter != 0 ) {
			return iter;
		}
	}
#endif
	
	found = 0;

	cyg_mutex_lock(&jthread_system.threadLock);

	iter = jthread_system.terminal.next;
	for( i = 0 ; i < jthread_system.threadN ; i++ , iter = iter->next) {
		if ( iter->thread_handle == thread_handle ) {
			found++;
			break;
		}
	}
	cyg_mutex_unlock(&jthread_system.threadLock);

	if ( !found ) {
#ifdef DEBUG
		diag_printf("Not Found in find_my_jthread(N == %d)\n",jthread_system.threadN);
#endif
		return NULL;
	}

#ifdef USE_THREAD_COOKIE_FOR_KAFFE
	index = cyg_thread_new_data_index();
	diag_printf("handle 0x%x index %d\n",thread_handle,index);
	assert(index == jthread_index);

	cyg_thread_set_data(index,(CYG_ADDRWORD)iter);
#endif

	return iter;
}
#endif

/*
#define CHANGE_PRIORITY_JAVA2CYG(priority) (priority)
MAKE_NAME
*/
void 
jthread_dumpthreadinfo(jthread_t tid)
{
	diag_printf("tid:0x%08x,next:0x%08x,prev:0x%08x,status:%d,daemon:%d\n",
		tid,
		tid->next,tid->prev,tid->status,tid->daemon);
	diag_printf("      handle:0x%08x,cookie:0x%08x,func:0x%08x\n",
		tid->thread_handle,tid->cookie,tid->func);
}

static void
dumpJavaThread(void *jlThread)
{
	Hjava_lang_Thread *tid = jlThread;

	jthread_dumpthreadinfo((jthread_t)unhand(tid)->PrivateInfo);
}

void
dumpThreads(void)
{
	diag_printf("threadN %d dameonN %d\n",jthread_system.threadN,jthread_system.daemonN);
	diag_printf("Dumping live threads:current 0x%08x\n",cyg_thread_self());
	jthread_walkLiveThreads(dumpJavaThread);
}

