#include <sys/time.h>
#include <stdlib.h>
#include <string.h>

#include "indexl.h"

static unsigned BS,US;

static INLINE int
z_get_block(Map *z,Bn bno,unsigned **b1,unsigned **b,unsigned **be,
	    const Offset *o,unsigned *bs) {

  int i;

  if (!map_get(z,bno*BS,(bno+1)*BS))
    return 0;

  *b=*b1=z->m1;
  *be=z->me;

  if (o && o->bn==bno) {
    *be=*b1+o->byte+1;
    for (*bs=1,i=0;i<o->bit;i++,*bs+=*bs);
  } else if (bs)
    *bs=-1;

  return 1;

}


static INLINE int
z_update_file_offset(Files *f,File *fp,Bn bno,unsigned byte,unsigned bit) {

  Datum n1;
  Offset o;
  Gen g;

  memset(&n1,0,sizeof(n1));
  memset(&o,0,sizeof(o));

  o.bit=bit;
  o.byte=byte;
  o.bn=bno;

  g.v=fp->n;
  g.w=strlen(fp->n);

  fp->off.o=n1.off.o=o;

  if (!hash_put(&f->d,&g,&n1))
    return 0;

  return 1;
    
}

static INLINE int 
z_update_word_offset(Hash *d,Gen *g,Datum *n,Bn bno,unsigned *tot) {

  Datum n1;

  if (!n)
    return 0;

  if (n->off.l.last==bno)
    return 1;

  n1.off.l.last=bno;
  n1.off.l.total=n->off.l.total+1;
  n1.code=n->code;
  (*tot)++;

  if (!hash_put(d,g,&n1))
    return 0;

  return 1;

}


static INLINE int
z_prep_block(Words *w,unsigned tot) {

  Gen g;
  Datum *n;
  unsigned u,u1,nw,nt;
  Hash_totals *ht;
  W *ww;

  if (!(ht=hash_get_totals(&w->d))) {
    err("Can't get hash totals in z_prep_block\n");
    return 0;
  }
  if (!map_get(&w->b,0,sizeof(Bn)*(tot+2*ht->nw)))
    return 0;

  nw=nt=0;
  for (u=u1=0,ww=w->he;ww<(W *)w->h.me;ww=(W *)(ww->c+ww->u)) {

    Datum n1;

    g.v=ww->c;
    g.w=ww->u;

    if (!(n=hash_get(&w->d,&g))) {
      err("Can't get %*.*s in z_prep_block\n",g.w,g.w,g.v);
      return 0;
    }
    n1.code=n->code;
    u+=(2+n->off.l.total)*sizeof(Bn);
    nt+=n->off.l.total;
    nw++;
    n1.off.u=u1;
    u1=u;
    if (!hash_put(&w->d,&g,&n1))
      return 0;

  }

  return 1;

}

static INLINE int
z_block_index(Map *z,Words *w,Files *f) {

  unsigned *y1,*y,*ye;
  Bn bn,*bnp;
  Datum *n;
  unsigned ns,nss;
  Gen g;
  W *ww;
  File *fp;

  fp=f->p.me;
  fp--;
  for (bn=0;z_get_block(z,bn,&y1,&y,&ye,&fp->off.o,&nss);bn++) 

    for (ns=1;(ww=huff_decode(w->h.m1,w->he,&y,ye,&ns,nss));) {

      g.v=ww->c;
      g.w=ww->u;
      if (!(n=hash_get(&w->d,&g))) {
	err("Can't get %*.*s in z_block_index\n",g.w,g.w,g.v);
	return 0;
      }

      bnp=(Bn *)(w->b.m1+n->off.u);
      if (*bnp!=bn) {
	*bnp=bn;
	bnp[1]++;
	bnp[1+bnp[1]]=bn;
      }
      
    }

  return 1;
    
}
      
static INLINE int
z_test(Map *z,Words *w,Files *ff) {

  unsigned *y1,*y,*ye,ns,ns1,i,mm,nss;
  Bn bn;
  float f;
  W *ww;
  struct timeval tv,tv1;
  Map m={0},n={0};
  File *fp,*fpe;
  Hash_totals *ht;

  if (!(ht=hash_get_totals(&w->d))) {
    err("Can't get hash totals in z_testn");
    return 0;
  }
  mm=ht->mm;

  if (!map_get(&m,0,mm))
    return 0;

  inf("Timing decompression ...");

  gettimeofday(&tv,NULL);

  fpe=ff->p.me;
  fpe--;
  for (bn=0;z_get_block(z,bn,&y1,&y,&ye,&fpe->off.o,&nss);m.m=m.m1,bn++) {
    for (ns=1;(ww=huff_decode(w->h.m1,w->he,&y,ye,&ns,nss));) 
      token_to_str(ww,&m);
    if (m.m>m.me) {
      err("mm to small in z_file_decode\n");
      return 0;
    }
  }


  gettimeofday(&tv1,NULL);
  f=(tv1.tv_sec-tv.tv_sec)+1.0e-6*(tv1.tv_usec-tv.tv_usec);

  inf("done\n%d blocks in %e sec, %e usec/block\n",
	 bn,f,1.0e6*f/bn);

  inf("Testing file offsets...");

  for (fp=ff->p.m1,fpe=ff->p.me;fp<fpe-1;fp++) {

    bn=fp->off.o.bn;
    if (!z_get_block(z,bn,&y1,&y,&ye,&fp[1].off.o,&nss))
      return 0;

    y+=fp->off.o.byte;
    for (ns=1,i=0;i<fp->off.o.bit;i++,ns+=ns);
    for (ns1=1,i=0;i<fp[1].off.o.bit;i++,ns1+=ns1);

    if (!map_get(&m,0,mm))
      return 0;
    m.m=m.m1;
    do {

      for (;(ww=huff_decode(w->h.m1,w->he,&y,ye,&ns,nss));) {
	token_to_str(ww,&m);
	if (bn==fp[1].off.o.bn && y-y1==fp[1].off.o.byte && ns==ns1)
	  break;
      }

      if (!ww) {
	if (!z_get_block(z,++bn,&y1,&y,&ye,&fp[1].off.o,&nss))
	  return 0;
	if (!map_get(&m,m.o,m.o+(m.me-m.m1)+mm))
	  return 0;
      }

    } while (!ww);
	  
    
    if (!map_open(&n,fp->n,0))
      return 0;
    if (m.m-m.m1!=n.size) {
      err("Size mismatch %s %u %u\n",fp->n,m.m-m.m1,n.size);
      return 0;
    }
    if (!map_get(&n,0,n.size))
      return 0;

    if (memcmp(m.m1,n.m1,n.size)) {
      err("Mismatch at file %s\n",fp->n);
      return 0;
    }

    if (!map_close(&n))
      return 0;

  }
	    
  inf("done\n");

  if (!map_close(&m))
    return 0;

  return 1;

}

static INLINE int
z_write_z(Dict *d,unsigned *tot) {

  Map m={0};
  Datum *n;
  unsigned *b1,*b,*be,y,bb;
  Bn bno;
  char nb=0,tnb=0;
  void *tm=NULL;
  Gen g;
  File *fp=d->f.p.m1,*fpe=d->f.p.me;
  Hash_totals *ht,ht1;
  
  if (!(ht=hash_get_totals(&d->w.d))) {
    err("Can't get hash totals in z_write_z\n");
    return 0;
  }
  ht1=*ht;

  if (!z_get_block(&d->z,(bno=0),&b1,&b,&be,NULL,NULL))
    return 0;

  bb=0;
  *tot=0;
  for (;fp<fpe-1;fp++) {

    if (!map_open(&m,fp->n,0))
      return 0;
    if (!map_get(&m,0,m.size))
      return 0;
    tm=m.m1;

    if (!z_update_file_offset(&d->f,fp,bno,b-b1,tnb))
      return 0;

    for (;m.m<m.me;) {
      
      if (!str_to_token(&m,&g))
	return 0;

      if (!(n=hash_get(&d->w.d,&g))) {
	err("Can't get %*.*s in z_write_z\n",g.w,g.w,g.v);
	return 0;
      }

      y=n->code.c.value;
      nb=n->code.c.bits;

      if (!z_update_word_offset(&d->w.d,&g,n,bno,tot))
	return 0;

      *b|=y<<tnb;
      tnb+=nb;
      
      if (tnb>=US) {
	tnb-=US;
	if (++b==be) {
	  bb+=m.m-tm;
	  tm=m.m;
	  ht1.mm=ht1.mm<bb ? bb : ht1.mm;
	  bb=0;
	  if (!z_get_block(&d->z,++bno,&b1,&b,&be,NULL,NULL))
	    return 0;
	  if (tnb) {
	    *b=y;
	    bb+=g.w+1;
	    tnb=nb;
	  } else 
	    *b=0;
	} else 
	  *b=y>>(nb-tnb);
      }
      
    }
    
    inf("File %s: %d bytes\n",fp->n,m.me-m.m1);
    bb+=m.m-tm;
    if (!map_close(&m))
      return 0;
    
  }

  if (!z_update_file_offset(&d->f,fp,bno,b-b1,tnb))
    return 0;

  if (!hash_put_totals(&d->w.d,&ht1))
    return 0;

  return 1;

}

      

int
z_encode_files(Dict *d) {

  unsigned tot;
  const char *c;

  if (!z_write_z(d,&tot))
    return 0;

  c=strdup(d->z.n);
  if (!map_close(&d->z)) {
    free((void *)c);
    return 0;
  }
  if (!map_open(&d->z,c,0))
    return 0;
  free((void *)c);

  if (!z_prep_block(&d->w,tot))
    return 0;

  if (!z_block_index(&d->z,&d->w,&d->f))
    return 0;

  if (!z_test(&d->z,&d->w,&d->f))
    return 0;

  return 1;

}

static INLINE int
z_file_breakpoint(const File *f,unsigned bn,
		  unsigned *y1,unsigned *ye,
		  unsigned **yn,unsigned *nsn) {

  unsigned i,j;

  if (f->off.o.bn==bn) {
    *yn=y1+f->off.o.byte;
    for (i=1,j=0;j<f->off.o.bit;j++,i+=i);
    *nsn=i;
  } else {
    *yn=ye;
    *nsn=-1;
  }

  return 1;
  
}

int
z_file_decode(Dict *d,Bn bn,unsigned sv,Map *m,Map *l) {

  unsigned *y1,*y,*ye,*yn,ns,nss,nsn,cm,ph,phi;
  W *w,*w1;
  Hit t={0},*tp;
  Offset o={0};
  Hash_totals *ht;
  const File *f;

  o.bn=bn;

  ns=1;
  y=&sv;
  if (!(w1=huff_decode(d->w.h.m1,d->w.he,&y,y+1,&ns,-1)))
    return 0;

  f=(File *)d->f.p.me-1;
  if (!z_get_block(&d->z,bn,&y1,&y,&ye,&f->off.o,&nss))
    return 0;

  if (!(ht=hash_get_totals(&d->w.d))) {
    err("Can't get hash totals in z_file_decode\n");
    return 0;
  }
  if (!map_get(m,0,ht->mm))
    return 0;

  if (!(f=files_get_by_offset(&d->f,o)))
    return 0;
  cm=0;
  z_file_breakpoint(++f,bn,y1,ye,&yn,&nsn);

  phi=(Hit *)l->m-(Hit *)l->m1;
  for (ns=1,ph=phi;(w=huff_decode(d->w.h.m1,d->w.he,&y,ye,&ns,nss));) {
    
    if (w==w1) {
      t.b=(char *)cm;
      t.l=(char *)(m->m-m->m1);
      t.f=f[-1].n;

      if (!map_write_element(l,t))
	return 0;

    }

    token_to_str(w,m);
      
    if (y==yn && ns==nsn) {
      cm=m->m-m->m1;
      for (tp=(Hit *)l->m1+ph;tp<(Hit *)l->m;tp++,ph++)
	tp->be=(char *)cm;
      z_file_breakpoint(++f,bn,y1,ye,&yn,&nsn);
    }
      
  }

  if (m->m>m->me) {
    err("mm to small in z_file_decode\n");
    return 0;
  }

  for (tp=(Hit *)l->m1+ph;tp<(Hit *)l->m;tp++,ph++)
      tp->be=(char *)(m->m-m->m1);

  for (tp=(Hit *)l->m1+phi;tp<(Hit *)l->m;tp++) {
    tp->m1=m->m1;
    tp->me=m->me;
    tp->b=(unsigned)tp->b+m->m1;
    tp->be=(unsigned)tp->be+m->m1;
    tp->l=(unsigned)tp->l+m->m1;
    if (*tp->l==32)
      tp->l++;
  }

  return 1;

}
      

int
z_open(Map *z,const char *s,int new) {

  Offset o;
  
  if (!BS) {
    BS=(o.byte=-1)+1;
    BS*=(1+(o.bit=-1))>>3;
    US=(o.bit=-1)+1;
  }

  return map_open(z,map_temp_str(".%s.z",s),new);
  
}

int
z_close(Map *z) {

  return map_close(z);

}
  
