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

#ifdef VA2
#include	<varargs.h>
#else
#include	<stdarg.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include	"machine/err.h"
#include	"memory_debug.h"
#include	"stream.h"
#include	"task.h"

extern SEM stream_lock;

#define BLOCK_SIZE 1024

/* L_CHAR string memory writer */
typedef struct {
	L_CHAR *mem;
	int mem_size;
	int len;
}S_PRINTF_STREAM;


char sp[4000];

static void 
put_lstr(S_PRINTF_STREAM *s, L_CHAR *lstr){
	int len = l_strlen(lstr);
	int memsize_changed = 0;
	if(len == 0)
		return;
	while(s->mem_size <= (s->len + len)){
		if(s->mem_size == 0)
			s->mem_size = 256;
		else
			s->mem_size *= 2;
		memsize_changed = 1;
	}
	if(memsize_changed){
		if(s->mem == 0)
			s->mem = (L_CHAR*)d_alloc(
				s->mem_size*sizeof(L_CHAR),12);
		else
			s->mem = (L_CHAR*)d_re_alloc(
				s->mem, s->mem_size*sizeof(L_CHAR));
	}
	memcpy( &(s->mem[s->len]), lstr, len*sizeof(L_CHAR));
	s->len += len;
}




int 
_s_printf(STREAM *s, char * fmt, va_list p){
	S_PRINTF_STREAM out;
	char *in_begin;
	char *in_end;
	char *temp;
	char type;
	char prefix;
	
	char single_format[64];
	char single_format_dest[64];
	
	memset(&out, 0, sizeof(S_PRINTF_STREAM));


	temp = d_alloc(strlen(fmt)+1,33);
	
	/* begin parse */
	in_begin = fmt;

	while(*in_begin){
		in_end = in_begin;
		/* read to '%' */
		in_end = strchr(in_end, '%');
		if(in_end==NULL){
			in_end = in_begin + strlen(in_begin);
		}
		
		/* write string who has no format */
		if(in_begin != in_end){
			strncpy(temp, in_begin, in_end-in_begin);
			temp[in_end-in_begin] = '\0';
			put_lstr(&out, l_string(&utf8_cm, temp));
			/* end of format string */
			if(!*in_end){
				break;
			}
		}
		
		/* parse format string that is ... */
		/* [flags] [width] [precision] [{h | l | I64 | L}]type */
		in_begin = in_end;
		++in_end;

		/* flags */
		if(strspn(in_end, "+- 0#") != 0)
			++in_end;
		/* width */
		in_end += strspn(in_end, "0123456789*");
		/* precision */
		if(*in_end == '.')
			in_end+=2;
		/* prefix */
		if(!strncmp(in_end, "I64", 3)){
			prefix = *in_end;
			in_end += 3;
		}
		else if(strspn(in_end, "hlL") != 0){
			prefix = *in_end;
			++in_end;
		}
		/* type */
		type = *in_end++;
		
		/* copy single format string */
		strncpy(single_format, in_begin, in_end-in_begin);
		single_format[in_end-in_begin] = '\0';
		
		/* if type is string */
		if(type == 's' || type=='S'){
			if(prefix == 'l' || prefix == 'L'){
				put_lstr(&out, va_arg(p,L_CHAR*));
			}
			else{
				/* what is arg's-char* code ?  std or s->h.cm ?? */
				/* put_lstr(&out, l_string(&utf8_cm, va_arg(p,char*))); */
				put_lstr(&out, l_string(&utf8_cm, va_arg(p,char*)));
			}
		}
		else{
			single_format_dest[0] = '\0';
			switch(type){
				case '%':
					sprintf(single_format_dest, "%%");
					break;
				case 'c':
				case 'C':
				case 'd':
				case 'i':
				case 'o':
				case 'u':
				case 'x':
				case 'X':
					sprintf(single_format_dest, single_format, va_arg(p,int));
					break;
				case 'e':
				case 'E':
				case 'f':
				case 'g':
				case 'G':
					sprintf(single_format_dest, single_format, va_arg(p,double));
					break;
				case 'n':
					sprintf(single_format_dest, single_format, va_arg(p,int*));
					break;
				case 'p':
					sprintf(single_format_dest, single_format, va_arg(p,void*));
					break;
			}
			if(*single_format_dest)
				put_lstr(&out, l_string(&utf8_cm, single_format_dest));
		}
		
		/* update pointer */
		in_begin = in_end;
	}
	
	if ( out.mem == 0 )
		goto end;

	out.mem[out.len] = 0;
	
	/* write lstring buffer to STREAM */
	{
		int er,ss;
		L_CHAR * str;
		CODE_METHOD * cm;
		void * work;
		char * ret,* pp;
		char ex[20];
		char * exp;
		int rbyte;
		int len,ptr;
		S_TABLE * tbl;
		S_FILE_THREAD t;

		lock_task(stream_lock);
		if ( s == 0 ) {
			unlock_task(stream_lock,"s_write");
			return -1;
		}
		if ( s->h.tbl == 0 ) {
			unlock_task(stream_lock,"s_write");
			return -1;
		}
		tbl = s->h.tbl;
		_s_insert_thread(s,&t);


		str = out.mem;
		cm = s->h.cm;
		work = s->h.cm_work;
		
		len = 4*out.len;
		ret = d_alloc(len+1, 354);
		ptr = 0;
		
		for( ; *str; str++){
			if ( s->h.cr_mode &&
				(*str == '\n' || *str == '\r') ) {
				s->h.cr_cnt ++;
			}
			if ( len-ptr < cm->max_ret ) {
				len = len+cm->max_ret;
				ret = d_re_alloc(ret,len+1);
			}
			rbyte = (*cm->to_external)(&ret[ptr], work, *str);
			if ( rbyte == 0 ) {
				sprintf(ex,"&cx%x;",*str);
				for ( exp = &ex[0]; *exp ; exp ++ ) {
					if ( len-ptr < cm->max_ret ) {
						len = len+cm->max_ret;
						ret = d_re_alloc(ret,
								len+1);
					}
					rbyte = (*cm->to_external)
						(&ret[ptr],work,
						((L_CHAR)((*exp)&0x0ff)));
					ptr += rbyte;
				}
			}
			else {
				ptr += rbyte;
			}
		}

		unlock_task(stream_lock,"__s_printf");


		pp = ret;
		for ( ; ptr ; ) {
			ss = ptr;
		retry:
			er = (*tbl->write)(s,pp,ss);
			if ( er <= 0 ) {
				if ( errno == ESYS_AGAIN ) {
					ss = ss/2;
					if ( ss <= 0 )
						ss = 1;
					goto retry;
				}
				break;
			}
			if ( er == 0 )
				break;
			pp += er;
			ptr -= er;
		}


		d_f_ree(ret);

		
		s_delete_thread(s,&t);

	}

end:	

	d_f_ree(temp);
	if ( out.mem )
		d_f_ree(out.mem);
	return out.len;
}

int
s_printf(STREAM * s,char * fmt,...)
{
va_list	p;
int ret;

	if ( s == 0 )
		return -1; 
	if ( s->h.tbl == 0 )
		return -1;
#ifdef VA2
	va_start(p);
#else
	va_start(p,fmt);
#endif
	ret = _s_printf(s, fmt, p);
	
	va_end(p);
	return ret;
}

int
ss_printf(char * fmt,...)
{
va_list	p;
int ret;
STREAM * s;

	s = s_stdout;
	if ( s == 0 )
		return -1; 
	if ( s->h.tbl == 0 )
		return -1;
#ifdef VA2
	va_start(p);
#else
	va_start(p,fmt);
#endif

	ret = _s_printf(s, fmt, p);
	
	va_end(p);
	return ret;
}

