/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.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

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

#include	<stdlib.h>
#include	<stdio.h>
#include	<signal.h>
#include	<windows.h>
#include	<tlhelp32.h>
#include	<process.h>

#include	"machine/err.h"
#include	"memory_debug.h"
#include	"task.h"
#include "init.h"


#define DTI_LENGTH		10000


typedef struct task_block {
	struct task_block *	next;
	int			cmd;
#define TBC_EXEC		1
#define TBC_FINISH		2
	int			hard_tid;
	int			soft_tid;
	int			data;
	void 			(*func)(void *);
	unsigned int		finish_time;
} TASK_BLOCK;

typedef struct dti {
	TASK_BLOCK *		tb;
	void			(*func)();
	int			msg;
	int			msg2;
} DTI;

void er_panic(char * str);

DTI * debug_task_info;
int task_init;

s_task_flag = INI_WDONTCARE;

#define PRI_MAX 6 
static const int win_pri_table[] = 
	{
		THREAD_PRIORITY_IDLE,
		THREAD_PRIORITY_LOWEST,
		THREAD_PRIORITY_BELOW_NORMAL,
		THREAD_PRIORITY_NORMAL,
		THREAD_PRIORITY_ABOVE_NORMAL,
		THREAD_PRIORITY_HIGHEST,
		THREAD_PRIORITY_TIME_CRITICAL
	};

#define MAX_TEST 10
int test_tid[MAX_TEST];
int test_tno[MAX_TEST];

int
_sem_getvalue(SEM s, int *value)
{
	LONG l;
	if(ReleaseSemaphore(s->handle, 0x7fffffff, &l) != FALSE){
		er_panic("");
	}
	*value = l;
	return 0;
}

void
sleep_sec(int x)
{
	Sleep(x*1000);
}

int win_pri2gbs_pri(int pri)
{
	int i;
	for(i=0; i<PRI_MAX; ++i){
		if(win_pri_table[i] == pri){
			return i;
		}
	}
	er_panic("win_pri2gbs_pri");
	return 0;
}

void
alarm_handler()
{
}

void
bpipe_handler()
{
}

void
siglwp_handler()
{
}

int (*int_handler)();

void
set_alarm_mask()
{
}

void
init_task(int level)
{
int i;
void sw_init();

	if ( task_init )
		return;
	task_init = 1;

	debug_task_info = malloc(sizeof(DTI)*DTI_LENGTH);
	for ( i = 0 ; i < DTI_LENGTH ; i ++ ) {
		debug_task_info[i].tb = 0;
		debug_task_info[i].func = 0;
		debug_task_info[i].msg = 0;
		debug_task_info[i].msg2 = 0;
	}
	
	s_task_flag = level;
	sw_init();

}

void
set_t_msg(int msg)
{
int tid;
	tid = get_tid();
	debug_task_info[tid % DTI_LENGTH].msg = msg;
}


void
set_t_msg2(int msg)
{
int tid;
	tid = get_tid();
	debug_task_info[tid % DTI_LENGTH].msg2 = msg;
}


SEM sem_list = 0;

#ifdef DEADLOCK_CHECK
#include  <assert.h> 
SEM
new_lock(int level)
{
SEM s;
SEM *sp;
	s = d_alloc(sizeof(*s));
	sem_init(s,0,1);
	s->level = level;
	s->tid = 0;
	for(sp = &sem_list; *sp; sp = &((*sp)->next)){
		if((*sp)->level > level){
			s->next = *sp;
			*sp = s;
			return s;
		}
	}
	s->next = 0;
	*sp = s;
	return s;
}

void close_lock(SEM s)
{
SEM * sp;
	for ( sp = &sem_list ; *sp ; sp = &(*sp)->next ){
		if ( *sp == s ) {
			*sp = s->next;
			break;
		}
	}
	sem_destroy(s);
	d_f_ree(s);
}

void
_lock_task(SEM s,char * file,int line)
{
SEM sp;
int tid;
int value;
extern SEM block_lock;


	if ( s == block_lock )
		goto retry;
	tid = get_tid();
	for ( sp = sem_list ; sp ; sp = sp->next ){
		if ( s == sp ) {
			if ( sp->tid == tid ) {
				fprintf(stderr,"************* DOUBLELOCK\n");
				er_panic("lock_task");
			}
			break;
		}
		if ( sp->tid != tid )
			continue;
		fprintf(stderr,"************* DEADLOCK %i - %i\n",
			sp->level,s->level);
		er_panic("lock_task");
	}
retry:
	errno = 0;
	if ( sem_wait(&s->s) ) {
		if ( errno == EINTR )
			goto retry;
	}
	s->tid = tid;
	s->lock_file = fine;
	s->lock_line = line;
}
#endif // DEADLOCK_CHECK

#ifndef DEADLOCK_CHECK
SEM
new_lock(int level)
{
SEM s;

	s = d_alloc(sizeof(*s));
	sem_init(s,0,1);
	sem_trywait(s);
	sem_post(s);
	return s;
}
void close_lock(SEM s)
{
	sem_destroy(s);
	d_f_ree(s);
}
void
_lock_task(SEM s,char * file,int line)
{
	sem_wait(s);
	s->tid = get_tid();
	s->lock_file = file;
	s->lock_line = line;
}

void
_unlock_task(SEM s,char *str,char * file,int line)
{
	s->tid = 0; 
	s->unlock_file = file;
	s->unlock_line = line;
	sem_post(s);
}

#endif // ndef DEADLOCK_CHECK

int get_pri(int tid)
{
	int pri;
	HANDLE hTread;
	if(tid==0){
		hTread = GetCurrentThread();
	}
	else{
		HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
		THREADENTRY32 te;
		te.dwSize = sizeof(te);
		if(Thread32First(hSnapShot, &te)){
			do{
				if(te.th32ThreadID == (DWORD)tid){
					CloseHandle(hSnapShot);
					return win_pri2gbs_pri(te.tpBasePri + te.tpDeltaPri);
				}
			}while(Thread32Next(hSnapShot, &te));
		}
		else{
			CloseHandle(hSnapShot);
			er_panic("invalid pri\n");
			return 0;
		}
	}
	pri = GetThreadPriority(hTread);
	if(pri == THREAD_PRIORITY_ERROR_RETURN ){
		er_panic("invalid pri\n");
		return 0;
	}
	else
		return win_pri2gbs_pri(pri);
}

int 
change_pri(int tid, int pri)
{
	if(pri<0 || PRI_MAX <pri){
		fprintf(stderr,"pri is out of range : %d\n",pri);
		return 0;
	}
	if(tid == 0){
		tid = (int)GetCurrentThread();
	}
	if(!SetThreadPriority((HANDLE)tid, win_pri_table[pri]))
		fprintf(stderr,"can not set priority: %d\n",pri);
	else
		return 1;

	return 0;
}

void _task_grue(void *v){
TASK_BLOCK *tb;
TASK_BLOCK b;

	tb=(TASK_BLOCK *)v;
	b=*tb;
	d_f_ree(tb);
	b.func(b.data);
	close_thread_area();
	_endthread();
}

int
create_task(void (*func)(),int data, int pri)
{

unsigned long ret;
TASK_BLOCK *tb;
	
	tb = (TASK_BLOCK *)d_alloc(sizeof(*tb));
	
	tb->func = (void (__cdecl *)(void *))func;
	tb->data = data;
	ret = _beginthread(_task_grue, 0, tb);
	if(ret==-1){
		switch ( errno ) {
			case EAGAIN:
				fprintf(stderr,"EAGAIN\n");
				break;
			case EINVAL:
				fprintf(stderr,"EINVAL\n");
				break;
			default:
				fprintf(stderr,"default: %d\n", errno);
			break;
		}
	}
	else{
		change_pri(ret, pri);
	}
	return ret;
}

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

void
read_lock(RW_LOCK * rwl)
{
	lock_task(rwl->lock);
	for ( ; rwl->read_cnt < 0 ; ) {
		unlock_task(rwl->lock,"read_lock(1)");
		Sleep(10);
		lock_task(rwl->lock);
	}
	rwl->read_cnt ++;
	unlock_task(rwl->lock,"read_lock(2)");
}

void
write_lock(RW_LOCK * rwl)
{
	lock_task(rwl->lock);
	for ( ; rwl->read_cnt != 0 ; ) {
		unlock_task(rwl->lock,"write_lock(1)");
		Sleep(10);
		lock_task(rwl->lock);
	}
	rwl->read_cnt = -1;
	unlock_task(rwl->lock,"write_lock(2)");
}

void
rwl_unlock(RW_LOCK * rwl)
{
	lock_task(rwl->lock);

	if ( rwl->read_cnt > 0 )
		rwl->read_cnt --;
	else if ( rwl->read_cnt < 0 )
		rwl->read_cnt = 0;
	else	er_panic("rwl_unlock(1)");
	unlock_task(rwl->lock,"rwl_unlock(1)");
}

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;
}

int
get_tid()
{
	return GetCurrentThreadId();
}

void
print_tno(char * str)
{
int i;
	printf("%s ",str);
	for ( i = 0 ; i < MAX_TEST ; i ++ ) {
		if ( test_tid[i] == 0 )
			continue;
		printf("(%i-%i)",test_tid[i],test_tno[i]);
	}
	printf("\n");
}

void
test_no(int no)
{
int tid;
int i;
	tid = get_tid();
	for ( i = 0 ; i < MAX_TEST ; i ++ )
		if ( test_tid[i] == tid ) {
			test_tno[i] = no;
			return;
		}
	for ( i = 0 ; i < MAX_TEST ; i ++ )
		if ( test_tid[i] == 0 ) {
			test_tid[i] = tid;
			test_tno[i] = no;
			return;
		}
}
