/**********************************************************************
 
	Copyright (C) 2003-2004
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomoki SEKIYAMA <sekiyama@yahoo.co.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	This program is distributed in the hope that it will be 
	useful, but WITHOUT ANY WARRANTY; without even the 
	implied warranty of MERCHANTABILITY or FITNESS FOR A 
	PARTICULAR PURPOSE.

**********************************************************************/


/************************************

	machine dependent
	task routines

*************************************/

#if THREAD_DEBUG
#define THREAD_DEBUG_CUR
#endif

extern "C" {

#include	"task.h"
#include	"utils.h"
#include	"memory_debug.h"
#include	"LSemaphore.h"
#include	"LSimpleThread.h"
#include	"AppConstants.h"
#include	"utils.h"
void er_panic(char * str);
void sw_init();
void init_thread_block();
void _unlock_task_f(D_SEM s, bool forceSwitch, char *file, int line );
void pass_data_to_task_func(LThread &thread,  void *fad);
void sleep_ms(int ms);
void _i_unlock_task_f(SEM s, bool forceSwitch, char *file = 0, int line = 0);

int (*int_handler)();
int task_init = 0;

#ifdef THREAD_DEBUG_CUR

#define DEBUG_TH_TIMEOUT	10

LThread *last_locked = 0;

void see_lock_task();
typedef struct sem_list {
	D_SEM			sem;
	int			sem_id;
	char *			new_lock_file;
	int			new_lock_line;
	struct sem_list *	next;
} SEM_LIST;

#define SEM_LIST_HASH_SIZE	129
SEM_LIST ** sem_list_hash;
int debug_th_timeout_flag;
int debug_th_timeout_flag_2;

int debugging_tid = 0;

#endif

void init_task(int)
{
	if ( task_init )
		return;
#ifdef THREAD_DEBUG_CUR
	sem_list_hash = (SEM_LIST**)malloc(sizeof(SEM_LIST*)*SEM_LIST_HASH_SIZE);
	memset(sem_list_hash,0,sizeof(SEM_LIST*)*SEM_LIST_HASH_SIZE);
#endif
	task_init = 1;
	init_thread_block();
	sw_init();

#ifdef THREAD_DEBUG_CUR
	create_task(see_lock_task,0,1);
#endif
}

int
get_tid()
{
	return (int)LThread::GetCurrentThread();
}

HTID
_get_tid()
{
HTID ret;
	ret.tid = get_tid();
	ret.valid = 1;
	return ret;
}

#ifdef THREAD_DEBUG_CUR
class CSemaphore : public LSemaphore
{
  public:
	CSemaphore(int initial=0) : LSemaphore(initial) {}
	LThread* lock_thread;
	char lock_thread_name[256];
	char* lock_file;
	int lock_line;
	unsigned int	lock_time;

	LThread* unlock_thread;
	char unlock_thread_name[256];
	char* unlock_file;
	int unlock_line;
};
#define LSemaphore CSemaphore

struct WAIT_INFO {
	int line;
	char file[256];
	int ticks;
};

const char* thread_state_2_string(LThread *th);

const char*
thread_state_2_string(LThread *th)
{
	LThread::EThreadState s = th->GetState();

	if ( s == LThread::threadState_Waiting ||
		 s == LThread::threadState_Suspended ) {
		static char thread_state[256];
		
		sprintf(thread_state, "%s at %s:%d",
			s == LThread::threadState_Waiting ? "waiting" : "suspended",
			((WAIT_INFO*)th->mWorkArea)->file,
			((WAIT_INFO*)th->mWorkArea)->line);
		return thread_state;
	}
	
	return  s == LThread::threadState_Current	? "current" :
			s == LThread::threadState_Ready		? "ready"	:
			s == LThread::threadState_Sleeping	? "sleeping":
			s == LThread::threadState_Blocked	? "blocked(IO)" :
			"unknown";
}

int
debug_th_timeout_check(int time)
{
int t;
	t = get_xltime();
	if ( debug_th_timeout_flag == 0 ) {
		if ( t - time < DEBUG_TH_TIMEOUT )
			return -1;
		debug_th_timeout_flag = 1;
		debug_th_timeout_flag_2 = 1;
		return 0;
	}
	else {
		if ( t - time > DEBUG_TH_TIMEOUT )
			debug_th_timeout_flag_2 = 1;
	}
	return 0;
}

static void
printThreadNameIfBlocked(LThread& thread, void */* arg */)
{
	if ( thread.GetState() == LThread::threadState_Blocked )
		printf("blocked : %s\n", thread.mName);
}

void semaphore_info(D_SEM s);

void
semaphore_info(D_SEM s)
{
CSemaphore *cs = dynamic_cast<CSemaphore*>(s);
	if ( cs->lock_file == 0 )
		printf("semaphore-info n/a\n");
	else
		printf("semaphore-info %s [%s] %s %i\n",
			cs->lock_thread_name,
			thread_state_2_string(cs->lock_thread),
			cs->lock_file,cs->lock_line);
}

void
see_lock_task()
{
SEM_LIST * sl;
unsigned int t;
int ind;
int i;
void (*f)(int*);
	sleep_sec(10);
	for ( ; ; ) {

		sleep_sec(1);

		if ( debug_th_timeout_flag_2 == 0 )
			debug_th_timeout_flag = 0;
		debug_th_timeout_flag_2 = 0;

		t = get_xltime();
		ind = 0;
		for ( i = 0 ; i < SEM_LIST_HASH_SIZE ; i ++ )
			for ( sl = sem_list_hash[i] ; sl ; sl = sl->next ) {
			CSemaphore *cs = dynamic_cast<CSemaphore*>(sl->sem);
				if ( cs->lock_file == 0 )
					continue;
				if ( debug_th_timeout_check(cs->lock_time) < 0 )
					continue;
				if ( ind == 0 )
					printf("LOCK INFO ====\n");
				ind = 1;
				printf("locked %i(%s:%i) %isec :: %s[%s] %s %i\n",
					sl->sem_id,
					sl->new_lock_file,
					sl->new_lock_line,
					t - cs->lock_time,
					cs->lock_thread_name,
					thread_state_2_string(cs->lock_thread),
					cs->lock_file,cs->lock_line);
			}
		f = upper_layer_lock_tracer;
		if ( f )
			(*f)(&ind);
		if ( ind ) {
			LThread* prev;
			if ( prev = LThread::GetPrevThread() )
				printf("PrevThread : %s %s\n", prev->mName,
					 thread_state_2_string(prev));
			else
				printf("PrevThread : n/a\n");
			
			LThread::DoForEach(printThreadNameIfBlocked, 0);
			printf("LOCK INFO ==== END\n");
		}
	}
	
}

#else

int
debug_th_timeout_check(int time)
{
  return 0;
}

#endif

D_SEM xx_new_lock(int id,char * __file,int __line)
{
	D_SEM ret = new LSemaphore(1);

#ifdef THREAD_DEBUG_CUR
unsigned int key;
	SEM_LIST * sl;
	CSemaphore *cs = dynamic_cast<CSemaphore*>(ret);
	cs->lock_thread = 0;
	cs->lock_file = 0;
	sl = (SEM_LIST*)malloc(sizeof(*sl));
	sl->sem = ret;
	sl->sem_id = id;
	sl->new_lock_file = __file;
	sl->new_lock_line = __line;

	key = ((unsigned int)ret) % SEM_LIST_HASH_SIZE;
	sl->next = sem_list_hash[key];
	sem_list_hash[key] = sl;
#endif

	return ret;
}

void xx_close_lock(D_SEM s)
{
#ifdef THREAD_DEBUG_CUR
SEM_LIST ** slp,* sl;
unsigned int key;
	key = ((unsigned int)s) % SEM_LIST_HASH_SIZE;
	for ( slp = &sem_list_hash[key] ; *slp ; slp = &(*slp)->next ) {
		sl = *slp;
		if ( sl->sem == s )
			break;
	}
	*slp = sl->next;
	free(sl);
#endif
	delete s;
}


int lock_test_tid;

#ifdef THREAD_DEBUG_CUR
D_SEM td_test_sem;
#endif

void _lock_task(D_SEM s, char file[], int line)
{
void DisplayAlert(char *msg);
int err;

	try {
#ifdef THREAD_DEBUG_CUR
		WAIT_INFO wait_info;
		wait_info.line = line;
		strcpy(wait_info.file, file);
		wait_info.ticks = TickCount();
		memcpy(LThread::GetCurrentThread()->mWorkArea, &wait_info, sizeof(wait_info));
		last_locked = LThread::GetCurrentThread();
#endif

#ifdef THREAD_DEBUG_MSG
retry:
		if ( (err = s->Wait(thMillsecDeadLock)) != noErr ) {
			if ( err == errSemaphoreTimedOut ) {
				char buffer[128];
				sprintf(buffer, "Thread Stervation - %s[tid:%x]",
					((LThread*)get_tid())->mName, get_tid());
				//DisplayAlert(buffer);
				printf("Semaphore - %x  ",s);
				printf("%s ",buffer);
				goto retry;
			} else {
				throw err;
			}
		}
#else
#ifdef THREAD_DEBUG_CUR
		extern int td_test;
		if ( td_test && td_test == get_tid() ) {
			if ( (err = s->Wait(semaphore_NoWait)) != noErr ) {
				td_test_sem = s;
				printf("++td_test++ locked at %s %d\n", file, line);
				semaphore_info(s);
				if ( (err = s->Wait()) != noErr )
					throw err;
				printf("++td_test++ unlocked [%.2f sec]\n", (TickCount()-wait_info.ticks)/60.0);
				td_test_sem = 0;
			}
		}
		else
#endif
		if ( (err = s->Wait()) != noErr )
			throw err;
#endif

#ifdef THREAD_DEBUG_CUR
		last_locked = 0;
		CSemaphore *cs = dynamic_cast<CSemaphore*>(s);
		if ( cs ) {
			cs->lock_thread = LThread::GetCurrentThread();
			strcpy(cs->lock_thread_name, cs->lock_thread->mName);
			cs->lock_file = file;
			cs->lock_line = line;
			cs->lock_time = get_xltime();
		}
#endif

	} catch(...) {
		er_panic("lock_task");
	}
}

void _unlock_task_f(D_SEM s, bool forceSwitch, char *file = 0, int line = 0)
{
	try {

#ifdef THREAD_DEBUG_CUR
		CSemaphore *cs = dynamic_cast<CSemaphore*>(s);
		if ( cs ) {
			cs->lock_thread = 0;
			cs->lock_file = 0;
			cs->unlock_thread = LThread::GetCurrentThread();
			strcpy(cs->unlock_thread_name, cs->unlock_thread->mName);
			cs->unlock_file = file;
			cs->unlock_line = line;
		}
		if ( s == td_test_sem ) {
			WAIT_INFO *wait_info = (WAIT_INFO*)LThread::GetCurrentThread()->mWorkArea;
			printf("++td_test++ sem unlocked by %s [%.2f sec]\n", 
				LThread::GetCurrentThread()->mName, (TickCount()-wait_info->ticks)/60.0);
		}
#endif

		s->Signal();
		if ( forceSwitch /*&& LThread::GetCurrentThread()->GetPriority() < PRI_MAX-1*/ ) {
			UInt32 time = LThread::CurrentThreadTicks();
			if ( time > thTicksForceSwitch ) {
#ifdef THREAD_DEBUG_CUR
		extern int td_test;
		if ( td_test && td_test == get_tid() )
			printf("++td_test++ Yield [%.2f sec] at %s %d\n", time/60.0, file, line);
#endif
#ifdef THREAD_DEBUG_MSG
				printf("<<forcely yield : %s time = %d>>\n",
					LThread::GetCurrentThread()->mName, time);
#endif
				LThread::Yield();
			}
		}
	} catch(...) {
		er_panic("unlock_task");
	}
}

void 
_i_unlock_task_f(SEM s, bool forceSwitch, char *file , int line )
{
	_unlock_task_f(s.so->d_sem,forceSwitch,file,line);
}


void _unlock_task(D_SEM s, char *msg, char *file, int line)
{
	_unlock_task_f(s, true, file, line);
}

typedef struct func_and_data
{
	void (*func)(void *);
	void *data;
} FUNC_AND_DATA;


void pass_data_to_task_func(LThread &thread,  void *fad)
{
#pragma unused(thread)
	FUNC_AND_DATA* fad2 = (FUNC_AND_DATA *)fad;
#ifdef THREAD_DEBUG_MSG
	printf("<Start! %s @%d>\n", thread.mName, TickCount()%1000);
#endif
	try {
		(*(fad2->func))(fad2->data);
	}
	catch(LException exc) {
		int err = exc.GetErrorCode();
		char msg[256];
		if ( exc.GetErrorString() ) {
			const char *msg2;
			msg2 = ((LStr255)exc.GetErrorString()).ConstTextPtr();
			sprintf(msg,"non caught exeption !!!\r %s (ID:%d)", msg2, err);
		}
		else
			sprintf(msg,"non caught exeption !!! - ID:%d", err);
		er_panic(msg);
	}
	catch(...) {
		er_panic("non caught exeption !!!");
	}
	close_thread_area();
	free(fad2);
#ifdef THREAD_DEBUG_MSG
	printf("<Finish! %s @%d> -> (-Def)", thread.mName, TickCount()%1000);
#endif
}

int _create_task(void (*func)(), int data, int priority, char *name,char * file,int line)
{
	FUNC_AND_DATA *fad = (FUNC_AND_DATA *)malloc(sizeof(FUNC_AND_DATA));
	fad->func = (void (*)(void *))func;
	fad->data = (void*)data;
	THREAD t = new LSimpleThread(pass_data_to_task_func, (void *)fad);
	t->SetPriority(priority);
	t->mName = name;
	t->Resume();
	return 0;
}


int GET_TKEY(TKEY ptr)
{
	return (int)ptr;
}


void rw_lock_init(RW_LOCK * rwl, int)
{
	rwl->lock = new_lock(0);
	rwl->cnt = 0;
}

void
read_lock(RW_LOCK * rwl)
{
	while ( rwl->cnt < 0 )
		sleep_task((int)rwl->lock.so ,SEM_NULL);
	if ( rwl->cnt == 0 )
		lock_task(rwl->lock);
	rwl->cnt++;
}

void
write_lock(RW_LOCK * rwl)
{
	lock_task(rwl->lock);
	if ( rwl->cnt != 0 )
		er_panic("write_lock");
	rwl->cnt = -1;
}

void
rwl_unlock(RW_LOCK * rwl)
{
	if ( rwl->cnt == 0 )
		er_panic("rwl_unlock");
	else if ( rwl->cnt > 0 ) {
		if ( --rwl->cnt == 0 )
			unlock_task(rwl->lock, "rwl_unlock(1)");
	} else {
		rwl->cnt = 0;
		unlock_task(rwl->lock, "rwl_unlock(2)");
		wakeup_task((int)rwl->lock.so);
	}
}

void
init_fifo(FIFO * f)
{
	f->head = f->tail = 0;
	f->lock = new_lock(0);
	f->length = 0;
}

void
insert_fifo(FIFO * f,FIFO_EL * e)
{
	lock_task(f->lock);
	e->next = 0;
	if ( f->head == 0 )
		f->head = f->tail = e;
	else {
		f->tail->next = e;
		f->tail = e;
	}
	f->length ++;
	unlock_task(f->lock,"insert_fifo");
}

FIFO_EL *
delete_fifo(FIFO * f)
{
FIFO_EL * ret;
	lock_task(f->lock);
	if ( f->head == 0 ) {
		ret = 0;
	}
	else {
		ret = f->head;
		f->head = ret->next;
		if ( f->head == 0 )
			f->tail = 0;
		f->length --;
	}
	unlock_task(f->lock,"delete_fifo");
	return ret;
}

void
change_pri(int tid, int pri)
{
	LThread* thread;
	if ( tid )
		thread = (LThread*)tid;
	else
		thread = LThread::GetCurrentThread();

	if ( thread )
		thread->SetPriority(pri);
}


int
get_pri(int tid)
{
	LThread* thread;
	if ( tid )
		thread = (LThread*)tid;
	else
		thread = LThread::GetCurrentThread();

	if ( thread )
		return thread->GetPriority();
	else
		return 0;
}


void
sleep_sec(int sec)
{
	try {
		LThread::GetCurrentThread()->Sleep(1000 * sec);
	}
	catch(int err) {
		char buf[256];
		sprintf(buf,"sleep_sec failure (ID=%d)",err);
		er_panic(buf);
	}
	catch(...) {
		er_panic("sleep_sec Failure");
	}
}

void
sleep_ms(int ms)
{
	LThread::GetCurrentThread()->Sleep(ms);
}

struct THREAD_CHECK {
	 int tid;
	 int ret;
};

static void
eq_check(LThread &tid, void *arg)
{
	THREAD_CHECK *chk = (THREAD_CHECK*)arg;
	if ( (int)&tid == chk->tid )
		chk->ret = 0;
}

int
thread_exist(int tid)
{
	THREAD_CHECK chk = {tid, -1};
	LThread::DoForEach(eq_check, &chk);
	return chk.ret;
}

} //extern "C"
