/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
 */
/*
 * AtFS -- Attribute Filesystem
 *
 * afdeltaproc.c -- generate and merge deltas
 *
 * by Andreas Lampen, TU-Berlin (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: afdeltaproc.c[7.0] Fri Jun 25 14:31:51 1993 andy@cs.tu-berlin.de frozen $
 */

#include "atfs.h"

#define	MV	0
#define	AD	1

struct ed_cmd
{
  int    cmd;
  size_t length;
};

#define	TREESIZE	16129
#define SUFFSIZE        3
#define MINBLENG        18
#define MAXCMDLEN       32

struct indices
{
  size_t idx;
  struct indices *next;
};

struct suffixes
{
  struct indices *next;
  struct indices *last;
};

LOCAL size_t getCmd (strPtr, cmdPtr)
     char          *strPtr;
     struct ed_cmd *cmdPtr;
{
  size_t i=1;

  if (*strPtr == '\0')
    cmdPtr->cmd = MV;
  else
    cmdPtr->cmd = AD;

  cmdPtr->length = 0;
  while (strPtr[i]) {
    cmdPtr->length = cmdPtr->length << 3;
    cmdPtr->length |= ((strPtr[i++] - 48) & 07);
  }
  return (i+1);
}

LOCAL size_t getIndex (strPtr, indexPtr)
     char  *strPtr;
     size_t *indexPtr;
{
  size_t i=0;

  *indexPtr = 0;
  while (strPtr[i]) {
    *indexPtr = *indexPtr << 3;
    *indexPtr |= ((strPtr[i++] - 48) & 07);
  }
  
  if (i==0) i++; /* correction for zero value */
  
  return (i+1);
}

LOCAL size_t putCmd (command, length, strPtr)
     int   command;
     size_t length;
     char  *strPtr;
{
  size_t i=1, j=0;
  unsigned tmpLen;	/* This should be "unsigned size_t" (and if
		         * "size_t" were not a typedef, we could do just
			 * that) */
  char  tmpStr[16];

  switch (command) {
  case MV:
    strPtr[0] = '\0';
    break;
  case AD:
    strPtr[0] = '\001';
    break;
  default:
    strPtr[0] = '?';
  }
  
  tmpLen = length;	/* The compiler should warn about any narrowing */
  while(tmpLen) {
    tmpStr[j++] = (tmpLen & 07) + 48;
    tmpLen >>= 3;	/* The result would be implementation-defined
			 * if "tmpLen" were negative */
  }
  
  while (j)
    strPtr[i++] = tmpStr[--j];
  
  if (!length)
    strPtr[i++] = '\0';
  strPtr[i++] = '\0';

  return (i);
}

LOCAL size_t putIndex (idx, strPtr)
     size_t idx;
     char  *strPtr;
{
  size_t i=0, j=0;
  unsigned tmpIdx;	/* This should be "unsigned size_t" (and if
		         * "size_t" were not a typedef, we could do just
			 * that) */
  char  tmpStr[16];

  tmpIdx = idx;	/* The compiler should warn about any narrowing */
  while(tmpIdx) {
    tmpStr[j++] = (tmpIdx & 07) + 48;
    tmpIdx >>= 3;	/* The result would be implementation-defined
			 * if "tmpIdx" were negative */
  }
  
  while (j)
    strPtr[i++] = tmpStr[--j];
  
  if (!idx)
    strPtr[i++] = '\0';
  strPtr[i++] = '\0';

  return (i);
}

/*===================================================================
 * afConvertDelta -- convert Deltas
 *    For sake of portability of archive files, numbers occuring
 *    in deltas ("command", "length" and "idx") are stored in their
 *    ASCII representation rather than binary.
 *
 *===================================================================*/

EXPORT char *afConvertDelta (deltaStr, deltaSize, newDeltaSize)
     char  *deltaStr;
     size_t deltaSize, *newDeltaSize;
{
  struct ed_cmd cmd;
  size_t idx;
  char *deltaPtr = deltaStr, *deltaEnd = deltaStr + deltaSize;
  char *newDelta, *newDeltaPtr;

  if (deltaSize < 16)
    deltaSize = 16;

  if ((newDelta = malloc ((unsigned) deltaSize*2)) == NULL)
    FAIL ("ConvertDelta", "malloc", AF_ESYSERR, NULL);
  newDeltaPtr = newDelta;
  /* newDeltaEnd = newDelta + deltaSize*2; */

  while (deltaPtr < deltaEnd) {
    memcpy ((char *)&cmd, deltaPtr, sizeof (cmd));
    deltaPtr += sizeof (cmd);
    
    newDeltaPtr += putCmd (cmd.cmd, cmd.length, newDeltaPtr); 
    
    switch (cmd.cmd) {
    case MV:
      memcpy ((char*)&idx, deltaPtr, sizeof (idx));
      deltaPtr += sizeof (idx);
      newDeltaPtr += putIndex (idx, newDeltaPtr);
      break;
    case AD:
      memcpy (newDeltaPtr, deltaPtr, (size_t) cmd.length);
      deltaPtr += cmd.length;
      newDeltaPtr += cmd.length;
      break;
    }
  }
  *newDeltaSize = newDeltaPtr - newDelta;
  return (newDelta);
}

/*===================================================================
 *      afReconsData -- reconstruct data from delta
 *
 *===================================================================*/

EXPORT int afReconsData (srcStr, deltaStr, srcSize, deltaSize, targetfn)
     char  *srcStr, *deltaStr;
     size_t srcSize, deltaSize;
     char  *targetfn;
{
  FILE *ftarg;
  struct ed_cmd cmd;
  size_t idx;
  char *deltaPtr = deltaStr, *deltaEnd = deltaStr + deltaSize;

  if ((ftarg = fopen(targetfn, "w")) == NULL)
    return (ERROR);

  if (deltaSize == (size_t) 0) {
    if (srcSize > 0)
      fwrite (srcStr, sizeof(char), (size_t)srcSize, ftarg);
    fclose (ftarg);
    return (AF_OK);
  }
  
  while (deltaPtr < deltaEnd) {
    deltaPtr += getCmd (deltaPtr, &cmd);
    
    switch (cmd.cmd) {
    case MV:
      deltaPtr += getIndex (deltaPtr, &idx);
      fwrite (srcStr + idx, sizeof(char), (size_t)cmd.length, ftarg);
      break;
    case AD:
      fwrite (deltaPtr, sizeof(char), (size_t)cmd.length, ftarg);
      deltaPtr += cmd.length;
      break;
    default:
      fclose(ftarg);
      return (ERROR);
    }
  }
  fclose(ftarg);
  return(AF_OK);
}
      
/*===================================================================
 *      afMakeDelta  -- generate delta
 *
 *===================================================================*/

LOCAL FILE *fdelta = NULL;

/*============================
 * manage suffix tree
 *
 *============================*/

LOCAL struct suffixes *suff_array = NULL;
LOCAL struct indices *FreePtr;

#define HASH(STRING,OFFSET)\
        STRING[OFFSET] +\
        (STRING[OFFSET + 4] << 1) +\
        (STRING[OFFSET + 7] << 2) +\
        (STRING[OFFSET + 11] << 3) +\
        (STRING[OFFSET + 15] << 4) +\
        (STRING[OFFSET + 17] << 5)

LOCAL int build_suffix_tree (string, length)
     register char *string;
     size_t         length;
{
  register struct indices *current;
  register size_t i = 0;
  register int hash;
  struct indices *LargeMemPtr;

  suff_array = (struct suffixes *) malloc(TREESIZE * sizeof (struct suffixes));
  if (suff_array == NULL)
    return (ERROR);

  memset ((char *)suff_array, 0, TREESIZE * sizeof(struct suffixes));

  if ((LargeMemPtr = (struct indices *) calloc ((unsigned) length, sizeof (struct indices))) == 0)
    return (ERROR);

  FreePtr = LargeMemPtr;

  hash = abs (HASH (string, 0));

  suff_array[hash].next = LargeMemPtr;
  LargeMemPtr++;

  suff_array[hash].last = suff_array[hash].next;
  suff_array[hash].next->idx = 0;
  suff_array[hash].next->next = NULL;

  for (i = 1; i < (length - (MINBLENG - 1)); i++) {
    hash = abs (HASH (string,i));
    if (suff_array[hash].last)
      current = suff_array[hash].last->next = LargeMemPtr;
    else
      current = suff_array[hash].next = LargeMemPtr;
    
    LargeMemPtr++;
    current->idx = i;
    current->next = NULL;
    suff_array[hash].last = current;
  }
  return (AF_OK);
}

LOCAL void find_max_bm(t_str, s_str, targetSize, srcSize, t_offset, s_offset, leng)
     register char *t_str, *s_str;
     size_t targetSize, srcSize;
     size_t t_offset;
     size_t *s_offset, *leng;
{
  register struct indices *current;
  register int i,j;
  register int hash;
  int off;
  int max;
  
  hash = abs (HASH (t_str, t_offset));
  if (suff_array[hash].next == NULL)
    *leng = 0;
  else {
    max = 0;
    off = 0;
    current = suff_array[hash].next;
    while (current) {
      i = current->idx;
      j = t_offset;
      while ((i < srcSize) && (j < targetSize) && 
	     (t_str[j] == s_str[i]))  {
	j++;
	i++;
      }
      if ((i - current->idx > max) &&
	  (t_str[t_offset] == s_str[current->idx]) &&
	  (t_str[t_offset + 1] == s_str[current->idx + 1])) {
	max = i - current->idx;
	off = current->idx;
      }
      current = current->next;
      if (current) {
	while (((t_offset + max) < targetSize) &&
	       ((current->idx + max) < srcSize) &&
	       (t_str[t_offset + max] !=
		s_str[current->idx + max]) &&
	       (current->next))
	  current = current->next;
      }
    }
    *s_offset = off;
    if(max >= MINBLENG )
      *leng = max;
    else
      *leng = 0;
  }
  return;
}


LOCAL void write_cmd (command, length, idx, targetString)
     int   command;
     size_t length;
     size_t idx;
     char  *targetString;
{
  char cmdStr[MAXCMDLEN];
  int  cmdLength;

  cmdLength = putCmd (command, length, cmdStr);
  switch (command) {
  case MV:
    cmdLength += putIndex (idx, &cmdStr[cmdLength]);
    fwrite (cmdStr, sizeof(char), (size_t)cmdLength, fdelta);
    break;
  case AD:
    fwrite (cmdStr, sizeof(char), cmdLength, fdelta);
    fwrite (targetString+idx, sizeof(char), (size_t)length, fdelta);
    break;
  }
}


EXPORT int afMakeDelta (srcStr, targetStr, srcSize, targetSize, deltafn)
     char  *srcStr, *targetStr;
     size_t srcSize, targetSize;
     char  *deltafn;
{
  register size_t trg_index = 0;
  size_t          src_index, matchlength;
  register size_t nomtch_trg_index = 0, nomtchlength = 0;
  int nomatch = FALSE;

  fdelta  = fopen (deltafn, "w");

  /* if source and target are identical */
  if (srcSize == targetSize) {
    if ((memcmp (srcStr, targetStr, (int) srcSize)) == 0) {
      fclose (fdelta);
      return (AF_OK);
    }
  }
  
  if ((srcSize <= MINBLENG) || (targetSize <= MINBLENG)) {
    write_cmd (AD, targetSize, (size_t)0, targetStr);
    fclose (fdelta);
    return(AF_OK);
  }
  
  if (build_suffix_tree (srcStr, srcSize) == ERROR) {
    fclose (fdelta);
    return(ERROR);
  }
  
  while (trg_index < (targetSize - (MINBLENG - 1))) {
    find_max_bm (targetStr, srcStr, targetSize, srcSize, trg_index, &src_index, &matchlength);
    if (matchlength > 0) {
      if (nomatch) {
	write_cmd (AD, nomtchlength, nomtch_trg_index, targetStr);
	nomtch_trg_index = 0;
	nomtchlength = 0;
	nomatch = FALSE;
      }
      write_cmd (MV, matchlength, src_index, targetStr);
      trg_index = trg_index + matchlength;
    }
    else {
      if (nomatch)
	nomtchlength++;
      else {
	nomatch = TRUE;
	nomtch_trg_index = trg_index;
	nomtchlength = 1;
      }
      trg_index++;
      if (trg_index >= targetSize)
	write_cmd (AD, nomtchlength, nomtch_trg_index, targetStr);
    }
  }
  if (trg_index <= (targetSize - 1)) {
    if (nomatch)
      write_cmd (AD, (nomtchlength + (targetSize - trg_index)), nomtch_trg_index, targetStr);
    else
      write_cmd (AD, (targetSize - trg_index), trg_index, targetStr);
  }
  fclose (fdelta);
  
  return(AF_OK);
}
