#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <time.h>
#include "db.h"

#define DIRSTACKTOP fi->dirstack[fi->dirstackno]
uchar buff[100000];
void nextfile(dbase *db){
	int len=0,siz=0;
	uchar *s=NULL;
	if (db->fn.fn->len!=0){
		len=db->fn.fn->len;
		s=db->fn.fn->s;
		siz=sizeof(struct fileinfo);
	}else if (db->fn.fn->prefix==_FN_LFILEINFO){
		len=db->fn.lfn->len;
		s=db->fn.lfn->s;
		siz=sizeof(struct lfileinfo);
	}else if (db->fn.fn->prefix==_FN_DIRINFO){
		len=db->fn.dn->len;
		s=db->fn.dn->s;
		siz=sizeof(struct dirinfo);
	}else if (db->fn.fn->prefix==_FN_LDIRINFO){
		len=db->fn.ldn->len;
		s=db->fn.ldn->s;
		siz=sizeof(struct ldirinfo);
	}else if (db->fn.fn->prefix==_FN_DIREND){
		len=0;
		s=db->fn.fn->s;
		siz=sizeof(struct fileinfo);
	}
	len = huffmandecoder(db->hc,buff,s,len);
	db->fn.fn=ptrmov(db->fn.fn,siz+len);
}


dbase *initdbase(int *hist){
	dbase *fi=calloc(1,sizeof(dbase));
	fi->dirname=malloc(10000);
	fi->hist=hist;
	fi->newhist=calloc(256,sizeof(int));
	fi->hc=makehuffmancode(hist);
	fi->fnbegin.fn=fi->fn.fn=calloc(1,400000000);
	fi->fullname=fi->ptr=calloc(1,300000);
	fi->name=fi->fullname;
	fi->mtime=time(NULL);
	return fi;
}
int commn(uchar *a,uchar *b)
{ int i=0;
	while (a[i] && a[i]==b[i]) i++;
	return i;
}

void addfile(dbase *fi,uchar *file){
	fi->filetype=FN_FILE;
	strcpy((char *)fi->name,(char *)file);
}

void writefile(dbase *fi,uchar *file){
	int common=commn(file,fi->name),i;
	int prefix=common-(fi->ptr-fi->name),len=strlen((char *)file+common);
	if (!len) {len++;common--;prefix--;}
	uchar *buf;
	if (len<32 && prefix<=4 && prefix>=-3){
		struct fileinfo *fn=fi->fn.fn;
		fn->prefix=prefix+3;
		fn->len=len;
		buf=fn->s;
	} else{
		struct lfileinfo *fn=fi->fn.lfn;
		fn->slen=0;fn->sprefix=_FN_LFILEINFO;
		fn->prefix=prefix;
		fn->len=len;
		buf=fn->s;
	}
	huffmanencoder(fi->hc,buf,file+common,len);
	for (i=0;i<len;i++)fi->newhist[file[common+i]]++;
	fi->ptr=fi->name+common;
	addfile(fi,file);
	nextfile(fi);
}
void adddir(dbase *fi,uchar *s){
	fi->filetype=FN_DIR;
  strcpy((char *)fi->dirname,(char *)s);
	strcpy((char *)fi->name,(char *)s);
	strcat((char *)fi->name,"/");
	fi->name+=strlen((char *)fi->name);
	fi->ptr=fi->name;
}

void writedir(dbase *fi,uchar *s){
	 int len=strlen((char *)s),i;
	 for (i=0;i<len;i++)fi->newhist[s[i]]++;
	 unsigned	char *buf;
		if (len<255){
			fi->fn.dn->slen=0; fi->fn.dn->sprefix=_FN_DIRINFO;
			fi->fn.dn->len=len;
			buf=fi->fn.dn->s;
		} else {
			fi->fn.ldn->slen=0; fi->fn.dn->sprefix=_FN_LDIRINFO;
			fi->fn.ldn->len=len;
			buf=fi->fn.ldn->s;
		}
		huffmanencoder(fi->hc,buf,s,len);
	 	adddir(fi,s);
		fi->ptr=fi->name;
		nextfile(fi);
}
void addenddir(dbase *fi){
	fi->filetype=FN_ENDDIR;
	fi->name-=2;
	while (*fi->name!='/') fi->name--;
	fi->name++;
	*fi->name=0;
	fi->ptr=fi->name;
}
void writeenddir(dbase *fi){
	fi->fn.fn->len=0; fi->fn.fn->prefix=_FN_DIREND;
	addenddir(fi);
	nextfile(fi);	
}


uchar buf2[10000];
void readfile(dbase *fi){
		int len,prefix; uchar *buf;
	if(!fi->fn.fn->len&&(fi->fn.fn->prefix==_FN_DIRINFO||fi->fn.fn->prefix==_FN_LDIRINFO||fi->fn.fn->prefix==_FN_DIREND||fi->fn.fn->prefix==_FN_END)){
		if (fi->fn.fn->prefix==_FN_DIREND){
			addenddir(fi);
			nextfile(fi);
			return;
		}else if (fi->fn.fn->prefix==_FN_END){
			fi->filetype=FN_END;
			return;
		} else{
			if (fi->fn.fn->prefix==_FN_DIRINFO){
				len=fi->fn.dn->len;
				buf=fi->fn.dn->s;
			}else{
				len=fi->fn.ldn->len;
				buf=fi->fn.ldn->s;
			}
			huffmandecoder(fi->hc,buf2,buf,len);
			adddir(fi,buf2);
			nextfile(fi);
			return;
		}
	}else{
		fi->filetype=FN_FILE;
		if (fi->fn.fn->len){
			len=fi->fn.fn->len;
			prefix=fi->fn.fn->prefix-3;
			buf=fi->fn.fn->s;
		}else{
			len=fi->fn.lfn->len;
			prefix=fi->fn.lfn->prefix;
			buf=fi->fn.lfn->s;
		}
		fi->ptr+=prefix;
		huffmandecoder(fi->hc,fi->ptr,buf,len);
		nextfile(fi);
		return;
	}
}
void writeend(dbase *db){
	db->fn.fn->len=0;
	db->fn.fn->prefix=_FN_END;
}

struct savedb{
	int mc;
	int flen;
	time_t mtime;
	int hist[256],nhist[256];
};
void writedb(dbase *db,char *path){
	int fd=open(path,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
	int flen=ptrdist(db->fn.fn,db->fnbegin.fn)+1;
	char * dst=calloc(flen+sizeof(struct savedb),1);
	struct savedb *save=( struct savedb *) dst;
	save->flen=flen;
	save->mc=-42;
	save->mtime=db->mtime;
	memcpy(save->hist,db->hist,256*sizeof(int));	
	memcpy(save->nhist,db->newhist,256*sizeof(int));	
	memcpy(dst+sizeof(struct savedb),db->fnbegin.fn,flen);
	write(fd,dst,flen+sizeof(struct savedb));
	close(fd);
}
dbase *readdb(char *path){
	int fd=open(path,O_RDONLY);
	char *src=calloc(1,sizeof(struct savedb));
	if (fd==-1) return NULL;
	read(fd,src,sizeof(struct savedb));
	struct savedb *save=( struct savedb *) src;
	if (save->mc!=-42) exit(1);
	int flen=save->flen;	
	close(fd);
	fd=open(path,O_RDONLY);
	src=mmap(NULL,sizeof(struct savedb)+flen,PROT_READ,MAP_SHARED,fd,0);
	int *hist=save->hist;
	dbase *db=initdbase(hist);
	db->newhist=save->nhist;
	db->mtime=save->mtime;
	db->fn.fn=db->fnbegin.fn=(struct fileinfo*) ptrmov(src,sizeof(struct savedb));
	return db;
}
