/*
** lzcommon.c - Routines common to LZ compression / expansion.
**
** Author:  DavidDi
*/


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

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

/*
** bool LZInitTree(void);
**
** Initializes trees used in LZ compression.
**
** Arguments:  none
**
** Returns:    true/false
**
** Globals:    RightChild[] and Parent[] arrays reset to NIL to begin
**             encoding.
*/
BOOL LZInitTree(PLZINFO pLZI)
{
   INT i;

   /*
   ** For i = 0 to RING_BUF_LEN - 1, rightChild[i] and leftChild[i] will be the
   ** right and left children of node i.  These nodes need not be initialized.
   ** Also, parent[i] is the parent of node i.  These are initialized to
   ** NIL (= N), which stands for 'not used.'
   ** For i = 0 to 255, rightChild[RING_BUF_LEN + i + 1] is the root of the tree
   ** for strings that begin with character i.  These are initialized to NIL.
   ** n.b., there are 256 trees.
   */

   if (!pLZI->rightChild) {
      if (!(pLZI->rightChild = (INT*)LocalAlloc(LPTR, (RING_BUF_LEN + 257) * sizeof(INT)))) {
         return(FALSE);
      }
   }

   if (!pLZI->leftChild) {
      if (!(pLZI->leftChild = (INT*)LocalAlloc(LPTR, (RING_BUF_LEN + 1) * sizeof(INT)))) {
         return(FALSE);
      }
   }

   if (!pLZI->parent) {
      if (!(pLZI->parent = (INT*)LocalAlloc(LPTR, (RING_BUF_LEN + 1) * sizeof(INT)))) {
         return(FALSE);
      }
   }

   for (i = RING_BUF_LEN + 1; i <= RING_BUF_LEN + 256; i++)
      pLZI->rightChild[i] = NIL;

   for (i = 0; i < RING_BUF_LEN; i++)
      pLZI->parent[i] = NIL;

   return(TRUE);
}

VOID
LZFreeTree(PLZINFO pLZI)
{
   // Sanity check
   if (!pLZI) {
      return;
   }

   if (pLZI->rightChild) {
      LocalFree((HLOCAL)pLZI->rightChild);
      pLZI->rightChild = NULL;
   }

   if (pLZI->leftChild) {
      LocalFree((HLOCAL)pLZI->leftChild);
      pLZI->leftChild = NULL;
   }

   if (pLZI->parent) {
      LocalFree((HLOCAL)pLZI->parent);
      pLZI->parent = NULL;
   }
}

/*
** void LZInsertNode(int nodeToInsert, BOOL bDoArithmeticInsert);
**
** Inserts a new tree into the forest.  Inserts string of length
** cbMaxMatchLen, rgbyteRingBuf[r..r + cbMaxMatchLen - 1], into one of the trees
** (rgbyteRingBuf[r]'th tree).
**
** Arguments:  nodeToInsert        - start of string in ring buffer to insert
**                                   (also, associated tree root)
**             bDoArithmeticInsert - flag for performing regular LZ node
**                                   insertion or arithmetic encoding node
**                                   insertion
**
** Returns:    void
**
** Globals:    cbCurMatch - set to length of longest match
**             iCurMatch  - set to start index of longest matching string in
**                          ring buffer
**
** N.b., if cbCurMatch == cbMaxMatchLen, we remove the old node in favor of
** the new one, since the old node will be deleted sooner.
*/
VOID LZInsertNode(INT nodeToInsert, BOOL bDoArithmeticInsert, PLZINFO pLZI)
{
   INT  i, p, cmp, temp;
   BYTE FAR *key;

   // Sanity check
   if (!pLZI) {
      return;
   }

   cmp = 1;

   key = pLZI->rgbyteRingBuf + nodeToInsert;
   p = RING_BUF_LEN + 1 + key[0];

   pLZI->rightChild[nodeToInsert] = pLZI->leftChild[nodeToInsert] = NIL;
   pLZI->cbCurMatch = 0;

   FOREVER
   {
      if (cmp >= 0)
      {
         if (pLZI->rightChild[p] != NIL)
            p = pLZI->rightChild[p];
         else
         {
            pLZI->rightChild[p] = nodeToInsert;
            pLZI->parent[nodeToInsert] = p;
            return;
         }
      }
      else
      {
         if (pLZI->leftChild[p] != NIL)
            p = pLZI->leftChild[p];
         else
         {
            pLZI->leftChild[p] = nodeToInsert;
            pLZI->parent[nodeToInsert] = p;
            return;
         }
      }

      for (i = 1; i < pLZI->cbMaxMatchLen; i++)
         if ((cmp = key[i] - pLZI->rgbyteRingBuf[p + i]) != 0)
            break;

      if (bDoArithmeticInsert == TRUE)
      {
         // Do node insertion for arithmetic encoding.
         if (i > MAX_LITERAL_LEN)
         {
            if (i > pLZI->cbCurMatch)
            {
               pLZI->iCurMatch = (nodeToInsert - p) & (RING_BUF_LEN - 1);
               if ((pLZI->cbCurMatch = i) >= pLZI->cbMaxMatchLen)
                  break;
            }
            else if (i == pLZI->cbCurMatch)
            {
               if ((temp = (nodeToInsert - p) & (RING_BUF_LEN - 1)) < pLZI->iCurMatch)
                  pLZI->iCurMatch = temp;
            }
         }
      }
      else
      {
         // Do node insertion for LZ.
         if (i > pLZI->cbCurMatch)
         {
            pLZI->iCurMatch = p;
            if ((pLZI->cbCurMatch = i) >= pLZI->cbMaxMatchLen)
               break;
         }
      }
   }

   pLZI->parent[nodeToInsert] = pLZI->parent[p];
   pLZI->leftChild[nodeToInsert] = pLZI->leftChild[p];
   pLZI->rightChild[nodeToInsert] = pLZI->rightChild[p];

   pLZI->parent[pLZI->leftChild[p]] = nodeToInsert;
   pLZI->parent[pLZI->rightChild[p]] = nodeToInsert;

   if (pLZI->rightChild[pLZI->parent[p]] == p)
      pLZI->rightChild[pLZI->parent[p]] = nodeToInsert;
   else
      pLZI->leftChild[pLZI->parent[p]] = nodeToInsert;

   // Remove p.
   pLZI->parent[p] = NIL;

   return;
}


/*
** void LZDeleteNode(int nodeToDelete);
**
** Delete a tree from the forest.
**
** Arguments:  nodeToDelete - tree to delete from forest
**
** Returns:    void
**
** Globals:    Parent[], RightChild[], and LeftChild[] updated to reflect the
**             deletion of nodeToDelete.
*/
VOID LZDeleteNode(INT nodeToDelete, PLZINFO pLZI)
{
   INT  q;

   // Sanity check
   if (!pLZI) {
      return;
   }

   if (pLZI->parent[nodeToDelete] == NIL)
      // Tree nodeToDelete is not in the forest.
      return;

   if (pLZI->rightChild[nodeToDelete] == NIL)
      q = pLZI->leftChild[nodeToDelete];
   else if (pLZI->leftChild[nodeToDelete] == NIL)
      q = pLZI->rightChild[nodeToDelete];
   else
   {
      q = pLZI->leftChild[nodeToDelete];
      if (pLZI->rightChild[q] != NIL)
      {
         do
         {
            q = pLZI->rightChild[q];
         } while (pLZI->rightChild[q] != NIL);

         pLZI->rightChild[pLZI->parent[q]] = pLZI->leftChild[q];
         pLZI->parent[pLZI->leftChild[q]] = pLZI->parent[q];
         pLZI->leftChild[q] = pLZI->leftChild[nodeToDelete];
         pLZI->parent[pLZI->leftChild[nodeToDelete]] = q;
      }
      pLZI->rightChild[q] = pLZI->rightChild[nodeToDelete];
      pLZI->parent[pLZI->rightChild[nodeToDelete]] = q;
   }
   pLZI->parent[q] = pLZI->parent[nodeToDelete];

   if (pLZI->rightChild[pLZI->parent[nodeToDelete]] == nodeToDelete)
      pLZI->rightChild[pLZI->parent[nodeToDelete]] = q;
   else
      pLZI->leftChild[pLZI->parent[nodeToDelete]] = q;

   // Remove nodeToDelete.
   pLZI->parent[nodeToDelete] = NIL;

   return;
}

