/* 
 * This was adapted for Netpbm from bmpesoe.c in Dirk Krause's Bmeps package
 * by Bryan Henderson on 2005.01.05.
 *
 * Differences:
 *   - doesn't require Bmeps configuration stuff (bmepsco.h)
 *   - doesn't include pngeps.h
 *   - doesn't have test scaffold code
 *   - a few compiler warnings fixed
 *
 * Copyright (C) 2000 - Dirk Krause
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 * In this package the copy of the GNU Library General Public License
 * is placed in file COPYING.
 */

#include "bmepsoe.h"

#define RL_RUNLENGTH(i) (257 - (i))
#define RL_STRINGLENGTH(i) ((i) - 1)
#define RL_MAXLENGTH (127)

#define FINALOUTPUT(c) fputc((c),o->out)

void oe_init(Output_Encoder *o, FILE *out, int mode, int rate, int *buf,
  Bytef *flib, size_t flis, Bytef *flob, size_t flos
)
{
  
  o->out = out;
  o->mode = mode;
  o->textpos = 0;
  o->a85_value = 0UL; o->a85_consumed = 0;
  o->a85_0 = 1UL;
  o->a85_1 = 85UL;
  o->a85_2 = 85UL * 85UL;
  o->a85_3 = 85UL * o->a85_2;
  o->a85_4 = 85UL * o->a85_3;
  o->rl_lastbyte = 0;
  o->rl_buffer = buf;
  o->rl_bufused = 0;
  o->rl_state = 0;
  o->bit_value = 0;
  o->bit_consumed = 0;
  o->flate_rate = rate;
  if(o->flate_rate < 0) {
    o->mode &= (~OE_FLATE);
  }
  if(o->flate_rate > 9) {
    o->flate_rate = 9;
  }
  if((o->mode & OE_FLATE) && flib && flis && flob && flos) {
    (o->flate_stream).zfree = (free_func)0;
    (o->flate_stream).zalloc = (alloc_func)0;
    (o->flate_stream).opaque = (voidpf)0;
    if(deflateInit((&(o->flate_stream)),o->flate_rate) != Z_OK) {
      o->mode &= (~OE_FLATE);
    }
  }
  o->fl_i_buffer = flib;
  o->fl_o_buffer = flob;
  o->fl_i_size   = flis;
  o->fl_o_size   = flos;
  o->fl_i_used   = 0;
  
}

static char hexdigits[] = {
  "0123456789ABCDEF"
};

static void asciihex_add(Output_Encoder *o, int b)
{
  
  FINALOUTPUT(hexdigits[(b/16)]) ;
  FINALOUTPUT(hexdigits[(b%16)]) ;
  o->textpos = o->textpos + 2;
  if(o->textpos >= 64) {
    FINALOUTPUT('\n') ;
    o->textpos = 0;
  }
  
}

static void asciihex_flush(Output_Encoder *o)
{
  
  if(o->textpos > 0) {
    FINALOUTPUT('\n') ;
    o->textpos = 0;
  }
  
}

static char   ascii85_char(unsigned long x)
{
  unsigned u;
  int      i;
  char back;
  back = (char)0;
  u = (unsigned)x;
  i = (int)u;
  i += 33;
  back = (char)i;
  return back;
}

static void ascii85_output(Output_Encoder *o)
{
  int i;
  char buffer[6], c, *ptr;
  unsigned long value;
  value = o->a85_value;
  buffer[0] = ascii85_char(value / (o->a85_4));
  value = value % (o->a85_4);
  buffer[1] = ascii85_char(value / (o->a85_3));
  value = value % (o->a85_3);
  buffer[2] = ascii85_char(value / (o->a85_2));
  value = value % (o->a85_2);
  buffer[3] = ascii85_char(value / (o->a85_1));
  value = value % (o->a85_1);
  buffer[4] = ascii85_char(value);
  buffer[5] = '\0';
  i = o->a85_consumed + 1;
  ptr = buffer;
  o->textpos = o->textpos + i;
  while(i--) {
    c = *(ptr++);
    FINALOUTPUT(c) ;
  }
  if(o->textpos >= 64) {
    FINALOUTPUT('\n') ;
    o->textpos = 0;
  }
}

static void ascii85_add(Output_Encoder *o, int b)
{
  unsigned u;
  unsigned long ul;
  
  u = (unsigned)b;
  ul = (unsigned long)u;
  o->a85_value = 256UL * (o->a85_value) + ul;
  o->a85_consumed = o->a85_consumed + 1;
  if(o->a85_consumed >= 4) {
    ascii85_output(o);
    o->a85_value = 0UL;
    o->a85_consumed = 0;
  }
  
}

static void ascii85_flush(Output_Encoder *o)
{
  int i;
  
  if(o->a85_consumed > 0) {
    i = o->a85_consumed;
    while(i < 4) {
      o->a85_value = 256UL * o->a85_value;
      i++;
    }
    ascii85_output(o);
    o->a85_value = 0UL;
    o->a85_consumed = 0;
  }
  if(o->textpos > 0) {
    FINALOUTPUT('\n') ;
    o->textpos = 0;
  }
  
}

static void after_flate_add(Output_Encoder *o, int b)
{
  
  if(o->mode & OE_ASC85) {
    ascii85_add(o,b);
  } else {
    asciihex_add(o,b);
  }
  
}

static void do_flate_flush(Output_Encoder *o, int final)
{
  Bytef *iptr, *optr, *xptr;
  uLong  is, os, xs;
  int err, must_continue;
  
  iptr = o->fl_i_buffer; optr = o->fl_o_buffer;
  is = o->fl_i_size; os = o->fl_o_size;
  
  if(iptr && optr && is && os) {
    is = o->fl_i_used;
    if(is) {
      (o->flate_stream).next_in = iptr;
      (o->flate_stream).avail_in = is;
      if(final) { 
	must_continue = 1;
	while(must_continue) {
	  (o->flate_stream).next_out = optr;
	  (o->flate_stream).avail_out = os;
	  must_continue = 0;
	  err = deflate(&(o->flate_stream), Z_FINISH);
	  switch(err) {
	    case Z_STREAM_END: { 
              xptr = optr;
	      
	      xs = os - ((o->flate_stream).avail_out);
	      while(xs--) {
		after_flate_add(o, (*(xptr++)));
	      }
	    } break;
	    case Z_OK : {
	      must_continue = 1;
              xptr = optr;
	      
	      xs = os - ((o->flate_stream).avail_out);
	      while(xs--) {
		after_flate_add(o, (*(xptr++)));
	      }
	    } break;
	    default : { 
	    } break;
	  }
	}
      } else { 
	must_continue = 1;
	while(must_continue) {
	  must_continue = 0;
	  (o->flate_stream).avail_out = os; (o->flate_stream).next_out = optr;
	  err = deflate(&(o->flate_stream), 0);
	  switch(err) {
	    case Z_OK: {
	      if((o->flate_stream).avail_in) {
		must_continue = 1;
	      }
	      
	      xptr = optr; xs = os - ((o->flate_stream).avail_out);
	      while(xs--) {
		after_flate_add(o, (*(xptr++)));
	      }
	    } break;
	    default : { 
	    } break;
	  }
	}
      }
    }
  }
  
}

static void flate_add(Output_Encoder *o, int b)
{
  Byte bt;
  Bytef *btptr;
  
  btptr = o->fl_i_buffer;
  if(btptr) { 
    bt = (Byte)b; 
    btptr[(o->fl_i_used)] = bt;
    o->fl_i_used += 1UL;
    if(o->fl_i_used >= o->fl_i_size) {
      do_flate_flush(o, 0);
      o->fl_i_used = 0UL;
    }
  }
  
}

static void after_flate_flush(Output_Encoder *o)
{
  
  if(o->mode & OE_ASC85) {
    ascii85_flush(o);
  } else {
    asciihex_flush(o);
  }
  
}

static void flate_flush(Output_Encoder *o)
{
  do_flate_flush(o,1);
  deflateEnd(&(o->flate_stream));
  after_flate_flush(o);
}


static void after_rl_add(Output_Encoder *o, int b)
{
  
  if(o->mode & OE_FLATE) {
    flate_add(o,b);
  } else {
    after_flate_add(o,b);
  }
  
}

static void rl_add(Output_Encoder *o, int b)
{
  int lgt, i;
  int *buffer;
  /* ##### */
  
  buffer = o->rl_buffer;
  lgt = o->rl_bufused;
  if(buffer) {
    
    if(lgt > 0) {
      if(o->rl_lastbyte == b) {
	switch(o->rl_state) {
	  case 2: {
	    buffer[lgt++] = b;
	    o->rl_bufused = lgt;
	    o->rl_state = 2;
	    o->rl_lastbyte = b;
	    if(lgt >= RL_MAXLENGTH) {
	      after_rl_add(o, RL_RUNLENGTH(lgt));
	      after_rl_add(o, b);
	      o->rl_bufused = 0;
	      o->rl_state = 0;
	      o->rl_lastbyte = b;
	    }
	  } break;
	  case 1: {
	    buffer[lgt++] = b;
	    o->rl_bufused = lgt;
	    o->rl_state = 2;
	    o->rl_lastbyte = b;
	    lgt = lgt - 3;
	    if(lgt > 0) {
	      after_rl_add(o, RL_STRINGLENGTH(lgt));
	      for(i = 0; i < lgt; i++) {
		after_rl_add(o, buffer[i]);
	      }
	      buffer[0] = buffer[1] = buffer[2] = b;
	      o->rl_bufused = 3;
	      o->rl_state = 2;
	      o->rl_lastbyte = b;
	    }
	  } break;
	  default: {
	    buffer[lgt++] = b;
	    o->rl_bufused = lgt;
	    o->rl_state = 1;
	    o->rl_lastbyte = b;
	    if(lgt >= RL_MAXLENGTH) {
	      lgt = lgt - 2;
	      after_rl_add(o, RL_STRINGLENGTH(lgt));
	      for(i = 0; i < lgt; i++) {
		after_rl_add(o, buffer[i]);
	      }
	      buffer[0] = buffer[1] = b;
	      o->rl_bufused = 2;
	      o->rl_state = 1;
	      o->rl_lastbyte = b;
	    }
	  } break;
	}
      } else {
	if(o->rl_state == 2) {
	  after_rl_add(o, RL_RUNLENGTH(lgt));
	  after_rl_add(o, (o->rl_lastbyte));
	  buffer[0] = b; o->rl_bufused = 1; o->rl_lastbyte = b;
	  o->rl_state = 0;
	} else {
	  buffer[lgt++] = b;
	  o->rl_bufused = lgt;
	  o->rl_lastbyte = b;
	  if(lgt >= RL_MAXLENGTH) {
	    after_rl_add(o, RL_STRINGLENGTH(lgt));
	    for(i = 0; i < lgt; i++) {
	      after_rl_add(o, buffer[i]);
	    }
	    o->rl_bufused = 0;
	  }
	  o->rl_state = 0;
	}
      }
    } else {
      buffer[0] = b;
      o->rl_bufused = 1;
      o->rl_lastbyte = b;
    }
    o->rl_lastbyte = b;
    
  } else { 
    after_rl_add(o,0);
    after_rl_add(o,b);
  }
  
}

static void after_rl_flush(Output_Encoder *o)
{
  
  if(o->mode & OE_FLATE) {
    flate_flush(o);
  } else {
    after_flate_flush(o);
  }
  
}

static void rl_flush(Output_Encoder *o)
{
  int lgt;
  int *buffer;
  int i;
  
  buffer = o->rl_buffer;
  lgt = o->rl_bufused;
  if(lgt > 0) {
    if(o->rl_state == 2) {
      i = o->rl_lastbyte;
      after_rl_add(o,RL_RUNLENGTH(lgt));
      after_rl_add(o,i);
    } else {
      after_rl_add(o,RL_STRINGLENGTH(lgt));
      for(i = 0; i < lgt; i++) {
	after_rl_add(o,buffer[i]);
      }
    }
  }
  after_rl_flush(o);
  
}

static void internal_byte_add(Output_Encoder *o, int b)
{
  
  if((o->mode) & OE_RL) {
    rl_add(o,b);
  } else {
    after_rl_add(o,b);
  }
  
}

static void internal_byte_flush(Output_Encoder *o)
{
  
  if((o->mode) & OE_RL) {
    rl_flush(o);
  } else {
    after_rl_flush(o);
  }
  
}

void oe_bit_add(Output_Encoder *o, int b)
{
  
  o->bit_value = 2 * o->bit_value + (b ? 1 : 0);
  o->bit_consumed = o->bit_consumed + 1;
  if(o->bit_consumed >= 8) {
    o->bit_consumed = 0;
    internal_byte_add(o, (o->bit_value));
    o->bit_value = 0;
  }
  
}

void oe_bit_flush(Output_Encoder *o)
{
  
  if(o->bit_consumed) {
    int v, i;
    v = o->bit_value;
    i = o->bit_consumed;
    while(i < 8) {
      i++;
      v = v * 2;
    }
    internal_byte_add(o,v);
    o->bit_value = 0;
    o->bit_consumed = 0;
  }
  internal_byte_flush(o);
  
}

void oe_byte_add(Output_Encoder *o, int b)
{
  
  if(o->bit_consumed) {
    int testval,i;
    testval = 128;
    for(i = 0; i < 8; i++) {
      if(b & testval) {
	oe_bit_add(o,1);
      } else {
	oe_bit_add(o,0);
      }
      testval = testval / 2;
    }
  } else {
    internal_byte_add(o,b);
  }
  
}

void oe_byte_flush(Output_Encoder *o)
{
  
  oe_bit_flush(o);
  
}


