/* This source code is (c) Eric Mumpower 1997, all rights reserved. */
/* If you would like to redistribute or reuse this source code, please
 * contact Eric Mumpower (nocturne@mit.edu).
 */

/*
 * things to do:
 * standardize error reporting!!
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include "giflib.h"
#include "util.h"
#include "codetab.h"
#include "myerror.h"

int FindColInTable(color *CT, int CTlength, int tGreen, int tRed, int tBlue);
color *ReadColorTable(FILE *fp_in, int CTlength, FILE *spewout);
int CTIndexOfXY(FILE *fp_in, int x, int y, imageinfo *iinfo);
int getLZWpixel(FILE *fp_in, int pixelnum, imageinfo *iinfo);
int expand_elt(codetab *ct, int codenum, int goalpix);
int firstchar (codetab *ct, int codenum);
int read_gif_header(FILE *fp_in, imageinfo *iinfo, FILE *spewout);
void transify_gce(imageinfo *iinfo, unsigned char *gce,
		  int tRed, int tBlue, int tGreen);
int read_gif_stream(FILE *fp_in, imageinfo *iinfo, what_to_do *to_do);
int indexof (codetab *ct, int codenum, int offset);

#ifdef XSTUFF
void stuffpixels(codetab *ct, int codenum);
void do_x_stuff (imageinfo *iinfo);
#endif

void bin_lenprint (int x, int len);
void print_image ();
void bin_print (int x);

int  ImageX, ImageY; 
char *ImageData;
unsigned long ImageP;
unsigned long pixcount;

extern jmp_buf jmpbuffer;

#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))

/*
 *  
 *   Library goals...
 *
 *  (1) Provide transparentization of a GIF given an RGB color identifier
 *
 *  (2) Provide transparentization of a GIF given an image coordinate
 *
 *  (3) Die cleanly if we cannot malloc or read our input
 *
 *  use tmpfile() eventually
 *
 */


  /*
   * Need to do some hacking to make this convert gif87 to gif89a on the
   * fly. Ick.
   */

int read_gif(FILE *fp_in, what_to_do *directive)
{
  /* Return values:
   *   0 is success
   *  -1 is failure during read; file pointer was restored
   *  -2 is failure during fgetpos(); file pointer NOT restored.
   *  -3 is failure during write; file pointer was restored
   */

  fpos_t restore_position;
  imageinfo iinfo;

  if (fgetpos(fp_in, &restore_position) != 0) {
    fprintf(stderr, "Error reading input file.\n");
    return(-2);
  }
  
  rewind(fp_in);
  
  if (read_gif_header(fp_in, &iinfo, directive->spewout) == -1) {
    fprintf(stderr, "Error: input file not in the GIF format.\n");
    fsetpos(fp_in, &restore_position);
  }
  
  ImageData = malloc(iinfo.width * iinfo.height);
  ImageP = 0;

  read_gif_stream(fp_in, &iinfo, directive);

  fsetpos(fp_in, &restore_position);
  return(0);
}

int read_gif_header(FILE *fp_in, imageinfo *iinfo, FILE *spewout)
{
  unsigned char buf[13]; /* Be careful not to read more than 13 bytes! */
  int  packed_tmp;

  /* reads in 13 bytes */
  uc_fread(buf, 13, fp_in);

  if (memcmp(buf, "GIF87a", 6) == 0) {
    iinfo->itis87a = 1;
  } else if (memcmp(buf, "GIF89a", 6) == 0) {
    iinfo->itis87a = 0;
  } else {
    return(-1);
  }
  
  iinfo->width  = (unsigned int) (buf[7] << 8) + (unsigned int) buf[6];
  iinfo->height = (unsigned int) (buf[9] << 8) + (unsigned int) buf[8];
  
  packed_tmp = (int) buf[10];
  
  iinfo->gct_p           = getbits(1, 7, packed_tmp);
  iinfo->color_res       = getbits(3, 6, packed_tmp);
  iinfo->sort_flag       = getbits(1, 3, packed_tmp);
  iinfo->size_gct        = getbits(3, 2, packed_tmp);
  
  iinfo->bkg_color_index = buf[11];
  iinfo->pixel_aratio    = buf[12];
  
#ifdef DEBUG
  debugimageinfo(iinfo);
#endif

  if (iinfo->gct_p == 1) {
    /* CTlength == 2^(SGCT + 1) */
    iinfo->ct_length  = ipow(2, (iinfo->size_gct + 1));
    iinfo->colortable = ReadColorTable(fp_in, iinfo->ct_length, spewout);
    
  } else {
    iinfo->ct_length  = 0;
    iinfo->colortable = NULL;
  }

  if (spewout != NULL){
    memcpy(buf, "GIF89a", 6);
    uc_fwrite(buf, 13, spewout);
  }

  return(0); /* success */
}

int read_gif_stream(FILE *fp_in, imageinfo *iinfo, what_to_do *to_do)
{
  unsigned char buf[10]; /* We only ever use this many bytes. */
  unsigned char gce[9];
  int trailer_reached, packed;
  trailer_reached = gce[0] = 0;

  /* TransColorIndex is the index of the color-table entry which is to be  */
  /* transparent. We arbitrarily set it to 0; if no better index is found, */
  /* then we have the right to leave it at an arbitrary index, e.g. 0.     */
  iinfo->trans_index = 0;
  
  while (!trailer_reached) {
    unsigned char blocklabel, nextbyte;
    fpos_t start_of_block;
    int lct_p, lct_size;

    /* peek at the header of the next block (the next byte of the stream) */
    if (fgetpos(fp_in, &start_of_block) != 0) {
      fprintf(stderr, "Error reading input file.\n");
      return(-2);
    }
    /* read one byte */
    uc_fread(buf, 1, fp_in);
    blocklabel = buf[0];

    switch (blocklabel) {
    case 0x2C:			/* Image Descriptor */
      /*
       * read in the descriptor. Remember where we were, though.
       * If the LocalColorTable flag is set, we will need to read in
       * the LCT.
       */
      uc_fread(buf, 9, fp_in);

      packed = buf[8];
      lct_p = getbits(1, 7, packed);
      lct_size = getbits(3, 2, packed);
      iinfo->interlaced_p = getbits(1, 6, packed);

      if (lct_p) {
#ifdef DEBUG
	fprintf(stderr, "Local Color Table found.\n");
#endif
	/* We can throw away the information from the previous ColorTable; */
	/* it is superceded by the new ColorTable. */
	if (iinfo->colortable != NULL) {
	  free(iinfo->colortable);
	  iinfo->colortable = NULL;
	}

	iinfo->ct_length  = ipow(2, (lct_size + 1));
	iinfo->colortable = ReadColorTable(fp_in, iinfo->ct_length, NULL);
      }

      switch(to_do->doing) {
      case TRANS_IMG:
	transify_gce(iinfo, gce, to_do->red, to_do->grn, to_do->blu);
	break;
      case GET_PIXEL:
	to_do->answer = CTIndexOfXY(fp_in, to_do->x, to_do->y, iinfo);
#ifdef XSTUFF
	do_x_stuff(iinfo);
#endif
	return(0);
	break;
      default:
	printf("Should not have had an unknown directive. Aborting.\n");
	exit(-1);
      }

      if (to_do->spewout != NULL) {
	/* spew out the gce, rewind the input-stream-position-pointer
	 *  to point to the beginning of the incoming image descriptor, and
	 *  blindly spew the remainder of the input stream. (And we're done.)
	 */
	uc_fwrite(gce, 8, to_do->spewout); /* spew gce */
	fsetpos(fp_in, &start_of_block); /* rewind input */
	spewrest(fp_in, to_do->spewout); /* spew remainder of input stream */
      }
      return(0);

    case 0x21:			/* Extension Introducer */
      uc_fread(buf, 1, fp_in);
      nextbyte = buf[0];
	
      if (nextbyte == 0xF9) {	/* Graphic extension block */
	/*
	 * Read the graphic-extension-block into memory, to be twiddled
	 * when the index of the color to be transparentized is known.
	 *
	 * If we already have a gce in memory, it will be overwritten.
	 * It's okay to ignore the earlier gce since having more than
	 * one gce before the first graphic rendering block is against
	 * the gif standard. 
	 */
	gce[0] = 0x21;
	gce[1] = 0xF9;
	uc_fread((gce + 2), 6, fp_in);
      } else {
	fprintf(stderr, "Houston to Control, we have a problem.\n");
	/* XXX need to adjust this to perform a pass-through */
      }
      break;
    case 0x3B:			/* Trailer */
      /* Exit. We're done. */
#ifdef DEBUG
      fprintf(stderr, "End of GIF stream found.\n");
#endif
      trailer_reached = 1;
      if (to_do->spewout != NULL) {
	uc_fwrite(buf, 1, to_do->spewout);
      }
      break;
    default:
      /* XXX need an invocation of uc_fwrite here, dunno how many bytes. */
      /*
       * spew the block. Iterate to next block.
       */
    }
  }

  return(0);
}

int FindColInTable(color *ct, int ct_size, int g, int r, int b)
{
  /* ct here refers to *color*table, not *code*table. */
  int colornum, answer;

  /*
   * Find the first instance of our color in it and
   * set answer appropriately.;
   * If not found, answer should still be 0.;
   */
  answer = 0;
  for (colornum = 0; ! (colornum > ct_size) ; colornum++) {
    if (((ct[colornum]).red == r) &&
	((ct[colornum]).grn == g) &&
	((ct[colornum]).blu == b)) {
      answer = colornum;
      break;
    }
  }
  return(answer);
}

color *ReadColorTable(FILE *fp_in, int ct_length, FILE *spewout)
{
  /* ct here refers to *color*table, not *code*table. */
  color *ct;
  unsigned char buf[BUFSIZ];
  int colornum, tmp;

  ct = (color *) malloc(ct_length * sizeof(color));

  if (ct == NULL) {
    longjmp(jmpbuffer, G_ERR_NOMEM);
  }

  uc_fread(buf, (3 * ct_length), fp_in);
  if (spewout != NULL) {
    uc_fwrite(buf, (3 * ct_length), spewout);
  }

  for (colornum = 0; ! (colornum > ct_length) ; colornum++) {
    tmp = (3 * colornum);
    (ct[colornum]).red = (unsigned int) buf[tmp    ];
    (ct[colornum]).grn = (unsigned int) buf[tmp + 1];
    (ct[colornum]).blu = (unsigned int) buf[tmp + 2];
    }

  return(ct);
}

int CTIndexOfXY(FILE *fp_in, int x, int y, imageinfo *iinfo)
{
  int pixelnum, answer;

  if (iinfo->interlaced_p) {
    fprintf(stderr, "We don't know how to do interlaced gifs yet.\n");
    pixelnum = 1;
  } else {
    pixelnum = (y * iinfo->width) + x + 1;
    /* the top left pixel is pixel number 1 */
  }

  printf("finding (%i, %i)\n", x, y);

  pixcount = 0;
  answer = getLZWpixel(fp_in, pixelnum, iinfo);
  printf("Pixelcount: %lu\n", pixcount);
  answer--; /* this compensates for the contract of getLZWpixel */
  return(answer);
}

int getLZWpixel(FILE *fp_in, int pixelnum, imageinfo *iinfo)
{

  /*
   * return value of -1 is failure. A successful return value will
   * be positive and == (1 + colortable-index-of-desired-pixel)
   */
  
  /* 
   * This code assumes that BUFSIZ is at least "3". If it isn't, then this
   * code will do nasty things to your memory.
   */
#define MYBUFSIZ BUFSIZ
  unsigned char buf[MYBUFSIZ];
  codetab ct;
  int offset, answer, valuefornextjump, oldword, newtablep;
  int wasread, found, bitptr, byteptr, gotptr, leftinblock, eltnum;
  int word = 0;
  found = bitptr = byteptr = gotptr = 0;

  ct_preinitialize(&ct);

  uc_fread(buf, 2, fp_in);
  ct.rootsize  = (int) buf[0];
  ct.blocksize = (int) buf[1];

  ct.codesize  = (ct.rootsize + 1);
  valuefornextjump = ipow(2, ct.codesize);
  /* ct.blocksize = blocksize; */
  ct.clearcode = ipow(2, ct.rootsize);
  ct.endofinfo = (ct.clearcode + 1);
  
  printf("Blocksize %i !!\n", ct.blocksize);
  
  ct_seed(&ct, iinfo->ct_length);

  wasread = suckchunk(buf, MYBUFSIZ, fp_in);
  offset  = 0;
  leftinblock = ct.blocksize;
  oldword = -1;
  newtablep = 1;

  while (!found) {
    if (offset >= wasread ) {
      /* XXX need to make this more robust later. */
      offset  = 0;  /* offset - wasread; */
      wasread = suckchunk(buf, MYBUFSIZ, fp_in);
      if ((wasread == 0) && (feof(fp_in) != 0)) {
	found = 1;
      }
    }

    if (!leftinblock) {
      leftinblock = buf[offset++];
    } else {
      while ((bitptr < 8) && (!found)) {
	int bitstoget;

	bitstoget = min( (ct.codesize - gotptr), (8 - bitptr) );
	/* we want to get (ct.codesize - gotptr) bits unless this is greater */
	/*  than the number of bits left in the current byte              */

	word = word |
	  (getbits(bitstoget, (bitptr + bitstoget - 1), buf[offset]) << gotptr);
	bitptr = bitptr + bitstoget;
	gotptr = gotptr + bitstoget;
	bitstoget = 0;

	if (gotptr == ct.codesize) {
	  /* If we have assembled a full code */

	  if (word == ct.clearcode) {
	    ct.nextentry = ct.endofinfo + 1;
	    gotptr = 0;
	    word = 0;
	    oldword = -1;
	    ct.codesize  = (ct.rootsize + 1);
	    valuefornextjump = ipow(2, ct.codesize);
	    newtablep = 1;
	  } else {
	    if (word == ct.endofinfo) {
	      found = 1;
	    } else {
              oldword = word;
	      if (!newtablep) {
		if (word < ct.nextentry) {
		  /* that is, if this word refers to an entry already in the */
		  /* code table */
	      
		  if ((answer = expand_elt(&ct, word, pixelnum)) != 0) {
		    return(answer);
		  }
		  ct_append(&ct, oldword, firstchar(&ct, word));
		} else {
		  eltnum = ct_append(&ct, oldword, firstchar(&ct, oldword));
		  if ((answer = expand_elt(&ct, eltnum, pixelnum)) != 0) {
		    return(answer);
		  }
		}

		if ((ct.nextentry == valuefornextjump) &&
		    (ct.codesize < 12)){

		  ct.codesize++;
		  valuefornextjump = valuefornextjump * 2;
		}
	      } else {
		/* this is the first code of the new code table */
		newtablep = 0;
		if ((answer = expand_elt(&ct, word, pixelnum)) != 0) {
		  return(answer);
		}
	      }
              gotptr = 0;
              word = 0;
	    }
	  }
	}
      }
    }

    if (bitptr > 7) {
      bitptr = bitptr - 8;
      offset++;
      leftinblock--;
    }
  }
  printf("We finished.\n");
  /* print_image(); */
  return(3);
}

int expand_elt(codetab *ct, int codenum, int goalpix)
{
  /* returns 0 if not found; returns some positive int */
  /* == (1 + index_of_goalpix) if it's been found */

  register int length;
  int answer, offset;

#ifdef XSTUFF
  stuffpixels(ct, codenum);
#endif /* XSTUFF */

  length = elt_length(ct->table[codenum]);
  pixcount = pixcount + length;

  if (pixcount >= goalpix) {
    offset = goalpix - (pixcount - length);
    answer = 1 + indexof(ct, codenum, offset);
    return(answer);
  }
  return(0);
}

int indexof (codetab *biteme, int codenum, int offset)
{
  int answer;
  /* a bit of magic here; you need to look at the left-recursive */
  /* data structure to understand why this works. */
  if (offset == elt_length(biteme->table[codenum])) {
    answer = ((biteme->table)[codenum]).cdr;
  } else {
    answer = indexof(biteme, ((biteme->table)[codenum]).car, offset);
  }
  return(answer);
}

#ifdef XSTUFF
void stuffpixels(codetab *codtab, int codenum)
{
  int first;

#ifdef DEBUG
  if (codenum >= codtab->nextentry) {
    printf("WE HAVE A LZW PROBLEM.\n");
    longjmp(jmpbuffer, G_ERR_BADGIF);
  }
#endif /* DEBUG */

  if ((first = codtab->table[codenum].car) != -1) {
    /* If there is a 'head' for the current code, expand it. */
    stuffpixels(codtab, first);
  }

  ImageData[ImageP++] = (unsigned char) (codtab->table[codenum].cdr);
}
#endif /* XSTUFF */


int firstchar (codetab *ct, int codenum)
{
  int first, answer;

  if ((first = ct->table[codenum].car) != -1) {
    /* If there is a 'head' for the previous code we deref'd, */
    /* expand it. */
    answer = firstchar(ct, first);
  } else {
    answer = ct->table[codenum].cdr;
  }

  return(answer);
}

void print_image ()
{
  int i;
  for (i=0; i < ImageP; i++) {
    printf(" %i", ImageData[i]);
  }
  printf("\n");
}

void transify_gce(imageinfo *iinfo, unsigned char *gce,
		  int tRed, int tBlue, int tGreen)
{
  int gcefound;

  /*
   * Find the index number of the color we want to make transparent :
   * if CT == NULL, then we can use an index of 0 (because we have no CT)
   * If CT != NULL, we have a CT; scan it for the color we want.
   */
  if (iinfo->colortable == NULL) {
    iinfo->trans_index = 0;
  } else {
    iinfo->trans_index = FindColInTable(iinfo->colortable,
				       iinfo->ct_length,
				       tRed, tGreen, tBlue);
  }
#ifdef DEBUG
  fprintf(stderr, "\nIndex to be transparent: %u\n", iinfo->trans_index);
#endif
  
  /*
   * Once we have checked for a LCT and found our index, add
   *  the transparent color:
   *  + if we haven't yet found a graphic control extension, then
   *      create one with appropriate values.
   *  + Otherwise (if we've seen a gce), edit it (it's in memory).
   *
   */
  if (gcefound) {
    gce[3] = gce[3] || 0x01;	/* Turn on the transparency flag */
    gce[6] = iinfo->trans_index;
  } else {
    /* create a gce */
    gcefound = 1;		/* do we want this? XXX */
    gce[0] = 0x21;		/* Extension Introducer  */
    gce[1] = 0xF9;		/* Graphic Control Label */
    gce[2] = 0x04;		/* Block Size */
    gce[3] = 0x01;		/* Packed field, all "default" except transp flag */
    gce[4] = gce[5] = 0x00;	/* Delay Time */
    gce[6] = iinfo->trans_index;
    gce[7] = 0x00;		/* Block Terminator */
  }
}

void init_trans_directive(what_to_do *to_do, int r, int g, int b, FILE *fp)
{
  to_do->doing   = TRANS_IMG;
  to_do->red     = r;
  to_do->grn     = g;
  to_do->blu     = b;
  to_do->spewout = fp;
}

void init_pixel_directive(what_to_do *to_do, int x, int y, color *col)
{
  to_do->doing     = GET_PIXEL;
  to_do->x         = x;
  to_do->y         = y;
  to_do->the_color = col;
  to_do->spewout   = NULL;
}
