/*
 * program epmcompress - compress Extented Pixmap Format epm using
 *                       simple Runlength Encoding
 *
 * (C) by Oliver Vogel (e-mail: vogel@ikp.uni-koeln.de)
 * April 5th, 1997
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public Licence as by published
 * by the Free Software Foundation; either version 2; or (at your option)
 * any later version
 *
 * This program is distributed in the hope that it will entertaining,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>


#ifdef __STDC__
typedef unsigned char * (*store_func) (unsigned char *, unsigned, unsigned);
#else
typedef unsigned char * (*store_func) ();
#endif

static unsigned byteRaw[256];
static unsigned byteZip[256];
static store_func storeByte[256];

#ifdef __STDC__
static unsigned char *
StoreRaw (unsigned char *dst,
	  unsigned value, 
	  unsigned count)
#else
static unsigned char *
StoreRaw (dst, value, count)
     unsigned char *dst;
     unsigned value; 
     unsigned count;
#endif	  
{
  memset (dst, value, count);
  return dst + count;
}

#ifdef __STDC__
static unsigned char *
StoreZip (unsigned char *dst,
	  unsigned value, 
	  unsigned count)
#else
static unsigned char *
StoreZip (dst, value, count)
     unsigned char *dst;
     unsigned value; 
     unsigned count;
#endif	  
{
  *dst = value;
  dst ++;
  while (count >= 255) {
    *dst = 255;
    count -= 255;
  }
  *dst = count;
  dst ++;
  return dst;
}



#ifdef __STDC__
int 
main (int argc,
      char *argv[])
#else
int 
main (argv, argv)
  int argc;
  char *argv[];
#endif
{
  FILE *fp;
  int width, height, maxval, depth;
  int i, arg, n_pixel;
  unsigned char *inbuf, *outbuf;
  unsigned char *src, *dst;
  char magic[256];
  char file_name[1024];
  unsigned last, count;
  unsigned numZip;

  /* check args (part one) */
  if ( (argc < 2) || (0 == strcmp(argv[1],"-?") ) ) {
    fprintf(stderr, "usage: %s epmfile ...\n", argv[0]);
    return (argc != 2);
  }

  for (arg = 1; arg < argc; arg++) {

    /* open file */
    if (NULL == (fp = fopen(argv[arg],"r") ) ) {
      fprintf(stderr, "%s: failed to open file %s for reading\n", 
	      argv[0], argv[arg]);
      return 1;
    }

    /* read header */
    if (5 != fscanf(fp, "%s%d%d%d%d%*c", magic,&width,&height,&maxval,&depth) ) {
      fprintf(stderr, "%s: Failed to read epm header\n",argv[0]);
      return 1;
    }
    /* test magic */
    if (0 != strcmp(magic,"PX")) {
      if (0 == strncmp(magic, "PZ", 2) ) {
	fprintf(stderr, "File \"%s\" is already compressed.\n", argv[arg]);
	fclose(fp);
	continue;
      }
      fprintf(stderr, "%s: Wrong magic word \"%s\".\n",argv[0],magic);
      return 1;
    }

    /* calc number of pixel per layer */
    n_pixel = width*height;
    
    /* alloc input and color buffer */
    if ( (NULL == (inbuf  = malloc(depth*n_pixel*sizeof(char) ) ) ) ||
	 (NULL == (outbuf = calloc(2*depth*n_pixel,sizeof(char) ) ) ) ) {
      fprintf(stderr, "%s: Failed to alloc buffer\n",argv[0]);
      return 1;
    }
    
    /* read epm data */
    if (depth*n_pixel != fread(inbuf, sizeof(char), depth*n_pixel, fp) ) {
      perror(argv[0]);
      return 1;
    }

    fclose (fp);
    
    /* clear byte count buffers */
    memset (byteRaw, 0, sizeof (byteRaw) );
    for (i=0; i<256; i++) {
      byteZip[i] = 1;
    }
    /* create byte count table */
    last = *inbuf;
    count = 1;
    for (src = inbuf+1; src < (inbuf + depth*n_pixel); src++) {
      if (last == *src) {
	count ++;
      } else {
        byteRaw[last] += count;
	byteZip[last] += count / 255 + 2;
	count = 1;
	last = *src;
      }
    } 
    /* store last byte */
    byteRaw[last] += count;
    byteZip[last] += count / 255 + 2;

    /* check table */
    dst = outbuf + 1;
    numZip = 0;
    for (i=0; i<256; i++) {
      if (byteRaw[i] > byteZip[i]) {
	storeByte[i] =  StoreZip;
	numZip ++;
	*dst = i;
	dst ++;
      } else {
	storeByte[i] =  StoreRaw;
      }
    }
    *outbuf = numZip;

    /* convert data */
    count = 1;
    last = *inbuf;
    for (src = inbuf+1; src < (inbuf + depth*n_pixel); src++) {
      if (last == *src) {
	count ++;
      } else {
	dst = storeByte[last] (dst, last, count);
	last = *src;
	count = 1;
      }
    }
    dst = storeByte[last] (dst, last, count);
    
    fprintf (stderr, "File \"%s\": %6.1f%%.\n", argv[arg], 
	     100. * (1. - (double)(dst-outbuf)/(src-inbuf) ) );

    /* use compression only if better */
    if ( (dst-outbuf) < (src - inbuf) ) {
      /* rename old file */
      sprintf (file_name, "%s~", argv[arg]);
      if (rename (argv[arg], file_name)) {
	fprintf (stderr, "Failed to create backup file \"%s\".\n",
		 file_name);
	return 1;
      }
      /* write new file */
      if (NULL == (fp = fopen (argv[arg], "w") ) ) {
	fprintf(stderr, "%s: failed to open file %s for writing\n", 
		argv[0], argv[arg]);
	return 1;
      }
      /* write header */
      fprintf (fp, "PZ2\n");
      fprintf (fp, "%d %d %d %d\n", width, height, maxval, depth);
      fwrite (outbuf, sizeof(char), dst-outbuf, fp);
      fclose (fp);
    } else {
      fprintf (stderr, "Skipping file \"%s\".\n", argv[arg]);
    }
    /* free buffers */
    free (outbuf);
    free (inbuf);
  }
  return 0;
}




