/**********************************************************************
 
	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	"change_endian.h"
#include	"xl.h"
#include	"memory_debug.h"
#include	"pdb.h"
#include	"favt.h"
#include	"filespace.h"
#include	"dtree.h"
#include	"associate.h"


void gc_gb_sexp();

ASSOC_KEY	as_key[ASSOC_KEY_NO];


void
integer_endian(int * a)
{
	change_endian_i(*a);
}

int
cmp_assoc_integer(int * a,int * b)
{
	if ( *a < *b )
		return -1;
	if ( *a > *b )
		return 1;
	return 0;
}

void
change_endian_assoc_index(ASSOC_INDEX * ix)
{
	change_endian_i(ix->fofs);
	change_endian_s(ix->type);
	change_endian_s(ix->name);
}

void
change_endian_assoc_header_to_net(PN_ASSOC_HEADER * h)
{
int len;
int i;
	len = (h->h.size - PN_ASSOC_HEADER_S(0))/sizeof(ASSOC_INDEX);
	for ( i = 0 ; i < len ; i ++ )
		change_endian_assoc_index(&h->ix[i]);
	change_endian_header(&h->h);
}

void
change_endian_assoc_header_to_host(PN_ASSOC_HEADER * h)
{
int len;
int i;
	change_endian_header(&h->h);
	len = (h->h.size - PN_ASSOC_HEADER_S(0))/sizeof(ASSOC_INDEX);
	for ( i = 0 ; i < len ; i ++ )
		change_endian_assoc_index(&h->ix[i]);
}

void
change_endian_assoc_data(PN_ASSOC_DATA * d)
{
	change_endian_header(&d->h);
}

int
open_associate(
	char * filename,
	int oflags,
	int mode,
	int flags,
	int type,
	char * encoding)
{
int key;
int fofs;
void * ptr;

	for (  key = 1 ; as_key[key].p && key < ASSOC_KEY_NO ; key ++ );
	if ( key == ASSOC_KEY_NO )
		return -1;
	as_key[key].p = open_filespace(
		filename,oflags,mode,flags,type,encoding,0,0);
	if ( as_key[key].p == 0 )
		return -1;
	as_key[key].fheader = read_filespace(as_key[key].p,0);
	change_endian_file_header(&as_key[key].fheader->d);
	fofs = 0;
	ptr = get_file_record(&fofs,as_key[key].p,PNT_ASSOC_HEADER);

	if ( ptr == 0 ) {
		as_key[key].assoc = 0;
		as_key[key].assoc_len = 0;
	}
	else {
		d_f_ree(ptr);
		as_key[key].assoc = read_filespace(as_key[key].p,fofs);
		change_endian_assoc_header_to_host(&as_key[key].assoc->d);
		as_key[key].assoc_len
			= (as_key[key].assoc->d.h.size
			  - PN_ASSOC_HEADER_S(0))/sizeof(ASSOC_INDEX);
	}
	return key;
}

void
close_associate(int key)
{
	if ( key < 1 )
		return;
	if ( key >= ASSOC_KEY_NO )
		return;
	if ( as_key[key].p == 0 )
		return;
	close_filespace(as_key[key].p);
	if ( as_key[key].assoc )
		d_f_ree(as_key[key].assoc);
	d_f_ree(as_key[key].fheader);
	as_key[key].assoc = 0;
	as_key[key].assoc_len = 0;
	as_key[key].fheader = 0;
	as_key[key].p = 0;
}

int
new_index(int key,short type,short name)
{
ASSOC_INDEX * ix;
int i;
int ret;
	if ( key < 1 )
		return -1;
	if ( key >= ASSOC_KEY_NO )
		return -1;
	if ( as_key[key].p == 0 )
		return -1;
	if ( as_key[key].assoc == 0 ) {
		as_key[key].assoc = d_alloc(
			PN_ASSOC_HEADER_S(1)+sizeof(ACC_HEADER));
		as_key[key].assoc_len = 1;

		as_key[key].assoc->a.fofs = 0;
		as_key[key].assoc->d.h.type = PNT_ASSOC_HEADER;
		as_key[key].assoc->d.h.size = PN_ASSOC_HEADER_S(1);
		ix = as_key[key].assoc->d.ix;
		ret = 0;
	}
	else {
		for ( i = 0 ; i < as_key[key].assoc_len ; i ++ ) {
			if ( as_key[key].assoc->d.ix[i].name == name )
				return -1;
		}
		ret = as_key[key].assoc_len;
		as_key[key].assoc_len++;
		as_key[key].assoc = 
			d_re_alloc(as_key[key].assoc,
				PN_ASSOC_HEADER_S(ret+1)+
					sizeof(ACC_HEADER));
		ix = &as_key[key].assoc->d.ix[ret];
	}
	ix->type = type;
	ix->name = name;
	switch ( type ) {
	case AIT_INTEGER:
		ix->fofs = favt_alloc_root(
			as_key[key].p,FAT_ASSOC_INTEGER,integer_endian)
				->h.fofs;
		break;
	case AIT_STRING:
		ix->fofs = alloc_dtree(as_key[key].p,FAT_ASSOC_STRING)->h.fofs;
		break;
	default:
		er_panic("new_index");
	}
	if ( as_key[key].assoc->a.fofs )
		free_filespace(as_key[key].p,
			as_key[key].assoc->a.fofs);
	as_key[key].assoc->a.fofs
		= alloc_filespace(as_key[key].p,
			&as_key[key].assoc->d.h);
	change_endian_assoc_header_to_net(&as_key[key].assoc->d);
	write_filespace(as_key[key].p,
		as_key[key].assoc);
	return ret;
}


int
get_index(int key,short name)
{
int ret;
	if ( key < 1 )
		return -1;
	if ( key >= ASSOC_KEY_NO )
		return -1;
	if ( as_key[key].p == 0 )
		return -1;
	for ( ret = 0 ; ret < as_key[key].assoc_len ; ret ++ )
		if ( as_key[key].assoc->d.ix[ret].name == name )
			return ret;
	return -1;
}

int
get_index_type(int key,int index)
{
int ret;
	if ( key < 1 )
		return -1;
	if ( key >= ASSOC_KEY_NO )
		return -1;
	if ( as_key[key].p == 0 )
		return -1; 
	if ( index < 0 )
		return -1;
	if ( index >= as_key[key].assoc_len )
		return -1;
	return as_key[key].assoc->d.ix[index].type;
}

unsigned int
alloc_data(PDB * p,char * dd)
{
ACC_PN_ASSOC_DATA * dt;
int len;
unsigned int ret;
int r;

	len = strlen(dd)+1;
	if ( len < 8 )
		len = 8;
	dt = d_alloc(PN_ASSOC_DATA_S(len)+sizeof(ACC_HEADER));
	dt->d.h.type = PNT_ASSOC_DATA;
	dt->d.h.size = PN_ASSOC_DATA_S(len);
	strcpy(dt->d.data,dd);
	ret = dt->a.fofs = alloc_filespace(p,&dt->d.h);
	change_endian_assoc_data(&dt->d);
	write_filespace(p,dt);
	d_f_ree(dt);
	return ret;
}

int
insert_assoc_integer(PDB * p,ASSOC_INDEX * ix,void * from,char * dd)
{
FAVT_NODE * n, * nn;
FAVT_ROOT * r;
ASSOC_INTEGER in, * inp;

	in.data = *(int*)from;
	r = get_root(p,ix->fofs,integer_endian);
	n = favt_search(r,root_node(r),&in,cmp_assoc_integer);
	if ( n ) {
		inp = n->data;
		free_filespace(p,inp->fofs);
		inp->fofs = alloc_data(p,dd);
		n->h.flags |= FAF_DIRTY;
	}
	else {
		in.fofs = alloc_data(p,dd);
		n = favt_alloc_node(r,&in,sizeof(in));
		nn = favt_insert(r,&r->node,n,cmp_assoc_integer);
		if ( nn != n )
			er_panic("assoc_insert_integer");
	}
	return 0;
}

int
insert_assoc_string(PDB * p,ASSOC_INDEX * ix,void * from,char * dd)
{
FAVT_ROOT * r;
unsigned int old,new;
int size;

	r = get_root(p,ix->fofs,dtree_endian);
	old = search_dtree(&size,r,from,1);
	new = alloc_data(p,dd);
	if ( insert_dtree(r,from,old,new) < 0 )
		er_panic("insert_assoc_string");
	if ( old )
		free_filespace(p,old);
	return 0;
}

int
insert_associate(int key,int index,void * from,XL_SEXP * data)
{
ASSOC_INDEX * ix;
STREAM * st;
char * dd;
int ret;
	if ( key < 1 )
		return -1;
	if ( key >= ASSOC_KEY_NO )
		return -1;
	if ( index < 0 )
		return -1;
	if ( index >= as_key[key].assoc_len )
		return -1;
	ix = &as_key[key].assoc->d.ix[index];
	st = s_open_string_write(search_cm(
		as_key[key].fheader->d.encoding));
	if ( st == 0 )
		er_panic("insert_associate(1)");
	print_sexp(st,data,0);
	dd = s_get_string(st);
	if ( dd == 0 )
		er_panic("insert_associate(2)");
	switch ( ix->type ) {
	case AIT_INTEGER:
		ret = insert_assoc_integer(as_key[key].p,ix,from,dd);
		break;
	case AIT_STRING:
		ret = insert_assoc_string(as_key[key].p,ix,from,dd);
		break;
	default:
		er_panic("insert_associate(3)");
	}
	s_close(st);
	return ret;
}

ACC_PN_ASSOC_DATA *
search_assoc_integer(PDB * p,ASSOC_INDEX * ix,void * from)
{
FAVT_NODE * n, * nn;
FAVT_ROOT * r;
ASSOC_INTEGER in, * inp;
ACC_PN_ASSOC_DATA * ret;

	in.data = *(int*)from;
	r = get_root(p,ix->fofs,integer_endian);
	n = favt_search(r,root_node(r),&in,cmp_assoc_integer);
	if ( n == 0 )
		return 0;
	inp = n->data;
	ret = read_filespace(p,inp->fofs);
	change_endian_assoc_data(&ret->d);
	if ( ret == 0 )
		er_panic("search_assoc_integer");
	if ( ret->d.h.type != PNT_ASSOC_DATA )
		er_panic("search_assoc_integer(2)");
	return ret;
}

ACC_PN_ASSOC_DATA *
search_assoc_string(PDB * p,ASSOC_INDEX * ix,void * from,
	ASSOC_STRING_OPT * option)
{
FAVT_ROOT * r;
unsigned int fofs;
int size;
ACC_PN_ASSOC_DATA * ret;

	r = get_root(p,ix->fofs,dtree_endian);
	if ( option )
		fofs = search_dtree(&option->size,r,from,option->flags);
	else	fofs = search_dtree(0,r,from,1);
	if ( fofs == 0 )
		return 0;
	ret = read_filespace(p,fofs);
	change_endian_assoc_data(&ret->d);
	if ( ret == 0 )
		er_panic("search_assoc_string");
	if ( ret->d.h.type != PNT_ASSOC_DATA )
		er_panic("search_assoc_string(2)");
	return ret;
}

XL_SEXP *
search_associate(int key,int index,void * from,void * opt)
{
ASSOC_INDEX * ix;
STREAM * st;
ACC_PN_ASSOC_DATA * dd;


	if ( key < 1 )
		return 0;
	if ( key >= ASSOC_KEY_NO )
		return 0;
	if ( index < 0 )
		return 0;
	if ( index >= as_key[key].assoc_len )
		return 0;
	ix = &as_key[key].assoc->d.ix[index];
	switch ( ix->type ) {
	case AIT_INTEGER:
		dd = search_assoc_integer(as_key[key].p,ix,from);
		break;
	case AIT_STRING:
		dd = search_assoc_string(as_key[key].p,ix,from,opt);
		break;
	default:
		er_panic("insert_associate(3)");
	}
	if ( dd == 0 )
		return 0;
	st = s_open_string_read(dd->d.data,
		search_cm(as_key[key].fheader->d.encoding),
		strlen(dd->d.data),
		1);
	d_f_ree(dd);
	if ( st == 0 )
		er_panic("search_assoc");
	return car(init_parse(st,l_string(std_cm,"associate"),
			l_string(std_cm,"associate")));
}

typedef struct get_all_work {
	PDB * 			p;
	XL_SEXP *		ret;
	CODE_METHOD *		cm;
} GET_ALL_WORK;

int
get_all_integer_func(FAVT_NODE * a,GET_ALL_WORK * w)
{
XL_SEXP * s;
ASSOC_INTEGER * inp;
ACC_PN_ASSOC_DATA * dd;
STREAM * st;
	inp = a->data;
	dd = read_filespace(w->p,inp->fofs);
	if ( dd == 0 )
		er_panic("search_assoc_integer");
	change_endian_assoc_data(&dd->d);
	if ( dd->d.h.type != PNT_ASSOC_DATA )
		er_panic("search_assoc_integer(2)");

	gc_push(w->ret,gc_gb_sexp,"get_all_integer");

	st = s_open_string_read(dd->d.data,
		w->cm,
		strlen(dd->d.data),
		1);
	d_f_ree(dd);
	if ( st == 0 )
		er_panic("search_assoc");
	w->ret = cons(car(init_parse(st,l_string(std_cm,"associate"),
			l_string(std_cm,"associate"))),
			w->ret);

	gc_pop(w->ret,gc_gb_sexp);

	return 0;
}

int
get_all_string_func(int fofs,ACC_PN_ASSOC_DATA * dd,DTREE_TRACE_WORK * ww)
{
STREAM * st;
GET_ALL_WORK  * w;
XL_SEXP * ret;
ACC_PN_ASSOC_DATA * ddd;

	ddd = dd;
	w = ww->w;
	if ( dd == 0 ) {
		dd = read_filespace(w->p,fofs);
		if ( dd == 0 )
			er_panic("search_assoc_integer");
	}
	change_endian_assoc_data(&dd->d);
	if ( dd->d.h.type != PNT_ASSOC_DATA )
		er_panic("get_all_string(2)");


	gc_push(w->ret,gc_gb_sexp,"get_all_string");

	st = s_open_string_read(dd->d.data,
		w->cm,
		strlen(dd->d.data),
		1);
	if ( st == 0 )
		er_panic("search_assoc");
	ret = cons(car(init_parse(st,l_string(std_cm,"associate"),
			l_string(std_cm,"associate"))),
			w->ret);
	w->ret = ret;

	gc_pop(w->ret,gc_gb_sexp);

	if ( ddd == 0 )
		d_f_ree(dd);

	return 0;
}



XL_SEXP *
get_assoc_all(int key,int index)
{
ASSOC_INDEX * ix;
STREAM * st;
GET_ALL_WORK w;
FAVT_ROOT * r;

	if ( key < 1 )
		return 0;
	if ( key >= ASSOC_KEY_NO )
		return 0;
	if ( index < 0 )
		return 0;
	if ( index >= as_key[key].assoc_len )
		return 0;
	ix = &as_key[key].assoc->d.ix[index];
	w.ret = 0;
	w.cm = search_cm(as_key[key].fheader->d.encoding),
check_cache("assoc 1");
	r = get_root(as_key[key].p,ix->fofs,integer_endian);
check_cache("assoc 2");

	switch ( ix->type ) {
	case AIT_INTEGER:
		favt_trace_from_large(r,root_node(r),get_all_integer_func,&w);
		break;
	case AIT_STRING:
		dtree_trace_from_large(r,get_all_string_func,&w);
		break;
	default:
		er_panic("get_assoc_all(3)");
	}
	return w.ret;
}
