/*
    File lz_nonslide.c, part of lzxcomp library
    Copyright (C) 2002 Matthew T. Russotto

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; version 2.1 only

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* 
 * Document here
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64)
    // a hack. You may need to add other OSes or fix the problem differently
#   include <string.h>
#   define __inline__
#else
#   include <strings.h>
#endif
#ifdef DEBUG_PERF
#include <sys/time.h>
#include <sys/resource.h>
#endif
#include "lz_nonslide.h"

#define MAX_MATCH 253
#define MIN_MATCH 2

void lz_init(lz_info *lzi, int wsize, int max_dist,
	     int max_match, int min_match,
	     int frame_size,
	     get_chars_t get_chars,
	     output_match_t output_match,
	     output_literal_t output_literal, void *user_data)
{
  /* the reason for the separate max_dist value is LZX can't reach the 
     first three characters in its nominal window.  But using a smaller
     window results in inefficiency when dealing with reset intervals
     which are the length of the nominal window */

  lzi->wsize = wsize;
  if (max_match > wsize)
    lzi->max_match = wsize;
  else
    lzi->max_match = max_match;

  lzi->min_match = min_match;
  if (lzi->min_match < 3) lzi->min_match = 3;

  lzi->max_dist = max_dist; 
  lzi->block_buf_size = wsize + lzi->max_dist; 
  lzi->block_buf = malloc(lzi->block_buf_size);
  lzi->block_bufe = lzi->block_buf + lzi->block_buf_size;
  assert(lzi->block_buf != NULL);
  
  lzi->cur_loc = 0;
  lzi->block_loc = 0;
  lzi->chars_in_buf = 0;
  lzi->eofcount = 0;
  lzi->get_chars = get_chars;
  lzi->output_match = output_match;
  lzi->output_literal = output_literal;
  lzi->user_data = user_data;
  lzi->frame_size = frame_size;
  lzi->lentab = calloc(sizeof(int), lzi->block_buf_size);
  lzi->prevtab = calloc(sizeof(u_char *), lzi->block_buf_size);
  lzi->analysis_valid = 0;
}

void lz_release(lz_info *lzi)
{
  free(lzi->block_buf);
  free(lzi->lentab);
  free(lzi->prevtab);
}

void lz_reset(lz_info *lzi)
{
  int residual = lzi->chars_in_buf - lzi->block_loc;
  memmove(lzi->block_buf, lzi->block_buf + lzi->block_loc, residual);
  lzi->chars_in_buf = residual;
  lzi->block_loc = 0;
  lzi->analysis_valid = 0;
}

#ifdef LZNONSLIDE_MAIN
typedef struct lz_user_data
{
  FILE *infile;
  FILE *outfile;
  int R0, R1, R2;
} lz_user_data;

int tmp_get_chars(lz_info *lzi, int n, u_char *buf)
{
  lz_user_data *lzud = (lz_user_data *)lzi->user_data;
  return fread(buf, 1, n, lzud->infile);
}

int tmp_output_match(lz_info *lzi, int match_pos, int match_len)
{
  lz_user_data *lzud = (lz_user_data *)lzi->user_data;
  int mod_match_loc;
  
  mod_match_loc = match_pos;

  fprintf(lzud->outfile, "(%d, %d)(%d)\n", match_pos, match_len, mod_match_loc);
  return 0;
}

void tmp_output_literal(lz_info *lzi, u_char ch)
{
  lz_user_data *lzud = (lz_user_data *)lzi->user_data;
  fprintf(lzud->outfile, "'%c'", ch);
}

int main(int argc, char *argv[])
{
  int wsize = atoi(argv[1]);
  lz_info lzi;
  lz_user_data lzu = {stdin, stdout, 1, 1, 1};

  lz_init(&lzi, wsize, wsize, MAX_MATCH, MIN_MATCH, 8192, tmp_get_chars, tmp_output_match, tmp_output_literal,&lzu);
  lz_compress(&lzi);
  return 0;
}
#endif

__inline__ int lz_left_to_process(lz_info *lzi)
{
  return lzi->chars_in_buf - lzi->block_loc;
}

static void
fill_blockbuf(lz_info *lzi, int maxchars)
{
  int toread;
  u_char *readhere;
  int nread;

  if (lzi->eofcount) return;
  maxchars -= lz_left_to_process(lzi);
  toread = lzi->block_buf_size - lzi->chars_in_buf;
  if (toread > maxchars) toread = maxchars;
  readhere = lzi->block_buf + lzi->chars_in_buf;
  nread = lzi->get_chars(lzi, toread, readhere);
  lzi->chars_in_buf += nread;
  if (nread != toread)
    lzi->eofcount++;
}

static void lz_analyze_block(lz_info *lzi)
{
  int *lentab, *lenp;
  u_char **prevtab, **prevp;
  u_char *bbp, *bbe;
  u_char *chartab[256];
  u_char *cursor;
  int prevlen;
  int ch;
  int maxlen;
  long wasinc;
  int max_dist = lzi->max_dist;
#ifdef DEBUG_ANALYZE_BLOCK
  static short n = 0;
#endif
#ifdef DEBUG_PERF
  struct rusage innerloop;
  struct timeval innertime, tmptime;
  struct rusage outerloop;
  struct timeval outertime;
  struct rusage initialloop;
  struct timeval initialtime;
  struct rusage totalloop;
  struct timeval totaltime;
#endif

#ifdef DEBUG_ANALYZE_BLOCK
  fprintf(stderr, "Analyzing block %d, cur_loc = %06x\n", n, lzi->cur_loc);
#endif
  memset(chartab, 0, sizeof(chartab));
  prevtab = prevp = lzi->prevtab;
  lentab = lenp = lzi->lentab;
  memset(prevtab, 0, sizeof(*prevtab) * lzi->chars_in_buf);
  memset(lentab, 0, sizeof(*prevtab) * lzi->chars_in_buf);
#ifdef DEBUG_PERF
  memset(&innertime, 0, sizeof(innertime));
  memset(&outertime, 0, sizeof(outertime));
  getrusage(RUSAGE_SELF, &initialloop);
  totalloop = initialloop;
#endif
  bbp = lzi->block_buf;
  bbe = bbp + lzi->chars_in_buf;
  while (bbp < bbe) {
    if (chartab[ch = *bbp]) {
      *prevp = chartab[ch];
      *lenp = 1;
    }
    chartab[ch] = bbp;
    bbp++;
    prevp++;
    lenp++;
  }
#ifdef DEBUG_PERF
  initialtime = initialloop.ru_utime;
  getrusage(RUSAGE_SELF, &initialloop);
  timersub(&initialloop.ru_utime, &initialtime, &initialtime);
#endif
  wasinc = 1;
  for (maxlen = 1; wasinc && (maxlen < lzi->max_match); maxlen++) {
#ifdef DEBUG_PERF
    getrusage(RUSAGE_SELF, &outerloop);
#endif
    bbp = bbe - maxlen - 1;
    lenp = lentab + lzi->chars_in_buf - maxlen - 1;
    prevp = prevtab + lzi->chars_in_buf - maxlen - 1;
    wasinc = 0;
    while (bbp > lzi->block_buf) {
      if (*lenp == maxlen) {
#ifdef DEBUG_PERF
	getrusage(RUSAGE_SELF, &innerloop);
#endif
	ch = bbp[maxlen];
	cursor = *prevp;
	while(cursor && ((bbp - cursor) <= max_dist)) {
	  prevlen = *(cursor - lzi->block_buf + lentab);
	  if (cursor[maxlen] == ch) {
	    *prevp = cursor;
	    (*lenp)++;
	    wasinc++;
	    break;
	  }
	  if (prevlen != maxlen) break;
	  cursor = *(cursor - lzi->block_buf + prevtab);
	}
#ifdef DEBUG_PERF
	tmptime = innerloop.ru_utime;
	getrusage(RUSAGE_SELF, &innerloop);
	timersub(&innerloop.ru_utime, &tmptime, &tmptime);
	timeradd(&tmptime, &innertime, &innertime);
#endif
      }
      bbp--;
      prevp--;
      lenp--;
    }
#ifdef DEBUG_PERF
    tmptime = outerloop.ru_utime;
    getrusage(RUSAGE_SELF, &outerloop);
    timersub(&outerloop.ru_utime, &tmptime, &tmptime);
    timeradd(&tmptime, &outertime, &outertime);
#endif
    //    fprintf(stderr, "maxlen = %d, wasinc = %ld\n", maxlen, wasinc);
  }
#ifdef DEBUG_PERF
  totaltime = totalloop.ru_utime;
  getrusage(RUSAGE_SELF, &totalloop);
  timersub(&totalloop.ru_utime, &totaltime, &totaltime);
  fprintf(stderr, "Time spend in initial loop = %f\n", initialtime.tv_sec + initialtime.tv_usec/(double)1E6);
  fprintf(stderr, "Time spend in outer loop = %f\n", outertime.tv_sec + outertime.tv_usec/(double)1E6);
  fprintf(stderr, "Time spend in inner loop = %f\n", innertime.tv_sec + innertime.tv_usec/(double)1E6);
  fprintf(stderr, "Time spend in all loops = %f\n", totaltime.tv_sec + totaltime.tv_usec/(double)1E6);
#endif
  lzi->analysis_valid = 1;
#ifdef DEBUG_ANALYZE_BLOCK
  fprintf(stderr, "Done analyzing block %d, cur_loc = %06x\n", n++, lzi->cur_loc);
#endif
}

void lz_stop_compressing(lz_info *lzi) 
{
  lzi->stop = 1;
  /*  fprintf(stderr, "Stopping...\n");*/
}

int lz_compress(lz_info *lzi, int nchars) 
{

  u_char *bbp, *bbe;
  int *lentab, *lenp;
  u_char **prevtab, **prevp;
  int len;
  int holdback;
  short trimmed;

  lzi->stop = 0;
  while ((lz_left_to_process(lzi) || !lzi->eofcount) && !lzi->stop && nchars > 0) {
#if 1
    if (!lzi->analysis_valid ||
	(!lzi->eofcount &&
	 ((lzi->chars_in_buf- lzi->block_loc) < nchars))) {
      int residual = lzi->chars_in_buf - lzi->block_loc;
      int bytes_to_move = lzi->max_dist + residual;
      if (bytes_to_move > lzi->chars_in_buf)
	bytes_to_move = lzi->chars_in_buf;
#ifdef DEBUG_ANALYZE_BLOCK
      fprintf(stderr, "Moving %06x, chars_in_buf %06x, residual = %06x, nchars= %06x block_loc = %06x\n", bytes_to_move, lzi->chars_in_buf, residual, nchars, lzi->block_loc);
#endif
      memmove(lzi->block_buf, lzi->block_buf + lzi->chars_in_buf - bytes_to_move,
	      bytes_to_move);
      
      lzi->block_loc = bytes_to_move - residual;
      lzi->chars_in_buf = bytes_to_move;
#ifdef DEBUG_ANALYZE_BLOCK
      fprintf(stderr, "New chars_in_buf %06x,  new block_loc = %06x, eof = %1d\n", lzi->chars_in_buf, lzi->block_loc, lzi->eofcount);
#endif
      fill_blockbuf(lzi, nchars);
#ifdef DEBUG_ANALYZE_BLOCK
      fprintf(stderr, "Really new chars_in_buf %06x,  new block_loc = %06x, eof = %1d\n", lzi->chars_in_buf, lzi->block_loc, lzi->eofcount);
#endif
      lz_analyze_block(lzi);
    }
#else
    if (!lzi->analysis_valid ||
	(lzi->block_loc - lzi->chars_in_buf) == 0) {
      lzi->block_loc = 0;
      lzi->chars_in_buf = 0;
      fill_blockbuf(lzi, nchars);
      lz_analyze_block(lzi);
    }
#endif
    prevtab = prevp = lzi->prevtab + lzi->block_loc;
    lentab = lenp = lzi->lentab + lzi->block_loc;
    bbp = lzi->block_buf + lzi->block_loc;
    holdback = lzi->max_match;
    if (lzi->eofcount) holdback = 0;
    if (lzi->chars_in_buf < (nchars + lzi->block_loc))
      bbe = lzi->block_buf + lzi->chars_in_buf - holdback;
    else
      bbe = bbp + nchars;
    while ((bbp < bbe) && (!lzi->stop)) {
      trimmed = 0;
      len = *lenp;
      if (lzi->frame_size && (len > (lzi->frame_size - lzi->cur_loc % lzi->frame_size))) {
#ifdef DEBUG_TRIMMING
	fprintf(stderr, "Trim for framing: %06x %d %d\n", lzi->cur_loc,len, (lzi->frame_size - lzi->cur_loc % lzi->frame_size));
#endif
	trimmed = 1;
	len = (lzi->frame_size - lzi->cur_loc % lzi->frame_size);
      }
      if (len > nchars) {
#ifdef DEBUG_TRIMMING
	fprintf(stderr, "Trim for blocking: %06x %d %d\n", lzi->cur_loc,len, nchars);
#endif
	trimmed = 1;
	len = nchars;
      }
      if (len >= lzi->min_match) {
#ifdef LAZY
	if ((bbp < bbe -1) && !trimmed &&
	    ((lenp[1] > (len + 1)) /* || ((lenp[1] == len) && (prevp[1] > prevp[0])) */)) {
	  len = 1;
	  /* this is the lazy eval case */
	}
	else 
#endif
	  if (lzi->output_match(lzi, (*prevp - lzi->block_buf) - lzi->block_loc,
				len) < 0) {
	    //	    fprintf(stderr, "Match rejected: %06x %d\n", lzi->cur_loc, len);
	    len = 1; /* match rejected */
	  }
      }
      else
	len = 1;
      
      if (len < lzi->min_match) {
	assert(len == 1);
	lzi->output_literal(lzi, *bbp);
      }
      //      fprintf(stderr, "len = %3d, *lenp = %3d, cur_loc = %06x, block_loc = %06x\n", len, *lenp, lzi->cur_loc, lzi->block_loc);
      bbp += len;
      prevp += len;
      lenp += len;
      lzi->cur_loc += len;
      lzi->block_loc += len;
      assert(nchars >= len);
      nchars -= len;

    }
  }
  return 0;
}
