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

#include	"machine/include.h"
#include	"memory_debug.h"
#include	"pdb.h"
#include	"change_endian.h"
#include	"filespace.h"
#include	"utils.h"


int
fa_write(int fd,void * d,int len)
{
char * dp;
int er;
int ret;
	ret = len;
	for ( dp = (char*)d ; len > 0 ; ) {
		er = u_write(fd,dp,len);
		if ( er <= 0 )
			break;
		len -= er;
		dp += er;
	}
	if ( len == 0 )
		return ret;
	else	return -1;
}

unsigned int
fs_raw_write(PDB * p,void * d,int flags)
{
PN_HEADER * h;
PN_HEADER hh;
int size;
#define DUMMY_SIZE 100
char dummy[DUMMY_SIZE];
int s;
int er,len;
unsigned int offset;

	offset = u_lseek(p->fid,0,SEEK_END);
	h = d;
	hh = *h;
	change_endian_header(&hh);
	size = hh.size;
	switch ( flags &(FSF_ONLYHEADER|FSF_RESERVE_SP) ) {
	case 0:
		fa_write(p->fid,d,size);
		break;
	case FSF_ONLYHEADER:
		fa_write(p->fid,d,sizeof(*h));
		break;
	case FSF_RESERVE_SP:
	case FSF_RESERVE_SP|FSF_ONLYHEADER:
		fa_write(p->fid,d,sizeof(*h));
		for ( s = size - sizeof(*h) ; s > 0 ; ) {
			if ( s > DUMMY_SIZE )
				len = DUMMY_SIZE;
			else	len = s;
			er = fa_write(p->fid,dummy,len);
			s -= er;
		}
		break;
	}
	return offset;
}

void
change_endian_freelist_to_net(PN_FREELIST * f)
{
	change_endian_header(&f->h);
	change_endian_i(f->next);
}


void
change_endian_freelist_to_host(PN_FREELIST * f)
{
	change_endian_header(&f->h);
	change_endian_i(f->next);
}

void *
get_file_record(unsigned int * retp,PDB * p,short type)
{
unsigned int fofs;
int er;
PN_HEADER * h;
int size;
unsigned int length;
int f;
	if ( retp ) {
		fofs = *retp;
		f = 0;
	}
	else {
		fofs = 0;
		f = 1;
	}
	h = d_alloc(sizeof(*h));
	length = u_lseek(p->fid,0,SEEK_END);
	for ( ; ; ) {
		if ( fofs >= length ) {
			d_f_ree(h);
			return 0;
		}
		er = u_lseek(p->fid,fofs,SEEK_SET);
		if ( er < 0 )
			er_panic("get_file_record(1)");
		er = u_read(p->fid,h,sizeof(*h));
		if ( er < sizeof(*h) )
			er_panic("get_file_record(2)");
		change_endian_header(h);
		if ( f && h->type == type )
			break;
		fofs += h->size;
		f = 1;
	}
	h = d_re_alloc(h,h->size);
	size = h->size - sizeof(*h);
	er = u_read(p->fid,h+1,size);
	if ( er < size )
		er_panic("get_file_record(3)");
	change_endian_header(h);
	if ( retp )
		*retp = fofs;
	return (void*)h;
}

PDB*
open_filespace(char * filename,int oflags,int mode,
	int flags,int type,char * encoding,
	unsigned int linear,unsigned int linear_length)
{
PDB * p;
unsigned int length;
PN_FILE_HEADER h;
PN_FREELIST	f, * ff;
FREELIST * fp;
	p = open_pdb(filename,oflags,mode,flags&PF_PDB_MASK);
	if ( p == 0 )
		return 0;
	length = u_lseek(p->fid,0,SEEK_END);
	if ( length == 0 ) {
		h.h.size = sizeof(h);
		h.h.type = PNT_FILE_HEADER;
		h.type = type;
		strcpy(h.version,VERSION);
		if ( encoding == 0 )
			strcpy(h.encoding,"UTF-8");
		else	strcpy(h.encoding,encoding);
		h.linear = linear;
		h.linear_length = linear_length;
		change_endian_file_header(&h);
		fs_raw_write(p,&h,0);
		if ( flags & PF_USEFREELIST ) {
			f.h.size = sizeof(f);
			f.h.type = PNT_FREELIST_HEADER;
			f.next = 0;
			fp = d_alloc(sizeof(*fp));
			memcpy(&fp->f,&f,sizeof(f));
			fp->next = 0;
			fp->fofs = sizeof(h);
			change_endian_freelist_to_net(&f);
			fs_raw_write(p,&f,0);
			p->freelist_work = fp;
		}
	}
	else {
		ff = get_file_record(0,p,PNT_FREELIST_HEADER);
		if ( ff ) {
			fp = d_alloc(sizeof(*fp));
			memcpy(&fp->f,ff,sizeof(*ff));
			fp->next = 0;
			fp->fofs = sizeof(h);
			d_f_ree(ff);
			change_endian_freelist_to_host(&fp->f);
			p->freelist_work = fp;
		}

	}
	return p;
}

void
free_freelist(FREELIST * f)
{
FREELIST * f2;
	for ( ; f ; ) {
		f2 = f->next;
		d_f_ree(f);
		f = f2;
	}
}


void
close_filespace(PDB * p)
{
	if ( p->freelist_work )
		free_freelist(p->freelist_work);
	flush_favt_cache(p);
	close_pdb(p);
}

void
flush_filespace(PDB * p)
{
	flush_favt_cache(p);
}


void
load_freelist(PDB * p,PN_FREELIST * f,unsigned int fofs)
{
int er;
	u_lseek(p->fid,fofs,SEEK_SET);
	er = u_read(p->fid,f,sizeof(*f));
	if ( er < sizeof(*f) )
		er_panic("load_freelist");
	change_endian_freelist_to_host(f);
}

void
save_freelist(PDB * p,PN_FREELIST * f,unsigned int fofs)
{
int er;
PN_FREELIST buf;
	buf = *f;
	change_endian_freelist_to_net(&buf);
	u_lseek(p->fid,fofs,SEEK_SET);
	er = fa_write(p->fid,&buf,sizeof(buf));
	if ( er < sizeof(buf) )
		er_panic("save_freelist");
}


FREELIST *
next_freelist(PDB * p,FREELIST * f)
{
FREELIST * ret;
int er;
	if ( f->next )
		return f->next;
	if ( f->f.next == 0 )
		return 0;
	ret = d_alloc(sizeof(*ret));

	load_freelist(p,&ret->f,f->f.next);

	if ( ret->f.h.type != PNT_FREELIST_HEADER &&
			ret->f.h.type != PNT_FREELIST_NODE )
		er_panic("next_freelist(2)");
	ret->next = 0;
	ret->fofs = f->f.next;
	return ret;
}

FREELIST *
insert_freelist(PDB * p,FREELIST * f,unsigned int fofs,int size)
{
FREELIST * ff;
int er;
PN_FREELIST buf;
	if ( size < sizeof(PN_FREELIST) )
		return 0;
	ff = d_alloc(sizeof(*ff));
	ff->f.h.type = PNT_FREELIST_NODE;
	ff->f.h.size = size;
	ff->f.next = f->f.next;
	save_freelist(p,&ff->f,fofs);
	ff->next = f->next;
	ff->fofs = fofs;

	f->next = ff;
	f->f.next = fofs;
	save_freelist(p,&f->f,f->fofs);

	return ff;
}

int
delete_freelist(unsigned int * retp,PDB * p,FREELIST * b)
{
unsigned int delete_fofs;
FREELIST * d;
PN_FREELIST buf;
int er;
	if ( b->f.next == 0 )
		return -1;
	delete_fofs = b->f.next;
	if ( b->next ) {
		d = b->next;
		b->f.next = d->f.next;
		b->next = d->next;
		d_f_ree(d);
	}
	else {
		load_freelist(p,&buf,delete_fofs);
		b->f.next = buf.next;
	}

	save_freelist(p,&b->f,b->fofs);

	if ( retp )
		*retp = delete_fofs;
	return 0;
}


unsigned int
alloc_filespace(PDB * p,PN_HEADER * h)
{
FREELIST * fp, * fp1;
unsigned int ret;
PN_FREELIST buf;
int er;

	if ( h->size < sizeof(PN_FREELIST) )
		er_panic("alloc_filespace sizeoerror(1)");
	fp = p->freelist_work;
	if ( fp == 0 )
		er_panic("alloc_filespace(1)");
	for ( ; ; ) {
		fp1 = next_freelist(p,fp);
		if ( fp1 == 0 )
			goto nothing;
		if ( fp1->f.h.size == h->size )
			goto fit_ok;
		if ( fp1->f.h.size > h->size + sizeof(PN_FREELIST) )
			goto large_ok;
		fp = fp1;
	}
fit_ok:

	delete_freelist(&ret,p,fp);
	buf.h = *h;
	save_freelist(p,&buf,ret);

	return ret;
large_ok:

	ret = fp1->fofs;
	buf.h = *h;
	save_freelist(p,&buf,ret);

	fp1->fofs += h->size;
	fp1->f.h.size -= h->size;
	save_freelist(p,&fp1->f,fp1->fofs);

	fp->f.next = fp1->fofs;
	save_freelist(p,&fp->f,fp->fofs);

	return ret;
nothing:

	buf.h = *h;
	change_endian_header(&buf.h);

	ret = fs_raw_write(p,&buf,FSF_RESERVE_SP|FSF_ONLYHEADER);

	return ret;
}

void
truncate_check(PDB * p,unsigned int length)
{
FREELIST * fp, * fp1;
int fofs;

	fp = p->freelist_work;
	if ( fp == 0 )
		er_panic("truncate_check(1)");
	fp1 = next_freelist(p,fp);
	if ( fp1 == 0 )
		return;
	if ( fp1->fofs + fp1->f.h.size != length )
		return;
	fofs = fp1->fofs;
	delete_freelist(0,p,fp);
	ftruncate(p->fid,fofs);
}

void
free_filespace(PDB * p,unsigned int fofs)
{
PN_FREELIST f;
FREELIST * fp,* fp1,* prev;
unsigned int length;

	load_freelist(p,&f,fofs);
	length = u_lseek(p->fid,0,SEEK_END);
	if ( fofs + f.h.size >= length ) {
		ftruncate(p->fid,fofs);
		truncate_check(p,length);
		return;
	}
	fp = p->freelist_work;
	if ( fp == 0 )
		er_panic("free_filespace(1)");
	fp1 = next_freelist(p,fp);
	if ( fp1 == 0 )
		goto only_one;
	if ( fp1->fofs < fofs )
		goto large;
	prev = fp;
	fp = fp1;
	for ( ; ; prev = fp, fp = fp1 ) {
		fp1 = next_freelist(p,fp);
		if ( fp1 == 0 )
			goto next_nothing;

		if ( fp1->fofs < fofs && fp->fofs > fofs )
			goto three_type;
	}
next_nothing:

	if ( fofs + f.h.size == fp->fofs )
		goto stack_on;
	goto only_one;
three_type:

	if ( fofs + f.h.size == fp->fofs ) {
		if ( fp1->fofs + fp1->f.h.size == fofs )
			goto sandwitch;
		else	goto stack_on;
	}
	else if ( fp1->fofs + fp1->f.h.size == fofs )
		goto hang_on;
	else	goto only_one;
large:

	if ( fp1->fofs + fp1->f.h.size == fofs )
		goto hang_on;
	else if ( fp1->fofs + fp1->f.h.size > fofs )
		er_panic("free_filespace(1)");
	else {
		fp1 = fp;
		fp = p->freelist_work;
		goto only_one;
	}

only_one:

	insert_freelist(p,fp,fofs,f.h.size);

	return;
hang_on:

	fp1->f.h.size += f.h.size;
	save_freelist(p,&fp1->f,fp1->fofs);

	return;
stack_on:

	fp->f.h.size += f.h.size;
	fp->fofs  = fofs;
	save_freelist(p,&fp->f,fp->fofs);
	prev->f.next = fofs;
	save_freelist(p,&prev->f,prev->fofs);

	return;
sandwitch:

	f.h.size += fp1->f.h.size + fp->f.h.size;
	delete_freelist(0,p,prev);
	fp1->f.h.size = f.h.size;
	save_freelist(p,&fp1->f,fp1->fofs);

	return;
}

int
check_freelist(PDB * p)
{
FREELIST * fp;
unsigned int prev,header;
unsigned int length;

	fp = p->freelist_work;
	if ( fp == 0 )
		return -1;
	header = fp->fofs;
	length = u_lseek(p->fid,0,SEEK_END);
	prev = 0;
	for ( ; fp ; prev = fp->fofs , fp = next_freelist(p,fp) ) {
		if ( fp == 0 )
			break;
		if ( fp->next ) {
			if ( fp->f.next != fp->next->fofs ) {
				fprintf(stderr,"prv%i ofs%i typ%x siz%i\n",
					prev,
					fp->fofs,fp->f.h.type,fp->f.h.size);
				fprintf(stderr,"f.next %i next->fofs %i\n",
					fp->f.next,fp->next->fofs);
				fprintf(stderr,"fp %x %x\n",fp,fp->next);
				er_panic("check_freelist(-1)");
			}
		}
		if ( fp->fofs >= length ) {
			fprintf(stderr,"prv%i ofs%i typ%x siz%i\n",
				prev,fp->fofs,fp->f.h.type,fp->f.h.size);
			er_panic("check_freelist(0)");
		}
		if ( prev && fp->f.h.type != PNT_FREELIST_NODE ) {
			fprintf(stderr,"prv%i ofs%i typ%x siz%i\n",
				prev,fp->fofs,fp->f.h.type,fp->f.h.size);
			er_panic("check_freelist(1)");
		}
		if ( prev == 0 )
			continue;
		if ( prev == header )
			continue;
		if (  prev <= fp->fofs ) {
			fprintf(stderr,"prv%i ofs%i typ%x siz%i\n",
				prev,fp->fofs,fp->f.h.type,fp->f.h.size);
			er_panic("check_freelist(2)");
		}
		if ( fp->fofs + fp->f.h.size >= prev ) {
			fprintf(stderr,"prv%i ofs%i typ%x siz%i\n",
				prev,fp->fofs,fp->f.h.type,fp->f.h.size);
			er_panic("check_freelist(3)");
		}
	}
	return 0;
}


int
check_filespace(PDB * p)
{
unsigned int fofs;
PN_HEADER h;
char * str;
unsigned int length;
int er;
	length = u_lseek(p->fid,0,SEEK_END);
	fofs = 0;
	for ( ; ; ) {
		if ( fofs == 0 )
			return 0;
		if ( fofs > length ) {
			str = "broken last segment";
			goto err;
		}
		u_lseek(p->fid,fofs,SEEK_SET);
		er = u_read(p->fid,&h,sizeof(h));
		if ( er < sizeof(h) ) {
			perror("cannot read");
			er_panic("check_filespace(1)");
		}
		change_endian_header(&h);
		if ( h.size < sizeof(h) ) {
			str = "too short segment";
			goto err;
		}
		fofs += h.size;
	}
err:
	fprintf(stderr,"%s offset %i type %x size %i\n",
		fofs,
		h.type,
		h.size);
	er_panic("check_filespace");
	return 0;
}


void *
read_filespace(PDB * p,unsigned int fofs)
{
ACC_PN_HEADER * h;
int er;
unsigned short size;

	h = d_alloc(sizeof(*h));
	h->a.fofs = fofs;
	u_lseek(p->fid,fofs,SEEK_SET);
	er = u_read(p->fid,&h->h,sizeof(h->h));
	if ( er < sizeof(h->h) )
		goto err;
	size = h->h.size;
	change_endian_s(size);
	h = d_re_alloc(h,size - sizeof(h->h) + sizeof(*h));
	er = u_read(p->fid,h+1,size - sizeof(h->h));
	if ( er < (int)size - sizeof(h->h) )
		goto err;
	return h;
err:
	d_f_ree(h);
	return 0;
}

int
write_filespace(PDB * p,void * h)
{
ACC_PN_HEADER * a;
int er;
unsigned short size;
	a = h;
	size = a->h.size;
	change_endian_s(size);
	u_lseek(p->fid,a->a.fofs,SEEK_SET);
	return fa_write(p->fid,&a->h,size);
}

