/**********************************************************************
 
	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.

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



#define STREAM_LIB
#define LIBRARY
#define S_FILE_LIBRARY

#include	<stdlib.h>
#include	<fcntl.h>
#include	<errno.h>
#include	<winsock2.h>
#include	<errno.h>
#include	"memory_debug.h"
#include	"stream.h"
#include	"task.h"
#include	"machine/include.h"
#include	<sys/types.h>
#include	<io.h>
#include	"flush_pipe.h"

extern STREAM * stream_list;
extern SEM stream_lock;
STREAM * s_error_stream();
int s_close_fpipe();
int s_write_fpipe();
int s_read_fpipe();
int s_flush_fpipe();
int s_proc_send_fpipe();
int s_get_socketip_fpipe();
STREAM * s_proc_recv_fpipe();
void er_panic(char * str);

S_TABLE s_fpipe_table = {
	'P',
	0,
	s_error_stream,
	s_close_fpipe,
	s_write_fpipe,
	s_read_fpipe,
	s_flush_fpipe,
	s_proc_send_fpipe,
	s_proc_recv_fpipe,
	s_get_socketip_fpipe
};

void
init_flush_pipe_stream()
{
extern void insert_s_table(S_TABLE * tbl);

	insert_s_table(&s_fpipe_table);
}

int
s_open_fpipe(STREAM * p[2])
{
extern void _s_open(STREAM * s,int mode);

int pfd1[2], pfd2[2];

	if ( _pipe(pfd1, 1024, O_BINARY ) < 0 || _pipe(pfd2, 1024, O_BINARY ) < 0 )
		return -1;

	errno = 0;
	p[0] = d_alloc(sizeof(S_FPIPE),134);
	p[0]->h.tbl = &s_fpipe_table;
	p[0]->h.thread = 0;
	p[0]->fpipe.fid_read = pfd1[0];
	p[0]->fpipe.fid_write = pfd2[1];
	p[0]->fpipe.head = p[0]->fpipe.tail = 0;
	p[0]->fpipe.read_flag = 0;
	p[0]->fpipe.flags = 0;
	p[0]->fpipe.this_ip_addr = p[0]->fpipe.peer_ip_addr;

	p[1] = d_alloc(sizeof(S_FPIPE),134);
	p[1]->h.tbl = &s_fpipe_table;
	p[1]->h.thread = 0;	
	p[1]->fpipe.fid_read = pfd2[0];
	p[1]->fpipe.fid_write = pfd1[1];
	p[1]->fpipe.head = p[1]->fpipe.tail = 0;
	p[1]->fpipe.read_flag = 0;
	p[1]->fpipe.flags = 0;
	p[1]->fpipe.this_ip_addr = p[1]->fpipe.peer_ip_addr;

	lock_task(stream_lock);
	_s_open(p[0],O_RDWR);
	_s_open(p[1],O_RDWR);
	unlock_task(stream_lock,"s_open_file");
	return 0;
}


int
s_close_fpipe(STREAM * s)
{
PIPE_READ_BUF * b;
void _s_close_sync(STREAM * s);
	
	_s_close_sync(s);
	close(s->fpipe.fid_read);
	close(s->fpipe.fid_write);
	for ( ; s->fpipe.head ; ) {
		b = s->fpipe.head;
		s->fpipe.head = b->next;
		d_f_ree(b);
	}
	s->fpipe.tail = 0;
	return 0;
}

int
rw_loop(int (*func)(),STREAM * s,int fid,void * data,int len)
{
int ret;
char * ptr;
int _ret;
	ptr = data;
	_ret = 0;
	for ( ; len ; ) {
		errno = 0;
		ret = (*func)(fid,ptr,len);

		if ( ret <= 0 ) {
			_ret = -1;
			break;
		}
		len -= ret;
		ptr += ret;
		_ret += ret;
	}
	return _ret;
}


int
s_write_fpipe(STREAM * s,void * data,int len)
{
int ret;
char * ptr;
int _len;
	if ( len == 0 )
		return 0;


	lock_task(stream_lock);
	for ( ; s->h.tbl && (s->fpipe.flags & FPF_WRITE) ; ) {
		sleep_task((int)s,stream_list);
		lock_task(stream_lock);
	}
	if ( s->h.tbl == 0 ) {
		unlock_task(stream_lock,"s_write_fpipe");
		ret = -1;
		goto end2;
	}
	s->fpipe.flags |= FPF_WRITE;
	unlock_task(stream_lock,"s_write_fpipe");

	errno = 0; 
	ret = rw_loop(write,s,s->fpipe.fid_write,&len,sizeof(len));
	if ( ret != sizeof(len) ) {
		ret = -1;
		goto end;
	}
	ret = rw_loop(write,s,s->fpipe.fid_write,data,len);

	
end:
	lock_task(stream_lock);
	s->fpipe.flags &= ~FPF_WRITE;
	wakeup_task((int)s);
	unlock_task(stream_lock,"s_write_fpipe");
end2:
	s->h.last_size = len;
	s->h.last_err = errno;
	return ret;
}

int
read_phy_pipe(STREAM * s,SEM lock)
{
int len;
int ret;
PIPE_READ_BUF * b;
char * ptr;
int ip;

	if ( s->fpipe.read_flag ) {
		sleep_task((int)s,lock);
		if ( lock )
			lock_task(lock);
		return 2;
	}
	s->fpipe.read_flag;
	unlock_task(lock,"read_phy_pipe");

	ret = rw_loop(read,s,s->fpipe.fid_read,&len,sizeof(len));
	if ( ret != sizeof(len) ) {
		if ( lock )
			lock_task(lock);
		s->fpipe.read_flag = 0;
		wakeup_task((int)s);
		return -1;
	}
	if ( len < 0 ) {
		if ( lock )
			lock_task(lock);
		switch ( len ) {
		case FPC_FLUSH:
			s->fpipe.flags |= FPF_FLUSH;
			break;
		case FPC_GET_IP:
			if ( s->fpipe.this_ip_addr == 0 )
				ip = -1;
			rw_loop(write,s,s->fpipe.fid_write,
				&ip,sizeof(ip));
			break;
		case FPC_GET_IP_REP:
			rw_loop(read,s,s->fpipe.fid_read,
				&s->fpipe.peer_ip_addr,sizeof(ip));
			break;
		default:
			er_panic("read_phy_fpipe(1)");
		}
		s->fpipe.read_flag = 0;
		wakeup_task((int)s);
		return 1;
	}
	b = d_alloc(sizeof(*b)+len,123);
	ptr = (char*)(b+1);
	b->ptr = 0;
	b->len = len;
	ret = rw_loop(read,s,s->fpipe.fid_read,ptr,len);
	if ( ret != len ) {
		d_f_ree(b);

		if ( lock ) 
			lock_task(lock);
		s->fpipe.read_flag = 0;
		wakeup_task((int)s);
		return -1;
	}
	b->next = 0;
	if ( s->fpipe.head ) {
		s->fpipe.tail->next = b;
		s->fpipe.tail = b;
	}
	else	s->fpipe.tail = s->fpipe.head = b;

	if ( lock )
		lock_task(lock);
	s->fpipe.read_flag = 0;
	wakeup_task((int)s);
	return 0;
}

int
copy_out_head(STREAM * s,void * data,int len)
{
char * ptr;
PIPE_READ_BUF * b;
int copy_size;
int ret;
	ptr = data;
	ret = 0;
	for ( ; ; ) {
		if ( len == 0 )
			break;
		if ( s->fpipe.head == 0 )
			break;
		b = s->fpipe.head;
		if ( b->len - b->ptr > len ) {
			memcpy(ptr,((char*)(b+1))+b->ptr,len);
			b->ptr += len;
			ptr += len;
			ret += len;
			len = 0;
		}
		else {
			copy_size = b->len - b->ptr;
			memcpy(ptr,((char*)(b+1))+b->ptr,copy_size);
			ptr += copy_size;
			len -= copy_size;
			ret += copy_size;

			s->fpipe.head = b->next;
			d_f_ree(b);
			if ( s->fpipe.head == 0 )
				s->fpipe.tail = 0;
		}
	}
	return ret;
}

int
s_read_fpipe(STREAM * s,void * data,int len)
{
int ret;
	lock_task(stream_lock);
	if ( len == 0 ){
		ret = -1;
		goto end;
	}
	for ( ; ; ) {
		if ( s->fpipe.head ) {
			ret = copy_out_head(s,data,len);
			break;
		}
		switch ( read_phy_pipe(s,stream_lock) ) {
		case 0:
		case 1:
		case 2:
			continue;
		case -1:
			ret = -1;
			goto end;
		default:
			er_panic("s_read_pipe");
		}
		break;
	}
end:
	unlock_task(stream_lock,"s_read_fpipe");
	return ret;
}

int
s_flush_fpipe(STREAM * s)
{
int len;
int ret;
	len = -1;
	lock_task(stream_lock);
	ret = rw_loop(write,s,s->fpipe.fid_write,&len,sizeof(len));
	unlock_task(stream_lock,"s_flush_fpipe");
	if ( ret < 0 )
		return -1;
	return 0;
}

int
s_proc_send_fpipe(STREAM * s,PROC_SEND * ps)
{

	if ( s->fpipe.head )
		return -1;
	ps->msg[0] = s->h.tbl->type;
	ps->msg[1] = 0;
	ps->fid[0] = s->fpipe.fid_read;
	ps->fid[1] = s->fpipe.fid_write;
	ps->fid[1] = -1;
	return 0;
}


STREAM *
s_proc_recv_fpipe(PROC_RECV * pr)
{
int fid;
STREAM * ret;
int mode;

	pr->ptr ++;

	mode = O_RDONLY;
	ret = d_alloc(sizeof(S_FPIPE),133);
	ret->h.tbl = pr->tbl;
	ret->h.thread = 0;
	ret->fpipe.head = ret->fpipe.tail = 0;
	ret->fpipe.read_flag = 0;

	fid = pr->fidp ++;
/* 
	// winapi dose not have fctrl.
	mode = fcntl(fid,F_GETFL);
	if ( mode < 0 ) {
		d_f_ree(ret);
		return 0;
	}
	mode &= ~3;
	fcntl(fid,F_SETFL,mode);
*/
	ret->fpipe.fid_read = fid;

	fid = pr->fidp ++;
/*
	// winapi dose not have fctrl.
	mode = fcntl(fid,F_GETFL);
	if ( mode < 0 ) {
		d_f_ree(ret);
		return 0;
	}
	mode &= ~3;
	fcntl(fid,F_SETFL,mode);
*/
	ret->fpipe.fid_write = fid;

	_s_open(ret,mode);
	return ret;
}

int
_s_get_fpipe_data_size(STREAM * s)
{
int ret;
PIPE_READ_BUF * r;
	ret = 0;
	for ( r = s->fpipe.head ; r ; r = r->next )
		ret += r->len - r->ptr;
	return ret;
}

int
s_get_fpipe_data_size(STREAM * s)
{
int ret;
	lock_task(stream_lock);
	ret = _s_get_fpipe_data_size(s);
	unlock_task(stream_lock,"s_get_fpipe_data_size");
	return ret;
}


int
s_wait_flush_fpipe(STREAM * s)
{
int ret;
	lock_task(stream_lock);
	s->fpipe.flags &= ~FPF_FLUSH;
	for ( ; ; ) {
		if ( read_phy_pipe(s,stream_lock) < 0 ) {
			ret = -1;
			break;
		}
		if ( s->fpipe.flags & FPF_FLUSH ) {
			ret = _s_get_fpipe_data_size(s);
			break;
		}
	}
	unlock_task(stream_lock,"s_wait_flush_fpipe");
	return ret;
}

int
s_get_socketip_fpipe(STREAM * s)
{
int cmd;

	if ( s->fpipe.peer_ip_addr )
		return s->fpipe.peer_ip_addr;
	s->fpipe.read_flag = 1;
	cmd = FPC_GET_IP;
	if ( rw_loop(write,s,s->fpipe.fid_write,&cmd,sizeof(cmd)) <= 0 )
		return -1;
	for ( ; ; ) {
		if ( read_phy_pipe(s,stream_lock) < 0 ) {
			return -1;
		}
		if ( s->fpipe.peer_ip_addr )
			return s->fpipe.peer_ip_addr;
	}
}

int
s_set_fpipe_ip_addr(STREAM * s,int ip)
{
int ret;
	lock_task(stream_lock);
	if ( s->h.tbl != &s_fpipe_table ) {
		ret = -1;
		goto end;
	}
	s->fpipe.this_ip_addr = ip;
	ret = 0;
end:
	unlock_task(stream_lock,"s_set_fpipe_ip_addr(1)");
	return ret;
}