#include "CMP_Pub.h"
 
#if defined(D_CMP_DIFF)
 
#include <stdio.h>
#include "CMP_CPA.h"
#include "ErmCmp.h"
#include "CMP_File.h"
 
/**************************************************************************/
#define D_CMP_DIFF_CODED
/**************************************************************************/
#define C_CMP_DIFF_BLOCK_FACTOR  1
#define C_CMP_DIFF_BLOCK_SIZE    (C_CMP_DIFF_BLOCK_FACTOR<<3)
#define C_CMP_DIFF_NB_BLOCKS     256
/**************************************************************************/
char *CMP_DIFF_g_szCompressionName = "Differential method";
char CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS+1][C_CMP_DIFF_BLOCK_SIZE];
unsigned char CMP_DIFF_g_ucfinish=0;
/**************************************************************************/
/*#define D_CMP_DIFF_CODE_DEBUG*/
#define C_CMP_DIFF_INIT_CODE 0x53
#define C_CMP_DIFF_INIT_CODE1 0xB9
#define C_CMP_DIFF_INIT_CODE2 ((unsigned long) C_CMP_DIFF_INIT_CODE * 0x01010101 + 0x01020304)
unsigned char CMP_DIFF_g_ucCode=C_CMP_DIFF_INIT_CODE;
unsigned char CMP_DIFF_g_ucCheckSum;
long CMP_DIFF_g_lNbCodedBytes=0;
long CMP_DIFF_g_lNbBytes=0;
 
#if defined(D_CMP_DIFF_CODED)
/**************************************************************************/
unsigned char CMP_DIFF_fn_ucSwapCode(unsigned char code)
{
  unsigned char i,newcode=0;
 
  for (i=0;i<8;i++)
  {
    newcode |= (((code >> i) & 0x1) <<(7-i));
  }
  return newcode;
}
/**************************************************************************/
unsigned char CMP_DIFF_fn_ucGetCheckSum(FILE *input)
{
  unsigned char chksum;
  fread(&chksum,sizeof(chksum),1,input);
  chksum ^= C_CMP_DIFF_INIT_CODE;
  #ifdef D_CMP_DIFF_CODE_DEBUG
    printf("get checksum = %d\n",(int) chksum);
  #endif
  return chksum;
}
/**************************************************************************/
void CMP_DIFF_fn_vSetCheckSum(unsigned char chksum,FILE *output)
{
  fseek(output,0L,SEEK_SET);
  #ifdef D_CMP_DIFF_CODE_DEBUG
    printf("set checksum = %d\n",(int) chksum);
  #endif
  chksum ^= C_CMP_DIFF_INIT_CODE;
  fwrite(&chksum,sizeof(chksum),1,output);
}
/**************************************************************************/
long CMP_DIFF_fn_lGetSize(FILE *input)
{
  long size;
  fread(&size,sizeof(size),1,input);
  size ^= C_CMP_DIFF_INIT_CODE2;
  #ifdef D_CMP_DIFF_CODE_DEBUG
    printf("get size = %ld\n",(long) size);
  #endif
  return size;
}
/**************************************************************************/
void CMP_DIFF_fn_vSetSize(long size,FILE *output)
{
  #ifdef D_CMP_DIFF_CODE_DEBUG
    printf("set size = %ld\n",(long) size);
  #endif
  size ^= C_CMP_DIFF_INIT_CODE2;
  fwrite(&size,sizeof(size),1,output);
}
/**************************************************************************/
long CMP_DIFF_fn_lUncodedGetc(FILE *input)
{
  long dummy;
 
  if ((dummy=getc(input))!=EOF) CMP_DIFF_g_lNbBytes++;
  return dummy;
}
/**************************************************************************/
long CMP_DIFF_fn_lCodedPutc(int c,FILE *output)
{
  long dummy;
 
  dummy = c ^ ((int) ( CMP_DIFF_fn_ucSwapCode(CMP_DIFF_g_ucCode++) ^ C_CMP_DIFF_INIT_CODE1 ));
  CMP_DIFF_g_ucCheckSum = (unsigned char)(CMP_DIFF_g_ucCheckSum + (unsigned char)c);
  CMP_DIFF_g_lNbCodedBytes++;
  dummy = putc(dummy,output);
 
  #ifdef D_CMP_DIFF_CODE_DEBUG
    printf("line %04d org %03d coded written %03d checksum %03d\n",(int) CMP_DIFF_g_lNbCodedBytes,(int) c,(int) dummy,(int) CMP_DIFF_g_ucCheckSum);
  #endif
 
  return dummy;
}
/**************************************************************************/
long CMP_DIFF_fn_lCodedGetc(FILE *input)
{
  int dummy;
  int c;
 
  if ((dummy=c=getc(input))!=EOF)
  {
    dummy = c ^ ((int) ( CMP_DIFF_fn_ucSwapCode(CMP_DIFF_g_ucCode++) ^ C_CMP_DIFF_INIT_CODE1 ));
    CMP_DIFF_g_ucCheckSum = (unsigned char)(CMP_DIFF_g_ucCheckSum - (unsigned char)dummy);
    CMP_DIFF_g_lNbCodedBytes++;
 
    #ifdef D_CMP_DIFF_CODE_DEBUG
      printf("line %04d read %03d decoded fin %03d checksum %03d\n",(int) CMP_DIFF_g_lNbCodedBytes,(int) c,(int) dummy,(int) CMP_DIFF_g_ucCheckSum);
    #endif
  }
 
  return dummy;
}
/**************************************************************************/
long CMP_DIFF_fn_lUncodedPutc(int c,FILE *output)
{
  CMP_DIFF_g_lNbBytes--;
  if (CMP_DIFF_g_lNbBytes<0) CMP_DIFF_g_ucfinish=1;
  return putc(c,output);
}
/**************************************************************************/
#else /* D_CMP_DIFF_CODED */
/**************************************************************************/
long CMP_DIFF_fn_lUncodedGetc(FILE *input)
{
  return getc(input);
}
/**************************************************************************/
long CMP_DIFF_fn_lUncodedPutc(int c,FILE *output)
{
  return putc(c,output);
}
/**************************************************************************/
long CMP_DIFF_fn_lCodedGetc(FILE *input)
{
  return getc(input);
}
/**************************************************************************/
long CMP_DIFF_fn_lCodedPutc(int c,FILE *output)
{
  return putc(c,output);
}
/**************************************************************************/
#endif /* D_CMP_DIFF_CODED */
/**************************************************************************/
void CMP_DIFF_fn_vInitBuffer()
{
  int i,j;
 
  for(i=0;i<C_CMP_DIFF_NB_BLOCKS;i++)
  {
    for (j=0;j<C_CMP_DIFF_BLOCK_SIZE;j++)
    {
      CMP_DIFF_g_aa_cBuffer[i][j]=(char)i;
    }
  }
 
  CMP_DIFF_g_lNbCodedBytes=CMP_DIFF_g_lNbBytes=0;
  CMP_DIFF_g_ucfinish=0;
  CMP_DIFF_g_ucCode=C_CMP_DIFF_INIT_CODE + 4;
}
/**************************************************************************/
void CMP_DIFF_fn_vCompressFile( FILE *input, FILE *output )
{
  int i,j,currentsize;
  int bestblock = 0;
  int bestcount,matchcount;
  int changeindex = 0,bitvalue;
 
  CMP_DIFF_fn_vInitBuffer();
 
  #if defined(D_CMP_DIFF_CODED)
  CMP_DIFF_fn_vSetCheckSum(CMP_DIFF_g_ucCheckSum=0,output);
  CMP_DIFF_fn_vSetSize(CMP_DIFF_g_lNbBytes=0,output);
  #endif
 
  while(!CMP_DIFF_g_ucfinish)
  {
    currentsize=C_CMP_DIFF_BLOCK_SIZE;
    for(j=0;j<C_CMP_DIFF_BLOCK_SIZE;j++)
    {
      i=CMP_DIFF_fn_lUncodedGetc(input);
      if (i==EOF)
      {
	      currentsize=j;
	      break;
      }
      CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j]=(char)i;
    }
 
    if (currentsize==0)
    {
      CMP_DIFF_g_ucfinish=1;
    }
    else
    {
      bestcount=-1;
      for(i=0;i<C_CMP_DIFF_NB_BLOCKS;i++)
      {
	      matchcount=0;
	      for(j=0;j<currentsize;j++)
       {
	       if (CMP_DIFF_g_aa_cBuffer[i][j]==CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j])
        {
	        matchcount++;
        }
       }
 
	      if (matchcount>bestcount)
       {
	       bestcount=matchcount;
	       bestblock=i;
	       if (bestcount==currentsize)
	        break;
       }
      }
 
      CMP_DIFF_fn_lCodedPutc(bestblock,output);
 
      for (i=0;i<C_CMP_DIFF_BLOCK_FACTOR;i++)
      {
	      changeindex=0;
	      bitvalue=1;
	      for (j=i*8;j<i*8+8;j++)
       {
	       if (j>=currentsize)
	        break;
 
	       if (CMP_DIFF_g_aa_cBuffer[bestblock][j] != CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j])
        {
	        changeindex += bitvalue;
        }
	       bitvalue *= 2;
       }
 
	      CMP_DIFF_fn_lCodedPutc(changeindex,output);
 
	      for (j=i*8;j<i*8+8;j++)
       {
	       if (changeindex % 2 == 1)
        {
	        CMP_DIFF_fn_lCodedPutc(CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j],output);
        }
	       changeindex /= 2;
       }
 
	      if (currentsize<C_CMP_DIFF_BLOCK_SIZE)
       {
	       CMP_DIFF_fn_lCodedPutc(currentsize,output);
	       CMP_DIFF_g_ucfinish=1;
       }
	      else
       {
	       for(j=0;j<C_CMP_DIFF_BLOCK_SIZE;j++)
        {
	        CMP_DIFF_g_aa_cBuffer[bestblock][j]=CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j];
        }
       }
      }
    }
  }
 
  #ifdef D_CMP_DIFF_CODED
  CMP_DIFF_fn_vSetCheckSum(CMP_DIFF_g_ucCheckSum,output);
  CMP_DIFF_fn_vSetSize(CMP_DIFF_g_lNbBytes,output);
  #endif
}
/**************************************************************************/
void CMP_DIFF_fn_vExpandFile( FILE *input, FILE *output )
{
  int i,j;
  int bestblock=-1;
  int changeindex = 0;
 
  CMP_DIFF_fn_vInitBuffer();
 
  #ifdef D_CMP_DIFF_CODED
  CMP_DIFF_g_ucCheckSum=CMP_DIFF_fn_ucGetCheckSum(input);
  CMP_DIFF_g_lNbBytes=CMP_DIFF_fn_lGetSize(input);
  #endif
 
  while (!CMP_DIFF_g_ucfinish)
  {
    if (bestblock==-1)
    {
      bestblock=CMP_DIFF_fn_lCodedGetc(input);
      if (bestblock==EOF)
      {
	      CMP_DIFF_g_ucfinish=1;
      }
      else
      {
	      changeindex=CMP_DIFF_fn_lCodedGetc(input);
      }
    }
    else
    {
      bestblock=CMP_DIFF_fn_lCodedGetc(input);
      if (bestblock==EOF)
      {
	      for(j=0;j<C_CMP_DIFF_BLOCK_SIZE;j++)
       {
	       CMP_DIFF_fn_lUncodedPutc(CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j],output);
       }
	      CMP_DIFF_g_ucfinish=1;
      }
      else
      {
	      changeindex=CMP_DIFF_fn_lCodedGetc(input);
	      if (changeindex==EOF)
       {
	       for(j=0;j<bestblock;j++)
        {
	        CMP_DIFF_fn_lUncodedPutc(CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j],output);
        }
	       CMP_DIFF_g_ucfinish=1;
       }
	      else
       {
	       for(j=0;j<C_CMP_DIFF_BLOCK_SIZE;j++)
        {
	        CMP_DIFF_fn_lUncodedPutc(CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j],output);
        }
       }
      }
    }
 
    if (!CMP_DIFF_g_ucfinish)
    {
      for (i=0;i<C_CMP_DIFF_BLOCK_FACTOR;i++)
      {
      	if (i>0) 
        changeindex=CMP_DIFF_fn_lCodedGetc(input);
	      for(j=i*8;j<i*8+8;j++)
       {
	       if (changeindex % 2 == 1)
        {
	        CMP_DIFF_g_aa_cBuffer[bestblock][j]=(char)CMP_DIFF_fn_lCodedGetc(input);
        }
 
	       CMP_DIFF_g_aa_cBuffer[C_CMP_DIFF_NB_BLOCKS][j]=CMP_DIFF_g_aa_cBuffer[bestblock][j];
	       changeindex /= 2;
       }
      }
    }
  }
 
 
  #ifdef D_CMP_DIFF_CODED
    if (CMP_DIFF_g_lNbBytes==0)
    {
     if (CMP_DIFF_g_ucCheckSum!=0)
      M_CMPFatalError(E_uwCMPCheckSum);
    }
    else
    {
     M_CMPFatalError(E_uwCMPFileCorrupted);
    }
  #endif
}
/**************************************************************************/
 
#endif /* D_CMP_DIFF */
