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

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


#include	"memory_debug.h"
#include	"lock_level.h"
#include	"pri_level.h"
#include	"task.h"
#include	"stream.h"
#include	"s_buf.h"
#include	"xl.h"
#include	"XLoHTTP.h"
#include	"xlerror.h"


typedef struct eu_list {
	struct eu_list *	next;
	int			ret;
	STREAM *		st;
	HTTP_INFO *		info;
} EU_LIST;

SEM ha_lock;
HTTP_AGENT_LIST * ha_root;
EU_LIST * eu_list_root;

HTTP_ERR he200 = {200,"OK"};
HTTP_ERR he404 = {404,"Not Found"};
HTTP_ERR he500 = {500,"Internal Server Error"};
HTTP_ERR he503 = {503,"Service Unavailable"};

int leftcmp(char* a,char*b);

void exec_url_task();
void tick_http_agent();
HTTP_AGENT_LIST * 
search_http_agent_list(char *path);
int lock_ha_access(HTTP_AGENT_LIST * a,int ret_flag);
void unlock_ha_access(HTTP_AGENT_LIST * a);
int output_server_header_by_info(STREAM * s,HTTP_INFO * info);
void output_error_print(STREAM * s,HTTP_ERR * e,char * msg,XL_SEXP * s_msg);
void _initialize_remote(HTTP_AGENT_LIST * a);
void return_code(STREAM * st,HTTP_INFO * of,XL_SEXP * snd);
int _exec_url(HTTP_INFO * info,STREAM * st);
EU_LIST *  _delete_eu_list();
EU_LIST * delete_eu_list();
void _insert_eu_list(EU_LIST * inp);
void insert_eu_list(EU_LIST * inp);
char * convert_percent(char * inp);



void
init_http_agent()
{


	ha_lock = new_lock(LL_HTTP_AGENT);
//	create_task(exec_url_task,PRI_USER_INTERFACE,0);
	new_tick(tick_http_agent,10,10);
}

void
gc_http_agent()
{
HTTP_AGENT_LIST * al;
	for ( al = ha_root ; al ; al = al->next ) {
		gc_gblisp_env(al->env);
		gc_gb_sexp(al->initialize);
	}
}


int
leftcmp(char* a,char*b)
{
int len;
	len = strlen(b);
	return memcmp(a,b,len);
}


HTTP_AGENT_LIST *
insert_http_agent_list(
	char * path,
	XLISP_ENV * env,
	L_CHAR* remote_url,
	XL_SEXP * initialize)
{
HTTP_AGENT_LIST * ret;
	lock_task(ha_lock);
	ret = d_alloc(sizeof(*ret));
	ret->access_tid = 0;
	ret->path = copy_str(path);
	ret->env = env;
	ret->next = ha_root;
	if ( remote_url ) {
		ret->remote_url = ll_copy_str(remote_url);
		ret->iid = -1;
	}
	else {
		ret->remote_url = 0;
		ret->iid = 0;
	}
	ret->initialize = initialize;

	ha_root = ret;
	unlock_task(ha_lock,"insert_http_agent_list");
	return ret;
}

HTTP_AGENT_LIST * 
search_http_agent_list(char *path)
{
HTTP_AGENT_LIST * ret;
int len;
	lock_task(ha_lock);
	ret = 0;
	for ( ret = ha_root ; ret ; ret = ret->next ) {
		if ( strcmp(ret->path,path) == 0 )
			break;
		len = strlen(ret->path);
		if ( memcmp(ret->path,path,len) )
			continue;
		if ( path[len] == '?' )
			break;
	}
	unlock_task(ha_lock,"insert_http_agent_list");
	return ret;
}

int
lock_ha_access(HTTP_AGENT_LIST * a,int ret_flag)
{
int ret;
	lock_task(ha_lock);
	ret = 0;
	if ( a->access_tid && ret_flag ) {
		ret = -1;
		goto end;
	}
	for ( ; a->access_tid ; ) {
		sleep_task((int)a,ha_lock);
		lock_task(ha_lock);
	}
	a->access_tid = get_tid();
end:
	unlock_task(ha_lock,"lock_ha_access");
	return ret;
}

void
unlock_ha_access(HTTP_AGENT_LIST * a)
{
	lock_task(ha_lock);
	a->access_tid = 0;
	wakeup_task((int)a);
	unlock_task(ha_lock,"lock_ha_access");
}

int
output_server_header_by_info(STREAM * s,HTTP_INFO * info)
{
	if ( info->err2.code )
		s_printf(s,"HTTP/1.1 %i %s\r\n",
			info->err2.code,info->err2.msg);
	else	s_printf(s,"HTTP/1.1 %i OK\r\n",info->err);
	s_printf(s,"Date: Fri, 20 Sep 2002 09:36:44 GMT\r\n");
	s_printf(s,"Server: LANDSCAPE GLOBALBASE SERVER\r\n");
	if ( info->flags & XoHINF_F_KEEP_ALIVE )
		s_printf(s,"Connection: keep-alive\r\n");
	else	s_printf(s,"Connection: close\r\n");
	if ( info->content_type )
		s_printf(s,"Content-Type: %s\r\n",info->content_type);
	else	s_printf(s,"Content-Type: text/html\r\n");
	if ( info->content_length >= 0 )
		s_printf(s,"Content-Length: %i\r\n",info->content_length);
	if ( info->flags & XoHINF_F_CACHE_OFF ) {
		s_printf(s,"Expires: -1\r\n");
		s_printf(s,"Cache-Control: no-cache\r\n");
		s_printf(s,"Pragma: no-cache\r\n");
	}
	s_printf(s,"\r\n");
	return 0;
}


void
output_error_print(STREAM * s,HTTP_ERR * e,char * msg,XL_SEXP * s_msg)
{
	s_printf(s,"<HTML>\n\r");
	s_printf(s,"<HEAD>\n\r");
	s_printf(s,"<TITLE>LANDSCAPE HTTP AGENT ERROR</TITLE>\n\r");
	s_printf(s,"</HEAD>\n\r");
	s_printf(s,"<BODY>\n\r");
	s_printf(s,"<HR>\n\r");
	s_printf(s,"HTTP Status: %i %s\n\r",e->code,e->msg);
	s_printf(s,"<HR>\n\r");
	s_printf(s,"Message: %s<BR>\n\r",msg);
	s_printf(s,"XL Code:");
	print_sexp(s,s_msg,0);
	s_printf(s,"\n\r<BR>\n\r");
	s_printf(s,"<HR>\n\r");
	s_printf(s,"</BODY>\n\r");
	s_printf(s,"</HTML>\n\r");
}

void
_initialize_remote(HTTP_AGENT_LIST * a)
{
XL_INTERPRETER * xli;
URL u;
XL_SEXP * ret;
XL_SEXP * ptr;

	if( a->iid > 0 ) {
		return;
	}

	get_url2(&u,a->remote_url);

	xli = new_xl_interpreter();
	xli->a_type = XLA_CONNECT;
	xli->env = a->env;
	xli->port = u.port;
	xli->environment = 1;
	xli->hostname = ll_copy_str(u.server);
	xli->connection_timeout = -1;

	free_url(&u);

	a->iid = setup_i(xli);


	if ( a->iid < 0 )
		return;

	ret = remote_query(
			a->iid,a->env,0,
			List(n_get_symbol("SetAgent"),
				get_string(u.agent),
				n_get_string("user"),
				-1));
	if ( get_type(ret) == XLT_ERROR ) {
		close_interpreter(a->iid);
		a->iid = -1;
	}
	for ( ptr = a->initialize ; get_type(ptr) == XLT_PAIR ;
			ptr = cdr(ptr) ) {
		ret = remote_query(
			a->iid,a->env,0,car(ptr));
		if ( get_type(ret) == XLT_ERROR )
			ss_printf("REMOTE ERR ");
			print_sexp(s_stdout,ret,0);
			ss_printf("\n");
			return;
	}
}

void
initialize_remote(HTTP_AGENT_LIST * a)
{
	lock_ha_access(a,0);
	_initialize_remote(a);
	unlock_ha_access(a);
}

void
return_code(STREAM * st,HTTP_INFO * of,XL_SEXP * snd)
{
XL_SEXP * sym;
XL_SEXP * data;
char * ctype;
char * err_msg;
int ret_len;
L_CHAR * cc;
	sym = car(snd);
	data = 0;
	if ( get_type(sym) != XLT_SYMBOL ) {
		err_msg = "symbol required";
		goto sys_err;
	}
	ctype = n_string(std_cm,sym->symbol.data);
	memset(of,0,sizeof(*of));
	cc = get_sf_attribute(sym->symbol.field,
			l_string(std_cm,"Cache-Control"));
	if ( cc == 0 ) {
		of->flags |= 0;
	}
	else if ( l_strcmp(cc,l_string(std_cm,"private")) == 0 ) {
		of->flags |= XoHINF_F_CACHE_OFF;
	}
	else {
		of->flags |= 0;
	}
	of->content_type = ctype;
	of->content_length = -1;
	of->err2 = he200;
	if ( strcmp(ctype,"text/html") == 0 ) {
		data = get_el(snd,1);
		if ( get_type(data) == XLT_STRING ) {
			output_server_header_by_info(st,of);
			s_printf(st,"%ls",data->string.data);
		}
		else {
			output_server_header_by_info(st,of);
			print_sexp(st,data,
				PF_MULTI_ROOT|PF_HTML|PF_INDENT);
		}
	}
	else if ( strcmp(ctype,"text/xml") == 0 ) {
		data = get_el(snd,1);
		if ( get_type(data) == XLT_STRING ) {
			output_server_header_by_info(st,of);
			s_printf(st,"%ls",data->string.data);
		}
		else {
			output_server_header_by_info(st,of);
			print_sexp(st,data,
				PF_MULTI_ROOT|PF_XML|PF_INDENT);
		}
	}
	else if ( strcmp(ctype,"image/jpeg") == 0 ) {
		data = get_el(snd,1);
		if ( get_type(data) != XLT_RAW ) {
			err_msg = "XLT_RAW is required";
			goto sys_err;
		}
		output_server_header_by_info(st,of);
		en_do(&ret_len,s_write,st,data->raw.data,data->raw.size);
	}
	else if ( strcmp(ctype,"image/gif") == 0 ) {
		data = get_el(snd,1);
		if ( get_type(data) != XLT_RAW ) {
			err_msg = "XLT_RAW is required";
			goto sys_err;
		}
		output_server_header_by_info(st,of);
		en_do(&ret_len,s_write,st,data->raw.data,data->raw.size);
	}
	s_close(st);
	return;
sys_err:
	of->err2 = he500;
	output_server_header_by_info(st,of);
	output_error_print(st,&of->err2,err_msg,data);
	s_close(st);
}


char *
convert_percent(char * inp)
{
char * ret;
char * p,* q;
char ch,_ch;
int cnt;
	ret = d_alloc(strlen(inp)+1);
	q = ret;
	p = inp;
	for ( ; ; ) {
		switch ( *p ) {
		case 0:
			*q = 0;
			break;
		case '%':
			p ++;
			ch = 0;
			cnt = 0;
			for ( ; cnt < 2 ; p ++ , cnt ++ ) {
				if ( *p == 0 )
					break;
				if ( '0' <= *p && *p <= '9' )
					_ch = *p - '0';
				else if ( 'a' <= *p && *p <= 'f' )
					_ch = *p - 'a' + 10;
				else if ( 'A' <= *p && *p <= 'F' )
					_ch = *p - 'A' + 10;
				else break;
				ch = (ch << 4) + _ch;
			}
			*q++ = ch;
			if ( ch == 0 )
				break;
			if ( *p == 0 ) {
				*q = 0;
				break;
			}
			continue;
		default:
			*q++ = *p++;
			continue;
		}
		break;
	}
	return ret;
}

int
_exec_url(HTTP_INFO * info,STREAM * st)
{
HTTP_AGENT_LIST * a;
XL_SEXP * sym;
char buf[10];
XL_SEXP * ret;
HTTP_INFO o_info;
char * _dir;

 	_dir = convert_percent(info->dir);
ss_printf("DIR = %s\n",_dir);
	if ( strcmp(_dir,"/cgi-bin/download.cgi") == 0 ) {
		d_f_ree(_dir);
		return 2;
	}
	if ( strcmp(_dir,"/cgi-bin/upload.cgi") == 0 ) {
		d_f_ree(_dir);
		return 2;
	}
	if ( leftcmp(_dir,"/cgi-bin/xlsv") == 0 ) {
		d_f_ree(_dir);
		return 2;
	}
	a = search_http_agent_list(_dir);
	if ( a == 0 ) {
		memset(&o_info,0,sizeof(o_info));
		o_info.err2 = he404;
		output_server_header_by_info(st,&o_info);
		output_error_print(st,&o_info.err2,info->dir,0);
		s_close(st);
		d_f_ree(_dir);
		return 0;
	}
	sym = n_get_symbol(info->method);
	sprintf(buf,"%i",info->http_ver);
	set_attribute(sym,
		l_string(std_cm,"http_ver"),
		l_string(std_cm,buf));
	sprintf(buf,"%i",info->http_rev);
	set_attribute(sym,
		l_string(std_cm,"http_rev"),
		l_string(std_cm,buf));
	set_attribute(sym,
		l_string(std_cm,"dir"),
		l_string(std_cm,_dir));
	if ( info->content_type ) {
		set_attribute(sym,
			l_string(std_cm,"Content-Type"),
			l_string(std_cm,info->content_type));
	}
	else {
		set_attribute(sym,
			l_string(std_cm,"Content-Type"),
			l_string(std_cm,""));
	}
	sprintf(buf,"%i",info->content_length);
	set_attribute(sym,
		l_string(std_cm,"Content-Length"),
		l_string(std_cm,buf));
	if ( info->charset ) {
		set_attribute(sym,
			l_string(std_cm,"Accept-Charset"),
			l_string(std_cm,info->charset));
	}
	else {
		set_attribute(sym,
			l_string(std_cm,"Accept-Charset"),
			l_string(std_cm,""));
	}
	if ( a->iid ) {
	int retry_cnt;
		if ( a->iid < 0 )
			goto init;
		retry_cnt = 2;
	retry:
		ret = remote_query(
			a->iid,a->env,0,List(n_get_symbol("HTTP"),
						List(sym,-1),
						-1));
		if ( get_type(ret) == XLT_ERROR ) {
			if ( ret->err.code == XLE_PROTO_INV_IID ) {
				a->iid = -1;
				if ( retry_cnt > 0 ) {
				init:
					initialize_remote(a);
					retry_cnt --;
					goto retry;
				}
			}
		}
	}
	else {
		ret = eval(a->env,List(sym,-1));
	}
	switch ( get_type(ret) ) {
	case XLT_ERROR:
		memset(&o_info,0,sizeof(o_info));
		o_info.content_length = -1;
		o_info.err2 = he503;
		output_server_header_by_info(st,&o_info);
		output_error_print(st,&o_info.err2,info->dir,ret);
		s_close(st);
		d_f_ree(_dir);
		return 0;
	case XLT_PAIR:
		return_code(st,&o_info,ret);
		d_f_ree(_dir);
		return 0;
	default:
		memset(&o_info,0,sizeof(o_info));
		o_info.content_length = -1;
		o_info.err2 = he500;
		output_server_header_by_info(st,&o_info);
		output_error_print(st,&o_info.err2,info->dir,ret);
		s_close(st);
		d_f_ree(_dir);
		return 0;
	}
	d_f_ree(_dir);
	return 0;
}

EU_LIST * 
_delete_eu_list()
{
EU_LIST * ret;
	for ( ; eu_list_root == 0 ; ) {
		sleep_task((int)&eu_list_root,ha_lock);
		lock_task(ha_lock);
	}
	ret = eu_list_root;
	eu_list_root = ret->next;
	return ret;
}

EU_LIST *
delete_eu_list()
{
EU_LIST * ret;
	lock_task(ha_lock);
	ret = _delete_eu_list();
	unlock_task(ha_lock,"delete_eu_list");
	return ret;
}

void
_insert_eu_list(EU_LIST * inp)
{
	inp->ret = 0xffff;
	inp->next = eu_list_root;
	eu_list_root = inp;
	wakeup_task((int)&eu_list_root);
	for ( ; inp->ret == 0xffff; ) {
		sleep_task((int)inp,ha_lock);
		lock_task(ha_lock);
	}
}

void
insert_eu_list(EU_LIST * inp)
{
	lock_task(ha_lock);
	_insert_eu_list(inp);
	unlock_task(ha_lock,"delete_eu_list");
}

void
exec_url_task()
{
XL_INTERPRETER*xli;
EU_LIST * eu;
	xli = new_xl_interpreter();
	xli->a_type = XLA_SELF;
	setup_i(xli);

	for ( ; ; ) {
		eu = delete_eu_list();
		gc_push(0,0,"exec_url");
		eu->ret = _exec_url(eu->info,eu->st);
		gc_pop(0,0);
		wakeup_task((int)eu);
	}

	close_self_interpreter();

}


int
exec_url(HTTP_INFO * info,STREAM * st)
{
//EU_LIST eu;
int ret;
XL_INTERPRETER * xli;

	if ( get_my_xli() == 0 ) {
		xli = new_xl_interpreter();
		xli->a_type = XLA_SELF;
		setup_i(xli);
	}

	gc_push(0,0,"exec_url");
	ret = _exec_url(info,st);
	gc_pop(0,0);

/*
	eu.info = info;
	eu.st = st;
	eu.ret = 0xffff;
	insert_eu_list(&eu);
	return eu.ret;
*/
	return ret;
}


void
tick_http_agent()
{
HTTP_AGENT_LIST * al;

XL_INTERPRETER * xli;
	if ( get_my_xli() == 0 ) {
		xli = new_xl_interpreter();
		xli->a_type = XLA_SELF;
		setup_i(xli);
	}

	for ( al = ha_root ; al ; al = al->next ) {
		if ( lock_ha_access(al,1) < 0 )
			continue;
		if ( al->remote_url == 0 )
			continue;
		if ( al->iid < 0 )
			_initialize_remote(al);
		else if ( check_iid(al->iid) == 0 ) {
			al->iid = -1;
			_initialize_remote(al);
		}
		unlock_ha_access(al);
	}
}
