/*    MPRESS - Compression-Decompression pour Magma
       Dec 96 - Sylvain HUET
*/
// Modification history:
//$ FA(06/06/2001): Includes baselib.h
//
//$LB (27/05/2002) : MGzip and MGunzip functions : compute the size in an unsigned long, instead of int
//
//$BLG (2/09/2005) : MGzip & MGunzip : forced end of output character strings (\0) to avoid data overflow.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "common/vscol.h"
#include "scolMMemory.h"
#include "vm/mbytec.h"
#include "listlab.h"
#include "baselib.h"


#define CLEARCODE 256
#define EOI 257
#define NBMOTIF 4096

struct Dictcase
{
  int val;
  int pere;
  int fils;
  int frere;
};
typedef struct Dictcase *dictcase;

struct Dict
{
  mmachine m;
  int i;      /* index d'ecriture */
  int imax;
  dictcase tape;
  int im;

  char *buf;

  int IOcodein;  /* code buffer IO in */
  int IObitsin;  /* taille buffer IO in */
  int IOcodeout; /* code buffer IO out */
  int IObitsout; /* taille buffer IO out */
  int tapein;
  int tapeout;
};
typedef struct Dict *dict;


#define MZSZBLOC 1024

int MZaddbloc(mmachine m)
{
  int a,k;

  if (MMpush(m,NIL)) return MERRMEM;
  if (MMpush(m,2*2)) return MERRMEM;
  if (k=MBdeftab(m)) return k;
  // SH000808
  a=MMget(m,1)>>1;
  if (a==NIL) MMset(m,1,MMget(m,0));
  else
  {
	  while(MMfetch(m,a,OFFLNEXT)!=NIL) a=MMfetch(m,a,OFFLNEXT)>>1;
	  MMstore(m,a,OFFLNEXT,MMget(m,0));
  }
  //
  return 0;
}


int Mcharout(dict d,int c)
{
  mmachine m;
  int k,s,sl;

  m=d->m;
  if (d->tapeout>=MZSZBLOC)
    {
      if (k=MZaddbloc(m)) return k;
      d->tapeout=0;
    }
  if (d->tapeout==0)
    {
      sl=(MZSZBLOC+4)>>2;
      s=MMmalloc(m,sl+1,TYPEBUF); if (s==NIL) return MERRMEM;
      MMset(m,0,s+s+1);
    }
  *(MMstartstr(m,MMget(m,0)>>1)+(d->tapeout++))=c;
  MMstore(m,MMget(m,0)>>1,0,d->tapeout);
  return 0;
}

/* envoie nbits bits sur la sortie */
int Mcodeout(dict d, int c)
{
  int nbits,k;

  if (d->i<512) nbits=9;
  else if (d->i<1024) nbits=10;
  else if (d->i<2048) nbits=11;
  else nbits=12;

  d->IOcodeout=(d->IOcodeout<<nbits)+c;
  d->IObitsout+=nbits;
  while(d->IObitsout>=8)
    {
      d->IObitsout-=8;
      if (k=Mcharout(d,(d->IOcodeout>>d->IObitsout)&255)) return k;
    }
  return 0;
}

/* flush le buffer de sortie, en completant avec des 0 */
int Mflushout(dict d)
{
  int k;
  if (d->IObitsout)
    {
      if (k=Mcharout(d,(d->IOcodeout<<(8-d->IObitsout))&255)) return k;
    }
  return MZaddbloc(d->m);
}

int Mcharin(dict d)
{
  mmachine m=d->m;

  if (d->tapein>=MMsizestr(m,MMget(m,2)>>1)) return EOF;
  return (*(MMstartstr(m,MMget(m,2)>>1)+(d->tapein++)))&255;
}

int maskbit[33]=
{0x00000000,0x00000001,0x00000003,0x00000007,
 0x0000000f,0x0000001f,0x0000003f,0x0000007f,
 0x000000ff,0x000001ff,0x000003ff,0x000007ff,
 0x00000fff,0x00001fff,0x00003fff,0x00007fff,
 0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff,
 0x000fffff,0x001fffff,0x003fffff,0x007fffff,
 0x00ffffff,0x01ffffff,0x03ffffff,0x07ffffff,
 0x0fffffff,0x1fffffff,0x3fffffff,0x7fffffff,
 0xffffffff};

int Mgetcode(dict d)
{
  int i,nbits;

  if (d->i<511) nbits=9;
  else if (d->i<1023) nbits=10;
  else if (d->i<2047) nbits=11;
  else nbits=12;

  while(d->IObitsin<nbits)
    {
      d->IOcodein=(d->IOcodein<<8)+(Mcharin(d)&255);
      d->IObitsin+=8;
    }
  d->IObitsin-=nbits;
  i=(d->IOcodein>>d->IObitsin)&maskbit[nbits];
  return i;
}

int Mbufout(dict d,int i)
{
  int k,size;
  char *p;

  p=&d->buf[d->imax];
  size=0;
  do
	{
	  p--;
	  *p=d->tape[i].val;
	  i=d->tape[i].pere;
	  size++;
	}
  while(i!=-1);

  for(i=0;i<size;i++) if (k=Mcharout(d,*(p++))) return k;
  return 0;
}

/* ajoute un motif : retourne le numero du motif ou -1 si erreur */
int Madddict(dict d, int val, int pere)
{
  if (d->i>=d->imax) return -1;

  d->tape[d->i].val=val;
  d->tape[d->i].pere=pere;
  d->tape[d->i].fils=-1;
  if (pere!=-1)
    {
      d->tape[d->i].frere=d->tape[pere].fils;
      d->tape[pere].fils=d->i;
    }
  else d->tape[d->i].frere=-1;
  d->i++;
  return d->i-1;
}

/* initialise le dictionnaire */
int Minidict(dict d)
{
  int i;
  
/*  MMechostr(MSKDEBUG,"reinitdict\n");*/
  d->i=0;
  d->im=-1;
  for(i=0;i<256;i++) Madddict(d,i,-1);
  Madddict(d,0,-1);  /* CLEARCODE */
  Madddict(d,0,-1);  /* EOI */
  return 0;
}

/* ouvre le dictionnaire, fait les mallocs */
int Mopendict(dict d, int nbcase)
{
  d->imax=nbcase;
  d->tape=(dictcase)malloc(sizeof(struct Dictcase)*nbcase);
  if (d->tape==NULL) return -1;

  d->buf=(char*)malloc(nbcase);
  if (d->buf==NULL)
  {
	  free((void*)d->tape);
	  return -1;
  }
  return 0;
}

/* ferme le dictionnaire, fait les free */
int Mclosedict(dict d)
{
  free((void*)d->tape);
  free((void*)d->buf);
  return 0;
}

/* cherche un motif, retourne son index ou -1 si introuvable */
int Mfindmotif(dict d,int c,int pere)
{
  int i;

  i=d->tape[pere].fils;
  while(i!=-1)
    {
      if (d->tape[i].val==c) return i;
      i=d->tape[i].frere;
    }
  return -1;
}

/* traite un nouveau caractere */
int Maddchar(dict d,int c)
{
  int k;

  if (d->im==-1) k=c&255;
  else k=Mfindmotif(d,c&255,d->im);
  if (k!=-1)
    {
      d->im=k;
      return 0;
    }
  if (Mcodeout(d,d->im)) return -1;
  if (Madddict(d,c&255,d->im)<0)
    {
      d->i++;
      if (Mcodeout(d,CLEARCODE)) return -1;
      d->i--;
      Minidict(d);
    }
  d->im=c&255;
  return 0;
}

/* fin du message */
int Mlastchar(dict d)
{
  if (d->im!=-1)
    {
      if (Mcodeout(d,d->im)) return -1;
      d->i++;
    }
  if (Mcodeout(d,EOI)) return -1;
  return Mflushout(d);
}

int Mbigfather(dict d,int i)
{
	while(d->tape[i].pere!=-1)
		i=d->tape[i].pere;
	return i;
}

/* traite un nouveau code */
int Mxcode(dict d,int c)
{
  if (c==CLEARCODE)
    {
      Minidict(d);
      return 0;
    }
  if (d->im==-1)  /* debut segment */
    {
      if ((c<0)||(c>=d->i)) return -1;
      if (Mbufout(d,c)) return -1;
      d->im=c;
      return 0;
    }

  if ((c>=0)&&(c<d->i))
    {
      if (Mbufout(d,c)) return -1;
      Madddict(d,d->tape[Mbigfather(d,c)].val,d->im);
      d->im=c;
      return 0;
    }
  else if (c!=d->i)
  {
	  return -1;
  }
  if (Madddict(d,d->tape[Mbigfather(d,d->im)].val,d->im)>=0)
    {
      if (Mbufout(d,c)) return -1;
      d->im=c;
    }
  return 0;
}

int MZcompress2(mmachine m)
{
  int c,k,a,b;
  struct Dict d;

  if (MMget(m,0)==NIL) return 0;

  if (MMpush(m,NIL)) return MERRMEM;
  if (MMpush(m,NIL)) return MERRMEM;

  d.IObitsin=0;
  d.IObitsout=0;
  d.tapein=0;
  d.tapeout=0;
  d.m=m;
  if (k=Mopendict(&d,NBMOTIF)) return k;
  Minidict(&d);
  while((c=Mcharin(&d))!=EOF)
    {
      if (k=Maddchar(&d,c))
        {
          Mclosedict(&d);
          return k;
        }
    }
  if (k=Mlastchar(&d))
    {
      Mclosedict(&d);
      return k;
    }
  Mclosedict(&d);
  MMpull(m);
  if (k=MBstrcatn(m)) return k;
  a=MMsizestr(m,MMget(m,0)>>1);
  b=MMsizestr(m,MMget(m,1)>>1);
  MMechostr(MSKDEBUG,"mzip: crunching result :%d/%d\n",a,b);
  if (b>=a)
    {
	  if (k=Mpushstrbloc(m,"Mzip")) return k;
	  MMset(m,2,MMget(m,0));
	  MMpull(m);
	  return MBstrcat(m);
    }
  MMset(m,0,MMget(m,1));
  if (k=Mpushstrbloc(m,"mzip")) return k;
  MMset(m,2,MMget(m,0));
  MMpull(m);
  return MBstrcat(m);
}

int MZcompress(mmachine m)
{
  int pp;

  pp=MMgetPP(m);
  if (MZcompress2(m))
    {
	  MMechostr(MSKDEBUG,"mzip: error on crunching\n");
	  MMsetPP(m,pp);
	  MMset(m,0,NIL);
	  return 0;
    }
  return 0;
}

int MZuncompress2(mmachine m)
{
  int c,k;
  struct Dict d;

  if (MMget(m,0)==NIL) return 0;

  if (MMpush(m,NIL)) return MERRMEM;
  if (MMpush(m,NIL)) return MERRMEM;

  d.IObitsin=0;
  d.IObitsout=0;
  d.tapein=0;
  d.tapeout=0;
  d.m=m;
  c=Mcharin(&d);
  if ((c!='M')&&(c!='m')) return MERRTYP;
  if (Mcharin(&d)!='z') return MERRTYP;
  if (Mcharin(&d)!='i') return MERRTYP;
  if (Mcharin(&d)!='p') return MERRTYP;

  if (c=='M')
    {
	  if (k=Mopendict(&d,NBMOTIF)) return k;
      Minidict(&d);
      do
        {
          c=Mgetcode(&d);
          if ((c!=EOI)&&(k=Mxcode(&d,c)))
            {
              Mclosedict(&d);
              return k;
            }
        }
      while(c!=EOI);
      Mclosedict(&d);
    }
  else
	while((c=Mcharin(&d))!=EOF) Mcharout(&d,c);

  if (k=MZaddbloc(m)) return k;
  MMset(m,2,MMget(m,1));
  MMpull(m); MMpull(m);
  return MBstrcatn(m);
}

int MZuncompress(mmachine m)
{
  int pp;

  pp=MMgetPP(m);
  if (MZuncompress2(m))
    {
	  MMechostr(MSKDEBUG,"munzip: error on decrunching\n");
	  MMsetPP(m,pp);
	  MMset(m,0,NIL);
	  return 0;
    }
  return 0;
}

#include <zlib.h>

//$LB (27/05/2002) : compute the size in an unsigned long, instead of int
int MGzip(mmachine m)
{
	int p,s,err,k;
	unsigned long l, nl, effl;
	char *z;

	p=MMget(m,0)>>1;;
	if (p==NIL) return 0;
	l=MMsizestr(m,p);
	nl=20+(102*l)/100;
	s=MMmalloc(m,(nl>>2)+2,TYPEBUF); if (s==NIL) return MERRMEM;
	p=MMget(m,0)>>1;;
	z=MMstartstr(m,s);
	z[0]='_';z[1]='Z';z[2]='I';z[3]='P';
	effl=nl-8;
	err=compress(z+8,&effl,MMstartstr(m,p),l);
	if (err != Z_OK)
	{
		MMset(m,0,NIL);
		MMechostr(MSKDEBUG,"zip error %d\n",err);
		return 0;
	}
	k=l&255; z[4]=k;
	k=(l>>8)&255; z[5]=k;
	k=(l>>16)&255; z[6]=k;
	k=(l>>24)&255; z[7]=k;
	//$BLG
	z[effl+8] = '\0';
	MMsetsizestr(m,s,effl+8);
	MMechostr(MSKDEBUG,"zip %d/%d\n",effl+8,l);
	MMset(m,0,s+s+1);
	return MBstrdup(m);
}
 
int MGunzip(mmachine m)
{
	int p,s,err,k;
	unsigned long effl, l;
	char *z;

	p=MMget(m,0)>>1;;
	if (p==NIL) return 0;
	if (MMsizestr(m,p)<8)
	{
		MMset(m,0,NIL);
		return 0;
	}
	z=MMstartstr(m,p);
	if ((z[0]!='_')||(z[1]!='Z')||(z[2]!='I')||(z[3]!='P'))
	{
		MMset(m,0,NIL);
		MMechostr(MSKDEBUG,"unzip: not a zip file\n");
		return 0;
	}

	k=z[4]&255; l=k;
	k=z[5]&255; l+=(k<<8);
	k=z[6]&255; l+=(k<<16);
	k=z[7]&255; l+=(k<<24);

	s=MMmalloc(m,(l>>2)+2,TYPEBUF); if (s==NIL) return MERRMEM;

	p=MMget(m,0)>>1;;
	z=MMstartstr(m,p);
	effl=l;
	err=  uncompress(MMstartstr(m,s),&effl,z+8,MMsizestr(m,p)-8);
	//$BLG
	MMstartstr(m,s)[effl] = '\0';
	if (err != Z_OK)
	{
		MMset(m,0,NIL);
		MMechostr(MSKDEBUG,"unzip error %d\n",err);
		return 0;
	}
	MMsetsizestr(m,s,effl);
	MMechostr(MSKDEBUG,"unzip %d/%d\n",effl,MMsizestr(m,p));
	MMset(m,0,s+s+1);
	return 0;
}
