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

#define stderr stdout

int FindColInTable(color *CT, int CTlength, int tGreen, int tRed, int tBlue);
color *ReadColorTable(FILE *fp_in, int CTlength, FILE *spewout);
int PixelColor(FILE *fp_in, color *TheColor, int x, int y);
int CTIndexOfXY(FILE *fp_in, int x, int y, imageinfo *iinfo);
int getLZWpixel(FILE *fp_in, int pixelnum, imageinfo *iinfo);
int seed_table(codetab *ct, int CTlength);
int expand_elt(codetab *ct, int codenum);
int firstchar (codetab *ct, int codenum);

int  ImageX, ImageY; 
char *ImageData;
unsigned long ImageP;
/*
 *  
 *   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 trans_rgb(FILE *fp_in, FILE *fp_out, int tRed, int tGreen, int tBlue) {

  /* 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;
  /* Maybe this should use "1024" instead of BUFSIZ. Hm. */
  unsigned char buf[BUFSIZ];
  unsigned int  imagewidth, imageheight;
  int  itis87a, packed;
  int  GlobalColorTableFlag, ColorResolution, SortFlag;
  int  SizeofGlobalColorTable;
  int  BackgroundColorIndex;
  int  PixelAspectRatio;
  int  TransColorIndex;
  int  CTlength = 0;
  color *CT = NULL;
  
  if (fgetpos(fp_in, &restore_position) != 0) {
    fprintf(stderr, "Error reading input file.\n");
    return(-2);
  }
  
  rewind(fp_in);
  
  uc_fread(buf, 6, fp_in);
  uc_fwrite("GIF89a", 6, fp_out);

  if (strcmp(buf, "GIF87a") == 0) {
    itis87a = 1;
  } else if (strcmp(buf, "GIF89a") ==0) {
    itis87a = 0;
  } else {
    fprintf(stderr, "Error: input file not in the GIF format.\n");
    fsetpos(fp_in, &restore_position);
    return(-1);
  }
  
  /* This reads 7 bytes */
  
  uc_fread(buf, 7, fp_in);
  uc_fwrite(buf, 7, fp_out);
  imagewidth  = (buf[1] << 8) + buf[0];
  imageheight = (buf[3] << 8) + buf[2];
  
  packed = (int) buf[4];
  
  GlobalColorTableFlag   = getbits(1, 7, packed);
  ColorResolution        = getbits(3, 6, packed);
  SortFlag               = getbits(1, 3, packed);
  SizeofGlobalColorTable = getbits(3, 2, packed);
  
  BackgroundColorIndex = buf[5];
  PixelAspectRatio     = buf[6];
  
#ifdef DEBUG
  if (itis87a == 1) {
    fprintf(stderr, "Hey, it's 87a.\n");
  } else {
    fprintf(stderr, "Hey, it's 89a.\n");
  }
  fprintf(stderr, "Width:  %u\nHeight: %u\n", imagewidth, imageheight);
  fprintf(stderr, "GCTF:   %u\n", GlobalColorTableFlag);
  fprintf(stderr, "CR:     %u\n", ColorResolution);
  fprintf(stderr, "SF:     %u\n", SortFlag);
  fprintf(stderr, "SGCT:   %u\n", SizeofGlobalColorTable);
  fprintf(stderr, "BCI:    %u\n", BackgroundColorIndex);
  fprintf(stderr, "PAR:    %u\n", PixelAspectRatio);
#endif
  
  if (GlobalColorTableFlag == 1) {
    /* CTlength == 2^(SGCT + 1) */
    CTlength = ipow(2, (SizeofGlobalColorTable + 1));
    CT = ReadColorTable(fp_in, CTlength, fp_out);
  }
  
  {
    unsigned char GCE[9], IDhead[11];
    int TrailerReached, GCEfound;
    TrailerReached = GCEfound = GCE[0] = IDhead[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.     */
    TransColorIndex = 0;

    while (!TrailerReached) {
      unsigned char blocklabel, nextbyte;
      fpos_t start_of_block;
      int LCTflag, LCTsize;

      /* 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.
	 */
	IDhead[0] = 0x2C;
	uc_fread((IDhead + 1), 9, fp_in);

	packed = IDhead[9];
	LCTflag = getbits(1, 7, packed);
	LCTsize = getbits(3, 2, packed);
	if (LCTflag) {
#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 (CT != NULL) {
	    free(CT);
	  }

	  CTlength = ipow(2, (LCTsize + 1));
	  CT = ReadColorTable(fp_in, CTlength, NULL);
	}

	/*
	 * 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 (CT == NULL) {
	  TransColorIndex = 0;
	} else {
	  TransColorIndex = FindColInTable(CT, CTlength, tRed, tGreen, tBlue);
	}
#ifdef DEBUG
	fprintf(stderr, "\nIndex to be transparent: %u\n", TransColorIndex);
#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] = TransColorIndex;
	} 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] = TransColorIndex;
	  GCE[7] = 0x00; /* Block Terminator */
	}
	/*
	 * Then, 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.)
	 */
#ifdef DEBUG
	  fprintf(stderr, "Yeehaw! Home free. We're done.\n");
#endif
	uc_fwrite(GCE, 8, fp_out);       /* spew GCE */
	fsetpos(fp_in, &start_of_block); /* rewind input */
	spewrest(fp_in, fp_out);         /* spew remainder of input stream */
	fsetpos(fp_in, &restore_position);
	return(0);
	break; /* XXX is this either appropriate or necessary? */
      case 0x21:		/* Extension Introducer */
	uc_fread(buf, 1, fp_in);
	nextbyte = buf[0];
	
	if (nextbyte == 0xF9) {	/* Graphic extension block */
#ifdef DEBUG
	  fprintf(stderr, "We've found the start of a GCE.\n");
#endif
	  /*
	   * XXX perhaps if we already have a GCE in memory, spew it out?
	   * This should NEVER happen, but you never know how bizarrely
	   * uncompliant some GIF encoders may be.
	   */
	  /*
	   * Read the graphic-extension-block into memory.
	   * Later, when we know the color index to be transparentized,
	   * we'll twiddle it so it specifies the correct color index.
	   */
	  GCEfound = 1;
	  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
	TrailerReached = 1;
	uc_fwrite(buf, 1, fp_out);
	break;
      default:
	/* XXX need an invocation of uc_fwrite here, dunno how many bytes. */
	/*
	 * spew the block. Iterate to next block.
	 */
      }
    }

    /*
     *   content model:
     *
     * (master) header
     *       ......
     */
    fsetpos(fp_in, &restore_position);
    return(0);
  }
}

int FindColInTable(color *CT, int CTlength, int tGreen, int tRed, int tBlue){
  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 > CTlength) ; colornum++) {
    if (((CT[colornum]).red == tRed  ) &&
	((CT[colornum]).grn == tGreen) &&
	((CT[colornum]).blu == tBlue )) {
      answer = colornum;
      break;
    }
  }
  return(answer);
}

color *ReadColorTable(FILE *fp_in, int CTlength, FILE *spewout) {
  color *CT;
  unsigned char buf[BUFSIZ];
  int colornum, tmp;

  CT = (color *) malloc(CTlength * sizeof(color));

  if (CT == NULL) {
    fprintf(stderr, "Cannot allocate memory. Aborting.\n");
    exit(1);
  }

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

  for (colornum = 0; ! (colornum > CTlength) ; 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];
    }

/*
 *  #ifdef DEBUG
 *   for (colornum = 0; colornum < CTlength ; colornum++) {
 *     fprintf(stderr, "Color %u: %u\n", colornum, (CT[colornum]).red);
 *     fprintf(stderr, "Color %u: %u\n", colornum, (CT[colornum]).grn);
 *     fprintf(stderr, "Color %u: %u\n", colornum, (CT[colornum]).blu);
 *     }
 * #endif
 */

  return(CT);
}

int PixelColor(FILE *fp_in, color *TheColor, int x, int y) {

  /* 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
   *  -4 is failure during gif decryption for now; probably malloc failure
   */
  
  fpos_t restore_position;
  /* Maybe this should use "1024" instead of BUFSIZ. Hm. */
  unsigned char buf[BUFSIZ];
  unsigned int  imagewidth, imageheight;
  int  itis87a, packed;
  int  GlobalColorTableFlag, ColorResolution, SortFlag;
  int  SizeofGlobalColorTable;
  int  BackgroundColorIndex;
  int  PixelAspectRatio;
  int  TransColorIndex;
  int  CTlength = 0;
  color *CT = NULL;
  
  if (fgetpos(fp_in, &restore_position) != 0) {
    fprintf(stderr, "Error reading input file.\n");
    return(-2);
  }
  
  rewind(fp_in);
  
  uc_fread(buf, 6, fp_in);

  if (strcmp(buf, "GIF87a") == 0) {
    itis87a = 1;
  } else if (strcmp(buf, "GIF89a") ==0) {
    itis87a = 0;
  } else {
    fprintf(stderr, "Error: input file not in the GIF format.\n");
    fsetpos(fp_in, &restore_position);
    return(-1);
  }
  
  /* This reads 7 bytes */
  
  uc_fread(buf, 7, fp_in);
  imagewidth  = (buf[1] << 8) + buf[0];
  imageheight = (buf[3] << 8) + buf[2];
  
  packed = (int) buf[4];
  
  GlobalColorTableFlag   = getbits(1, 7, packed);
  ColorResolution        = getbits(3, 6, packed);
  SortFlag               = getbits(1, 3, packed);
  SizeofGlobalColorTable = getbits(3, 2, packed);
  
  BackgroundColorIndex = buf[5];
  PixelAspectRatio     = buf[6];
  
#ifdef DEBUG
  if (itis87a == 1) {
    fprintf(stderr, "Hey, it's 87a.\n");
  } else {
    fprintf(stderr, "Hey, it's 89a.\n");
  }
  fprintf(stderr, "Width:  %u\nHeight: %u\n", imagewidth, imageheight);
  fprintf(stderr, "GCTF:   %u\n", GlobalColorTableFlag);
  fprintf(stderr, "CR:     %u\n", ColorResolution);
  fprintf(stderr, "SF:     %u\n", SortFlag);
  fprintf(stderr, "SGCT:   %u\n", SizeofGlobalColorTable);
  fprintf(stderr, "BCI:    %u\n", BackgroundColorIndex);
  fprintf(stderr, "PAR:    %u\n", PixelAspectRatio);
#endif
  
  if (GlobalColorTableFlag == 1) {
    /* CTlength == 2^(SGCT + 1) */
    CTlength = ipow(2, (SizeofGlobalColorTable + 1));
    CT = ReadColorTable(fp_in, CTlength, NULL);
  }
  
  {
    unsigned char GCE[9], IDhead[11];
    int TrailerReached;
    imageinfo iinfo;
    TrailerReached = IDhead[0] = 0;

    while (!TrailerReached) {
      unsigned char blocklabel, nextbyte;
      fpos_t start_of_block;
      int LCTflag, LCTsize, IsInterlaced;

      /* 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.
	 */
	IDhead[0] = 0x2C;
	uc_fread((IDhead + 1), 9, fp_in);

	packed = IDhead[9];
	LCTflag = getbits(1, 7, packed);
	LCTsize = getbits(3, 2, packed);
	IsInterlaced = getbits(1, 6, packed);
	if (LCTflag) {
#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 (CT != NULL) {
	    free(CT);
	  }

	  CTlength = ipow(2, (LCTsize + 1));
	  CT = ReadColorTable(fp_in, CTlength, NULL);
	}

	TransColorIndex = CTIndexOfXY(fp_in, IsInterlaced, x, y,
				      imagewidth, imageheight);
	fsetpos(fp_in, &restore_position);

	TheColor->red = CT[TransColorIndex].red;
	TheColor->blu = CT[TransColorIndex].blu;
	TheColor->grn = CT[TransColorIndex].grn;

	return(0);
	break; /* XXX is this either appropriate or necessary? */
      case 0x21:		/* Extension Introducer */
	uc_fread(buf, 1, fp_in);
	nextbyte = buf[0];
	
	if (nextbyte == 0xF9) {	/* Graphic extension block */
#ifdef DEBUG
	  fprintf(stderr, "We've found the start of a GCE.\n");
#endif
	  /*
	   * XXX perhaps if we already have a GCE in memory, spew it out?
	   * This should NEVER happen, but you never know how bizarrely
	   * uncompliant some GIF encoders may be.
	   */
	  /*
	   * Read the graphic-extension-block into memory.
	   * Later, when we know the color index to be transparentized,
	   * we'll twiddle it so it specifies the correct color index.
	   */
	  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
	TrailerReached = 1;
	break;
      default:
	/* XXX need an invocation of uc_fwrite here, dunno how many bytes. */
	/*
	 * spew the block. Iterate to next block.
	 */
      }
    }

    /*
     *   content model:
     *
     * (master) header
     *       ......
     */
    fsetpos(fp_in, &restore_position);
    return(0);
  }
}

int CTIndexOfXY(FILE *fp_in, int IsInterlaced, int x, int y,
		int width, int height) {
  int pixelnum, answer;
  if (IsInterlaced) {
    fprintf(stderr, "We don't know how to do interlaced gifs yet.\n");
    pixelnum = 1;
  } else {
    pixelnum = (y * width) + x; /* XXX may have off-by-one or sign errors */
  }

  answer = getLZWpixel(fp_in, pixelnum);

  return(answer);
}

int getLZWpixel(FILE *fp_in, int pixelnum) {

  unsigned char buf[BUFSIZ];
  int codesize, blocksize;
  /* int *Translation[ */

  uc_fread(buf, 2, fp_in);
  codesize  = (int) buf[0];
  blocksize = (int) buf[1];
  

  return(3);
}
