#include "CMP_Pub.h"
 
#if defined(D_CMP_LZSS)
 
#include <stdio.h>
#include "CMP_CPA.h"
#include "ErmCmp.h"
#include "CMP_File.h"
 
/**************************************************************************/
#define C_CMP_LZSS_INDEX_BIT_COUNT      12
#define C_CMP_LZSS_LENGTH_BIT_COUNT     4
#define C_CMP_LZSS_WINDOW_SIZE          ( 1 << C_CMP_LZSS_INDEX_BIT_COUNT )
#define C_CMP_LZSS_RAW_LOOK_AHEAD_SIZE  ( 1 << C_CMP_LZSS_LENGTH_BIT_COUNT )
#define C_CMP_LZSS_BREAK_EVEN           ( ( 1 + C_CMP_LZSS_INDEX_BIT_COUNT + C_CMP_LZSS_LENGTH_BIT_COUNT ) / 9 )
#define C_CMP_LZSS_LOOK_AHEAD_SIZE      ( C_CMP_LZSS_RAW_LOOK_AHEAD_SIZE + C_CMP_LZSS_BREAK_EVEN )
#define C_CMP_LZSS_TREE_ROOT            C_CMP_LZSS_WINDOW_SIZE
#define C_CMP_LZSS_END_OF_STREAM        0
#define C_CMP_LZSS_UNUSED               0
 
/**************************************************************************/
 
#define M_CMP_LZSS_MOD_WINDOW( a )      ( ( a ) & ( C_CMP_LZSS_WINDOW_SIZE - 1 ) )
 
/**************************************************************************/
 
unsigned char CMP_LZSS_g_a_ucWindow[C_CMP_LZSS_WINDOW_SIZE];
 
struct 
{
 int parent;
 int smaller_child;
 int larger_child;
} CMP_LZSS_g_a_stTree[C_CMP_LZSS_WINDOW_SIZE + 1 ];
 
char *CMP_LZSS_CompressionName = "LZSS method";
 
/**************************************************************************/
 
void CMP_LZSS_InitTree( int r );
void CMP_LZSS_ContractNode( int old_node, int new_node );
void CMP_LZSS_ReplaceNode( int old_node, int new_node );
int CMP_LZSS_FindNextNode( int node );
void CMP_LZSS_DeleteString( int p );
int CMP_LZSS_AddString( int new_node, int *match_position );
 
/**************************************************************************/
 
void CMP_LZSS_fn_vInitTree( int r )
{
 CMP_LZSS_g_a_stTree[ C_CMP_LZSS_TREE_ROOT ].larger_child = r;
 CMP_LZSS_g_a_stTree[ r ].parent = C_CMP_LZSS_TREE_ROOT;
 CMP_LZSS_g_a_stTree[ r ].larger_child = C_CMP_LZSS_UNUSED;
 CMP_LZSS_g_a_stTree[ r ].smaller_child = C_CMP_LZSS_UNUSED;
}
/**************************************************************************/
void CMP_LZSS_fn_vContractNode( int old_node , int new_node )
{
 CMP_LZSS_g_a_stTree[ new_node ].parent = CMP_LZSS_g_a_stTree[ old_node ].parent;
 if ( CMP_LZSS_g_a_stTree[ CMP_LZSS_g_a_stTree[ old_node ].parent ].larger_child == old_node )
 	CMP_LZSS_g_a_stTree[ CMP_LZSS_g_a_stTree[ old_node ].parent ].larger_child = new_node;
 else
	 CMP_LZSS_g_a_stTree[ CMP_LZSS_g_a_stTree[ old_node ].parent ].smaller_child = new_node;
 CMP_LZSS_g_a_stTree[ old_node ].parent = C_CMP_LZSS_UNUSED;
}
/**************************************************************************/
void CMP_LZSS_fn_vReplaceNode( int old_node , int new_node )
{
 int parent;
 
 parent = CMP_LZSS_g_a_stTree[ old_node ].parent;
 if ( CMP_LZSS_g_a_stTree[ parent ].smaller_child == old_node )
 	CMP_LZSS_g_a_stTree[ parent ].smaller_child = new_node;
 else
	 CMP_LZSS_g_a_stTree[ parent ].larger_child = new_node;
 CMP_LZSS_g_a_stTree[ new_node ] = CMP_LZSS_g_a_stTree[ old_node ];
 CMP_LZSS_g_a_stTree[ CMP_LZSS_g_a_stTree[ new_node ].smaller_child ].parent = new_node;
 CMP_LZSS_g_a_stTree[ CMP_LZSS_g_a_stTree[ new_node ].larger_child ].parent = new_node;
 CMP_LZSS_g_a_stTree[ old_node ].parent = C_CMP_LZSS_UNUSED;
}
/**************************************************************************/
int CMP_LZSS_fn_vFindNextNode( int node )
{
 int next;
 
 next = CMP_LZSS_g_a_stTree[ node ].smaller_child;
 while ( CMP_LZSS_g_a_stTree[ next ].larger_child != C_CMP_LZSS_UNUSED )
	 next = CMP_LZSS_g_a_stTree[ next ].larger_child;
 return( next );
}
/**************************************************************************/
void CMP_LZSS_fn_vDeleteString( int p )
{
 int  replacement;
 
 if ( CMP_LZSS_g_a_stTree[ p ].parent == C_CMP_LZSS_UNUSED )
	 return;
 if ( CMP_LZSS_g_a_stTree[ p ].larger_child == C_CMP_LZSS_UNUSED )
	 CMP_LZSS_fn_vContractNode( p, CMP_LZSS_g_a_stTree[ p ].smaller_child );
 else if ( CMP_LZSS_g_a_stTree[ p ].smaller_child == C_CMP_LZSS_UNUSED )
	 CMP_LZSS_fn_vContractNode( p, CMP_LZSS_g_a_stTree[ p ].larger_child );
 else 
 {
	 replacement = CMP_LZSS_fn_vFindNextNode( p );
	 CMP_LZSS_fn_vDeleteString( replacement );
	 CMP_LZSS_fn_vReplaceNode( p, replacement );
 }
}
/**************************************************************************/
int CMP_LZSS_fn_vAddString( int new_node , int *match_position )
{
 int i;
 int test_node;
 int delta = 0;
 int match_length;
 int *child;
 
 if ( new_node == C_CMP_LZSS_END_OF_STREAM )
	 return( 0 );
 test_node = CMP_LZSS_g_a_stTree[ C_CMP_LZSS_TREE_ROOT ].larger_child;
 match_length = 0;
 for ( ; ; ) 
 {
	 for ( i = 0 ; i < C_CMP_LZSS_LOOK_AHEAD_SIZE ; i++ ) 
  {
	  delta = CMP_LZSS_g_a_ucWindow[ M_CMP_LZSS_MOD_WINDOW( new_node + i ) ] - CMP_LZSS_g_a_ucWindow[ M_CMP_LZSS_MOD_WINDOW( test_node + i ) ];
   if ( delta != 0 )
  		break;
  }
	 if ( i >= match_length ) 
  {
	  match_length = i;
	  *match_position = test_node;
	  if ( match_length >= C_CMP_LZSS_LOOK_AHEAD_SIZE ) 
   {
		  CMP_LZSS_fn_vReplaceNode( test_node, new_node );
		  return( match_length );
	  }
	 }
	 if ( delta >= 0 )
	  child = &CMP_LZSS_g_a_stTree[ test_node ].larger_child;
	 else
	  child = &CMP_LZSS_g_a_stTree[ test_node ].smaller_child;
	 if ( *child == C_CMP_LZSS_UNUSED ) 
  {
	  *child = new_node;
	  CMP_LZSS_g_a_stTree[ new_node ].parent = test_node;
	  CMP_LZSS_g_a_stTree[ new_node ].larger_child = C_CMP_LZSS_UNUSED;
	  CMP_LZSS_g_a_stTree[ new_node ].smaller_child = C_CMP_LZSS_UNUSED;
	  return( match_length );
	 }
	 test_node = *child;
 }
}
/**************************************************************************/
void CMP_LZSS_fn_vCompressFile( FILE *input , tdstBitFile *output )
{
 int i;
 int c;
 int look_ahead_bytes;
 int current_position;
 int replace_count;
 int match_length;
 int match_position;
 
 current_position = 1;
 for ( i = 0 ; i < C_CMP_LZSS_LOOK_AHEAD_SIZE ; i++ ) 
 {
	 if ( ( c = getc( input ) ) == EOF )
	  break;
	 CMP_LZSS_g_a_ucWindow[ current_position + i ] = (unsigned char) c;
 }
 look_ahead_bytes = i;
 CMP_LZSS_fn_vInitTree( current_position );
 match_length = 0;
 match_position = 0;
 while ( look_ahead_bytes > 0 ) 
 {
	 if ( match_length > look_ahead_bytes )
	  match_length = look_ahead_bytes;
	 if ( match_length <= C_CMP_LZSS_BREAK_EVEN ) 
  {
	  replace_count = 1;
	  CMP_fn_vOutputBit( output, 1 );
	  CMP_fn_vOutputBits( output,
			            (unsigned long) CMP_LZSS_g_a_ucWindow[ current_position ], 8 );
	 }
  else 
  {
	  CMP_fn_vOutputBit( output, 0 );
	  CMP_fn_vOutputBits( output,
			(unsigned long) match_position, C_CMP_LZSS_INDEX_BIT_COUNT );
	  CMP_fn_vOutputBits( output,
		             (unsigned long) ( match_length - ( C_CMP_LZSS_BREAK_EVEN + 1 ) ),
            			C_CMP_LZSS_LENGTH_BIT_COUNT );
	  replace_count = match_length;
	 }
	 for ( i = 0 ; i < replace_count ; i++ ) 
  {
	  CMP_LZSS_fn_vDeleteString( M_CMP_LZSS_MOD_WINDOW( current_position + C_CMP_LZSS_LOOK_AHEAD_SIZE ) );
	  if ( ( c = getc( input ) ) == EOF )
  		look_ahead_bytes--;
	  else
		  CMP_LZSS_g_a_ucWindow[ M_CMP_LZSS_MOD_WINDOW( current_position + C_CMP_LZSS_LOOK_AHEAD_SIZE ) ] = (unsigned char) c;
	  current_position = M_CMP_LZSS_MOD_WINDOW( current_position + 1 );
	  if ( look_ahead_bytes )
		  match_length = CMP_LZSS_fn_vAddString( current_position, &match_position );
	 }
 }
 CMP_fn_vOutputBit( output, 0 );
 CMP_fn_vOutputBits( output, (unsigned long) C_CMP_LZSS_END_OF_STREAM, C_CMP_LZSS_INDEX_BIT_COUNT );
}
/**************************************************************************/
void CMP_LZSS_fn_vExpandFile( tdstBitFile *input , FILE *output )
{
 int i;
 int current_position;
 int c;
 int match_length;
 int match_position;
 
 current_position = 1;
 for ( ; ; ) 
 {
 	if ( CMP_fn_lInputBit( input ) ) 
  {
	  c = (int) CMP_fn_ulInputBits( input, 8 );
	  putc( c, output );
	  CMP_LZSS_g_a_ucWindow[ current_position ] = (unsigned char) c;
	  current_position = M_CMP_LZSS_MOD_WINDOW( current_position + 1 );
	 }
  else 
  {
	  match_position = (int) CMP_fn_ulInputBits( input, C_CMP_LZSS_INDEX_BIT_COUNT );
	  if ( match_position == C_CMP_LZSS_END_OF_STREAM )
		  break;
	  match_length = (int) CMP_fn_ulInputBits( input, C_CMP_LZSS_LENGTH_BIT_COUNT );
	  match_length += C_CMP_LZSS_BREAK_EVEN;
	  for ( i = 0 ; i <= match_length ; i++ ) 
   {
		  c = CMP_LZSS_g_a_ucWindow[ M_CMP_LZSS_MOD_WINDOW( match_position + i ) ];
		  putc( c, output );
		  CMP_LZSS_g_a_ucWindow[ current_position ] = (unsigned char) c;
		  current_position = M_CMP_LZSS_MOD_WINDOW( current_position + 1 );
	  }
	 }
 }
}
/**************************************************************************/
#endif /* D_CMP_LZSS */
