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

#include	<stdio.h>
#include	<fcntl.h>
#include	"machine/err.h"
#include	"stream.h"
#include	"xlerror.h"
#include	"memory_routine.h"
#include	"memory_debug.h"
#include	"task.h"
#include	"xl.h"
#include	"lock_level.h"

#define LOAD_SIZE	4096
/*
<Load option="raw/xl"> filename </Load>
*/

typedef struct delay_load_t {
	DELAY_FUNC		h;
	STREAM * 		st;
	void *			work;
	void			(*close_func)();
	struct delay_load_t *	next;
/*
	L_CHAR *		term;
*/
	AUTOMATON *		term_a;
	L_CHAR *		backbuffer;
	short			bb_size;
	short			bb_ptr;
} DELAY_LOAD_T;

SEM	df_lock;
DELAY_LOAD_T * 	df_list;

void
init_Load(XLISP_ENV * env)
{
extern XL_SEXP * xl_Load();
	df_lock = new_lock(LL_DF);
	set_env(env,l_string(std_cm,"Load"),
		get_func_prim(xl_Load,FO_APPLICATIVE,0,2,2));
}

void
sp_gc_load()
{
DELAY_LOAD_T ** dp, * d;

	for ( dp = &df_list ; *dp ; ) {
		d = *dp;
		if ( TEST_MEM(d) ) {
			dp = &d->next;
			continue;
		}
		if ( d->close_func )
			(*d->close_func)(CF_CLOSE,d->work);
		_s_close(d->st);
		free_automaton(d->term_a);
		if ( d->backbuffer )
			d_f_ree(d->backbuffer);
		*dp = d->next;
	}
}

void
xl_load_gc(DELAY_LOAD_T * t)
{
	if ( t == 0 )
		return;
	if ( TEST_AND_SET(t) )
		return;
/*
	gc_text((char*)t->term);
*/
	if ( t->close_func )
		(*t->close_func)(CF_GC,t->work);
	return;
}

XL_SEXP * 
xl_delay_load(XL_SEXP * s)
{
DELAY_LOAD_T * df;
char * data;
int er,size;
int flag;

	df = (DELAY_LOAD_T*)s->delay.d.func;
	gc_push(df,xl_load_gc,"xl_delay_load");
	data = mmalloc(LOAD_SIZE,gc_text);
	flag = 0;
	for ( size = 0 ; size < LOAD_SIZE ; ) {
		er = s_read(df->st,data,LOAD_SIZE);
		if ( er < 0 ) {
			if ( errno == ESYS_AGAIN )
				continue;
			if ( errno == ESYS_INTR )
				continue;
			flag = 1;
			set_sexp_inh(0,XLT_PAIR,s,s);
			s->pair.cdr = 0;
			s->pair.car = get_error(
				s->h.file,
				s->h.line,
				XLE_SYSTEM_READ_FILE,
				l_string(std_cm,"load"),
				list(n_get_string("cannot read the file"),
					0));
			goto zero_end;
		}
		else if ( er == 0 ) {
			flag = 1;
			break;
		}
		size += er;
	}
	if ( size ) {
		set_sexp_inh(0,XLT_PAIR,s,s);
		s->pair.car = get_raw_set_data(data,size);
		if ( flag ) {
			s->pair.cdr = 0;
			goto zero_end;
		}
		else	s->pair.cdr = init_delay_func(&df->h);
	}
	else {
		set_sexp_inh(0,XLT_NULL,s,s);
		goto zero_end;
	}
	gc_pop(0,0);
	return s;
zero_end:

	if ( df->close_func )
		(*df->close_func)(CF_CLOSE,df->work);
	s_close(df->st);
	df->st = 0;
	df->close_func = 0;
	df->work = 0;
	gc_pop(0,0);
	return s;
}



int
load_long_char(L_CHAR * ch,STREAM * st)
{
unsigned char c;
int er;
	for ( ; ; ) {
		er = s_read(st,&c,1);
		if ( er <= 0 )
			return er;
		if ( (*st->h.cm->to_internal)
				(ch,st->h.cm_work,c) )
			return 1;
	}
}

typedef struct state_checker {
	AUTOMATON *	a;
	int		stptr;
	int		endptr;
} STATE_CHECKER;

#define STC_OK		0
#define STC_REJECT	1
#define STC_UNKNOWN	2

int
en_state_checker(STATE_CHECKER * sc,L_CHAR * data)
{
A_STATE *	state;
int p;
int min_ptr,max_ptr;

	if ( sc->a == 0 ) {
		sc->stptr ++;
		return STC_REJECT;
	}
	min_ptr = -1;
	max_ptr = -1;
	state = search_state(sc->a,AF_INIT);
	if ( state == 0 ) {
		sc->stptr ++;
		return STC_REJECT;
	}
	for ( p = sc->stptr ; data[p] ; p ++ ) {
		if ( state->flags & AF_ACCEPT ) {
			if ( min_ptr < 0 )
				min_ptr = p;
			max_ptr = p;
		}
		state = get_next_state(state,data[p]);
		if ( state == 0 ) {
			if ( max_ptr >= 0 ) {
				sc->endptr = max_ptr;
				return STC_OK;
			}
			sc->stptr ++;
			return STC_REJECT;
		}
	}
	if ( state->flags & AF_ACCEPT ) {
		if ( state->table_size == 0 ) {
			sc->endptr = p;
			return STC_OK;
		}
		return STC_UNKNOWN;
	}
	return STC_UNKNOWN;
}

XL_SEXP * 
xl_delay_load_text(XL_SEXP * s)
{
DELAY_LOAD_T * df;
L_CHAR * data;
int er,size;
int flag;
int ptr;
int len;
int end_f;

STATE_CHECKER sc;

	df = (DELAY_LOAD_T*)s->delay.d.func;
	gc_push(df,xl_load_gc,"xl_delay_load_text");
	data = mmalloc(sizeof(L_CHAR),gc_text);
	ptr = 0;
	data[ptr] = 0;
	end_f = 0;

	sc.a = df->term_a;
	sc.stptr = 0;
	sc.endptr = -1;

	for ( ; ; ) {
		if ( df->bb_ptr < df->bb_size ) {
			data[ptr] = df->backbuffer[
					df->bb_ptr++];
		}
		else {
			if ( df->backbuffer ) {
				d_f_ree(df->backbuffer);
				df->backbuffer = 0;
				df->bb_size = df->bb_ptr = 0;
			}
			er = load_long_char(&data[ptr],df->st);
			if ( er < 0 ) {
				if ( errno == ESYS_AGAIN )
					continue;
				if ( errno == ESYS_INTR )
					continue;
				set_sexp_inh(0,XLT_PAIR,s,s);
				s->pair.cdr = 0;
				s->pair.car = get_error(
					s->h.file,
					s->h.line,
					XLE_SYSTEM_READ_FILE,
					l_string(std_cm,"load"),
					list(n_get_string(
						"cannot read the file"),
						0));
				gc_pop(0,0);
				return s;
			}
			else if ( er == 0 ) {
				s_close(df->st);
				df->st = 0;
				if ( df->backbuffer )
					d_f_ree(df->backbuffer);
				df->backbuffer = 0;
				free_automaton(df->term_a);
				df->term_a = 0;
				end_f = 1;
				break;
			}
		}
		ptr ++;
		data = mrealloc(data,sizeof(L_CHAR)*(ptr+1),gc_text);
		data[ptr] = 0;

		if ( en_state_checker(&sc,data) == 0 ) {
			if ( ptr - sc.endptr ) {
			L_CHAR * bb;
			int bb_size;
				if ( df->backbuffer ){
					bb = df->backbuffer;
					bb_size = df->bb_size 
						- df->bb_ptr;
				}
				else {
				 	bb = 0;
					bb_size = 0;
				}
				df->backbuffer =
					d_alloc(
						sizeof(L_CHAR)*
						((ptr-sc.endptr)
						+ bb_size),123);
				memcpy(df->backbuffer,
					&data[sc.endptr],
					sizeof(L_CHAR)*
					(ptr-sc.endptr));
				if ( bb && bb_size )
					memcpy(&df->backbuffer
						[ptr-sc.endptr],
						&bb[df->bb_ptr],
						bb_size);
				df->bb_size = ptr-sc.endptr+bb_size;
				df->bb_ptr = 0;
				if ( bb )
					d_f_ree(bb);
			}
			data[sc.stptr] = 0;
			break;
		}
/*
		if ( ptr < len )
			continue;
		if ( len && l_strcmp(&data[ptr-len],df->term) == 0 ) {
			data[ptr-len] = 0;
			break;
		}
*/
	}
	if ( end_f ) {
		if ( data[0] == 0 ) {
			set_sexp_inh(0,XLT_NULL,s,s);
		}
		else {
			set_sexp_inh(0,XLT_PAIR,s,s);
			s->pair.car = get_string(data);
			s->pair.cdr = 0;
		}
	}
	else {
		set_sexp_inh(0,XLT_PAIR,s,s);
		s->pair.car = get_string(data);
		s->pair.cdr = init_delay_func(&df->h);
	}
	gc_pop(0,0);
	return s;
}

int
set_encoding(XL_SEXP * ret)
{
XL_SEXP * en, * q;
XL_SYM_FIELD * sf;
L_CHAR * code;
	en = car(ret);
	if ( get_type(en) != XLT_PAIR )
		return 0;
	q = car(en);
	if ( get_type(q) != XLT_SYMBOL )
		return 0;
	if ( l_strcmp(q->symbol.data,l_string(std_cm,"?xml")) != 0 &&
		l_strcmp(q->symbol.data,l_string(std_cm,"?xl")) != 0 )
		return 0;
	code = 0;
	for ( sf = q->symbol.field ; sf ; sf = sf->next ) {
		if ( l_strcmp(sf->name,l_string(std_cm,"encoding")) == 0 ) {
			code = sf->data;
			break;
		}
	}
	if ( code == 0 )
		return 0;
	_q_xml_encoding(ret,code);
	return 1;
}


XL_SEXP *
load_file(XLISP_ENV * env,XL_SEXP * s,int type,L_CHAR * filename,
	L_CHAR * encode,AUTOMATON * term_a,
	int flags,
	void (*func)(),void * work)
{
DELAY_LOAD_T * df;
STREAM * st;
XL_SEXP * ss,* ret,* r;
L_CHAR * access_filename;
void gc_gb_sexp();
XL_SEXP * ssp;

	access_filename = get_script(filename);
	if ( access_filename == 0 )
		goto access_error;
	switch ( type ) {
	case 0:
		st = s_open_file(n_string(std_cm,access_filename),O_RDONLY);
		if ( st == 0 )
			goto access_error;
		df = mmalloc(sizeof(*df),xl_load_gc);
		df->st = st;
		df->close_func = func;
		df->work = work;
		df->term_a = 0;
		df->backbuffer = 0;
		df->h.func = xl_delay_load;
		df->h.gc_func = xl_load_gc;
		df->h.check_func = 0;
		lock_task(df_lock);
		df->next = df_list;
		df_list = df;
		unlock_task(df_lock,"xl_Load");
		d_f_ree(access_filename);
		return init_delay_func(&df->h);
	case 1:
	case 4:
		st = s_open_file(n_string(std_cm,access_filename),O_RDONLY);
		if ( st == 0 )
			goto access_error;

		ret = init_parse(st,access_filename,access_filename);
		if ( type == 4 && ret )
			ret->h.file->flags |= XLF_HTML;
		if ( ret )
			ret->h.file->flags |= flags;
		if ( encode )
			_q_xml_encoding(ret,encode);
		else	set_encoding(ret);
		set_close_func(ret->h.file,func,work);
		d_f_ree(access_filename);
		return ret;
	case 2:
		st = s_open_file(n_string(std_cm,access_filename),O_RDONLY);
		if ( st == 0 )
			goto access_error;
		gc_push(0,0,"Load");
		ss = init_parse(st,access_filename,access_filename);
		if ( ss )
			ss->h.file->flags |= flags;
		if ( encode )
			_q_xml_encoding(ss,encode);
		else	set_encoding(ss);
		set_close_func(ss->h.file,func,work);
		ret = 0;

		gc_pop(ss,gc_gb_sexp);

		gc_push(ss,gc_gb_sexp,"Load");		
		gc_push(ss,gc_gb_sexp,"load");

		for ( ; get_type(ss) ; ) {

			lock_parse();
			lock_mem();
			r = car(ss);
			ss = cdr(ss);
			gc_pop(0,0);
			gc_push(0,0,"Load");
			gc_set_nl(r,gc_gb_sexp);
			gc_set_nl(ss,gc_gb_sexp);

			unlock_mem();
			unlock_parse();
			ret = eval(env,r);
			if ( get_type(ret) == XLT_ERROR ) {
				d_f_ree(access_filename);
				gc_pop(ret,gc_gb_sexp);
				gc_pop(ret,gc_gb_sexp);
				return ret;
			}
/*
			ss = cdr(ss);
			gc_pop(ss,gc_gb_sexp);
			gc_push(ss,gc_gb_sexp,"Load");
*/
		}
		gc_pop(ret,gc_gb_sexp);
		gc_pop(ret,gc_gb_sexp);
		d_f_ree(access_filename);
		return ret;
	case 3:
		st = s_open_file(n_string(std_cm,access_filename),O_RDONLY);
		if ( st == 0 )
			goto access_error;
		if ( encode )
			set_encoding_st(st,encode);
		df = mmalloc(sizeof(*df),xl_load_gc);
		df->st = st;
		df->close_func = func;
		df->work = work;
		df->term_a = term_a;
		df->backbuffer = 0;
		df->bb_size = 0;
		df->h.func = xl_delay_load_text;
		df->h.gc_func = xl_load_gc;
		df->h.check_func = 0;
		lock_task(df_lock);
		df->next = df_list;
		df_list = df;
		unlock_task(df_lock,"xl_Load");
		d_f_ree(access_filename);
		return init_delay_func(&df->h);
	}
	er_panic("load_file(1)");
access_error:
	if ( s )
		return get_error(
			s->h.file,
			s->h.line,
			XLE_PROTO_OPEN_FILE,
			l_string(std_cm,"load"),
			list(	n_get_string("cannot open the file"),
				get_string(filename),
				0));
	else	return get_error(
			0,
			0,
			XLE_PROTO_OPEN_FILE,
			l_string(std_cm,"load"),
			list(	n_get_string("cannot open the file"),
				get_string(filename),
				0));
}

XL_SEXP *
xl_Load(XLISP_ENV * env,XL_SEXP * s,
	XLISP_ENV * arg_env,XL_SYM_FIELD * sf)
{
L_CHAR * filename;
int type;
XL_SEXP * ss;
L_CHAR * encode;
AUTOMATON * term_a;
int flags;

	type = 1;
	encode = 0;
	flags = 0;
	for ( ; sf ; sf = sf->next ) {
		if ( l_strcmp(sf->name,l_string(std_cm,"option"))
				== 0 || 
		     l_strcmp(sf->name,l_string(std_cm,"format.mode"))
				== 0 ) {
			if ( l_strcmp(sf->data,l_string(std_cm,"raw"))
					== 0 )
				type = 0;
			else if ( l_strcmp(sf->data,
					l_string(std_cm,"xl"))
					== 0 )
				type = 1;
			else if ( l_strcmp(sf->data,
					l_string(std_cm,"html"))
					== 0 ) {
				type = 4;
				flags = XLF_TEXT;
			}
			else if ( l_strcmp(sf->data,
					l_string(std_cm,"exec"))
					== 0 )
				type = 2;
			else if ( l_strcmp(sf->data,
					l_string(std_cm,"text"))
					== 0 )
				type = 3;
			else goto invalid_param;
		}
		else if ( l_strcmp(sf->name,l_string(std_cm,"format.text"))
				== 0 ) {
			if ( l_strcmp(sf->data,l_string(std_cm,"on")) == 0 ) {
				flags |= XLF_TEXT;
			}
			else {
				flags &= ~XLF_TEXT;
			}
		}
		else if ( l_strcmp(sf->name,l_string(std_cm,"encoding"))
				== 0 ) {
			encode = sf->data;
		}
		else if ( l_strcmp(sf->name,l_string(std_cm,"terminate"))
				== 0 ) {
			term_a = get_fa(sf->data);
		}
	}

	ss = get_el(s,1);
	switch ( get_type(ss) ) {
	case XLT_ERROR:
		return ss;
	case XLT_STRING:
		filename = ss->string.data;
		break;
	default:
		goto typemissmatch;
	}
	return load_file(env,s,type,filename,encode,term_a,flags,0,0);
typemissmatch:
	return get_error(
		s->h.file,
		s->h.line,
		XLE_SEMANTICS_TYPE_MISSMATCH,
		l_string(std_cm,"load"),
		list(	n_get_string("type missmatch"),
			0));
invalid_param:
	return get_error(
		s->h.file,
		s->h.line,
		XLE_PROTO_INV_PARAM,
		l_string(std_cm,"load"),
		list(	n_get_string("invalid parameter"),
			0));
}
