/*
** lzcomp.c - Routines used in Lempel-Ziv compression (a la 1977 article).
**
** Author:  DavidDi
*/


// Headers
///////////

#include "lz_common.h"
#include "lz_buffers.h"
#include "lz_header.h"
#include "lzcommon.h"


/*
** int LZEncode(int doshSource, int doshDest);
**
** Compress input file into output file.
**
** Arguments:  doshSource    - open DOS file handle of input file
**             doshDest      - open DOS file handle of output file
**
** Returns:    int - TRUE if compression was successful.  One of the LZERROR_
**                   codes if the compression failed.
**
** Globals:
*/
INT LZEncode(INT doshSource, INT doshDest, PLZINFO pLZI)
{
   INT   i, len, f,
         iCurChar,      // current ring buffer position
         iCurString,    // start of current string in ring buffer
         iCodeBuf,      // index of next open buffer position
         cbLastMatch;   // length of last match
   BYTE byte,           // temporary storage for next byte to write
        byteMask,       // bit mask (and counter) for eight code units
        codeBuf[1 + 8 * MAX_LITERAL_LEN]; // temporary storage for encoded data

#if 0
   pLZI->cbMaxMatchLen = LZ_MAX_MATCH_LEN;
#else
   pLZI->cbMaxMatchLen = FIRST_MAX_MATCH_LEN;
#endif

   ResetBuffers();

   pLZI->cblOutSize += HEADER_LEN;

   // Initialize encoding trees.
   if (!LZInitTree(pLZI)) {
      return( LZERROR_GLOBALLOC );
   }

   // CodeBuf[1..16] saves eight units of code, and CodeBuf[0] works as eight
   // flags.  '1' representing that the unit is an unencoded letter (1 byte),
   // '0' a position-and-length pair (2 bytes).  Thus, eight units require at
   // most 16 bytes of code, plus the one byte of flags.
   codeBuf[0] = (BYTE)0;
   byteMask = (BYTE)1;
   iCodeBuf = 1;

   iCurString = 0;
   iCurChar = RING_BUF_LEN - pLZI->cbMaxMatchLen;

   for (i = 0; i < RING_BUF_LEN - pLZI->cbMaxMatchLen; i++)
      pLZI->rgbyteRingBuf[i] = BUF_CLEAR_BYTE;

   // Read bytes into the last cbMaxMatchLen bytes of the buffer.
   for (len = 0; len < pLZI->cbMaxMatchLen && ((f = ReadByte(byte)) != END_OF_INPUT);
        len++)
   {
      if (f != TRUE) {
         return( f );
      }

      pLZI->rgbyteRingBuf[iCurChar + len] = byte;
   }

   // Insert the cbMaxMatchLen strings, each of which begins with one or more
   // 'space' characters.  Note the order in which these strings are inserted.
   // This way, degenerate trees will be less likely to occur.
   for (i = 1; i <= pLZI->cbMaxMatchLen; i++)
      LZInsertNode(iCurChar - i, FALSE, pLZI);

   // Finally, insert the whole string just read.  The global variables
   // cbCurMatch and iCurMatch are set.
   LZInsertNode(iCurChar, FALSE, pLZI);

   do // while (len > 0)
   {
      // cbCurMatch may be spuriously long near the end of text.
      if (pLZI->cbCurMatch > len)
         pLZI->cbCurMatch = len;

      if (pLZI->cbCurMatch <= MAX_LITERAL_LEN)
      {
         // This match isn't long enough to encode, so copy it directly.
         pLZI->cbCurMatch = 1;
         // Set 'one uncoded byte' bit flag.
         codeBuf[0] |= byteMask;
         // Write literal byte.
         codeBuf[iCodeBuf++] = pLZI->rgbyteRingBuf[iCurChar];
      }
      else
      {
         // This match is long enough to encode.  Send its position and
         // length pair.  N.b., pLZI->cbCurMatch > MAX_LITERAL_LEN.
         codeBuf[iCodeBuf++] = (BYTE)pLZI->iCurMatch;
         codeBuf[iCodeBuf++] = (BYTE)((pLZI->iCurMatch >> 4 & 0xf0) |
                                      (pLZI->cbCurMatch - (MAX_LITERAL_LEN + 1)));
      }

      // Shift mask left one bit.
      if ((byteMask <<= 1) == (BYTE)0)
      {
         // Send at most 8 units of code together.
         for (i = 0; i < iCodeBuf; i++)
            if ((f = WriteByte(codeBuf[i])) != TRUE) {
               return( f );
            }

         // Reset flags and mask.
         codeBuf[0] = (BYTE)0;
         byteMask = (BYTE)1;
         iCodeBuf = 1;
      }

      cbLastMatch = pLZI->cbCurMatch;

      for (i = 0; i < cbLastMatch && ((f = ReadByte(byte)) != END_OF_INPUT);
           i++)
      {
         if (f != TRUE) {
            return( f );
         }

         // Delete old string.
         LZDeleteNode(iCurString, pLZI);
         pLZI->rgbyteRingBuf[iCurString] = byte;

         // If the start position is near the end of buffer, extend the
         // buffer to make string comparison easier.
         if (iCurString < pLZI->cbMaxMatchLen - 1)
            pLZI->rgbyteRingBuf[iCurString + RING_BUF_LEN] = byte;

         // Increment position in ring buffer modulo RING_BUF_LEN.
         iCurString = (iCurString + 1) & (RING_BUF_LEN - 1);
         iCurChar = (iCurChar + 1) & (RING_BUF_LEN - 1);

         // Register the string in rgbyteRingBuf[r..r + cbMaxMatchLen - 1].
         LZInsertNode(iCurChar, FALSE, pLZI);
      }

      while (i++ < cbLastMatch)
      {
         // No need to read after the end of the input, but the buffer may
         // not be empty.
         LZDeleteNode(iCurString, pLZI);
         iCurString = (iCurString + 1) & (RING_BUF_LEN - 1);
         iCurChar = (iCurChar + 1) & (RING_BUF_LEN - 1);
         if (--len)
            LZInsertNode(iCurChar, FALSE, pLZI);
      }
   } while (len > 0);   // until there is no input to process

   if (iCodeBuf > 1)
      // Send remaining code.
      for (i = 0; i < iCodeBuf; i++)
         if ((f = WriteByte(codeBuf[i])) != TRUE) {
            return( f );
         }

   // Flush output buffer to file.
   if ((f = FlushOutputBuffer(doshDest, pLZI)) != TRUE) {
      return( f );
   }

   LZFreeTree(pLZI);
   return(TRUE);
}
