/** \file
 * \brief GIF - Graphics Interchange Format
 *
 * See Copyright Notice in im_lib.h
 */

#include "im_format.h"
#include "im_format_all.h"
#include "im_util.h"
#include "im_counter.h"

#include "im_binfile.h"

#include <stdlib.h>
#include <string.h>
#include <setjmp.h>

static const int InterlacedOffset[4] = { 0, 4, 2, 1 },  /* The way Interlaced image should */
	               InterlacedJumps[4]  = { 8, 8, 4, 2 };  /* be read - offsets and jumps... */

#define GIF_STAMP	  "GIF"	 /* First chars in file - GIF stamp. */
#define GIF_VERSION	"89a"	 /* First chars in file - GIF stamp. */
 
#define GIF_LZ_BITS		12

#define GIF_LZ_MAX_CODE	    4095		/* Biggest code possible in 12 bits. */
#define GIF_FLUSH_OUTPUT		4096    /* Impossible code, to signal flush. */
#define GIF_FIRST_CODE		  4097    /* Impossible code, to signal first. */
#define GIF_NO_SUCH_CODE		4098    /* Impossible code, to signal empty. */

#define GIF_HT_KEY_MASK		0x1FFF		/* 13bits keys */
#define GIF_HT_KEY_NUM_BITS		13		/* 13bits keys */
#define GIF_HT_MAX_KEY		  8191	  /* 13bits - 1, maximal code possible */
#define GIF_HT_SIZE			    8192	  /* 12bits = 4096 or twice as big! */

/*  GIF89 extension function codes                                             */
#define COMMENT_EXT_FUNC_CODE	    0xFE	/* comment */
#define GRAPHICS_EXT_FUNC_CODE    0xF9	/* graphics control */
#define PLAINTEXT_EXT_FUNC_CODE   0x01	/* plaintext */
#define APPLICATION_EXT_FUNC_CODE 0xFF	/* application block */

/* The 32 bits of the integer are divided into two parts for the key & code:   */
/* 1. The code is 12 bits as our compression algorithm is limited to 12bits */
/* 2. The key is 12 bits Prefix code + 8 bit new char or 20 bits.	    */
#define GIF_HT_GET_KEY(l)	(l >> 12)
#define GIF_HT_GET_CODE(l)	(l & 0x0FFF)
#define GIF_HT_PUT_KEY(l)	(l << 12)
#define GIF_HT_PUT_CODE(l)	(l & 0x0FFF)

struct iGIFData
{
  unsigned char global_colors[256 * 3]; /* global color table if any */
  int global_num_colors, /* global color table number of colors */
      offset,            /* image offset */
      step,              /* interlaced step */
      interlaced,        /* image is interlaced or not */
      screen_width,
      screen_height,
      start_offset[512], /* offset of first block */
	    ClearCode,				 /* The CLEAR LZ code. */
    	BitsPerPixel,	     /* Bits per pixel (Codes uses at list this + 1). */
	    EOFCode,				   /* The EOF LZ code. */
	    RunningCode,		   /* The next code algorithm can generate. */
	    RunningBits,       /* The number of bits required to represent RunningCode. */
	    MaxCode1,          /* 1 bigger than max. possible code, in RunningBits bits. */
	    LastCode,		       /* The code before the current code. */
	    CrntCode,				   /* Current algorithm code. */
	    StackPtr,		       /* For character stack (see below). */
	    CrntShiftState;		 /* Number of bits in CrntShiftDWord. */
  unsigned char Buf[256];	                  /* Compressed input is buffered here. */
  unsigned int CrntShiftDWord;             /* For bytes decomposition into codes. */
  unsigned char Stack[GIF_LZ_MAX_CODE];	    /* Decoded pixels are stacked here. */
  unsigned char Suffix[GIF_LZ_MAX_CODE+1];	/* So we can trace the codes. */
  unsigned int Prefix[GIF_LZ_MAX_CODE+1];
  unsigned int HTable[GIF_HT_SIZE];            /* hash table for the compression only, when using LZW */
};

/******************************************************************************
* Routine to generate an HKey for the hashtable out of the given unique key.  *
* The given Key is assumed to be 20 bits as follows: lower 8 bits are the     *
* new postfix character, while the upper 12 bits are the prefix code.	      *
* Because the average hit ratio is only 2 (2 hash references per entry),      *
* evaluating more complex keys (such as twin prime keys) does not worth it!   *
******************************************************************************/
static int iGIFHashKeyItem(unsigned int Item)
{
  return ((Item >> 12) ^ Item) & GIF_HT_KEY_MASK;
}

/******************************************************************************
* Routine to insert a new Item into the HashTable. The data is assumed to be  *
* new one.								      *
******************************************************************************/
static void iGIFInsertHashTable(unsigned int *HTable, unsigned int Key, int Code)
{
  int HKey = iGIFHashKeyItem(Key);

  while (GIF_HT_GET_KEY(HTable[HKey]) != 0xFFFFFL) 
  {
    HKey = (HKey + 1) & GIF_HT_KEY_MASK;
  }

  HTable[HKey] = GIF_HT_PUT_KEY(Key) | GIF_HT_PUT_CODE(Code);
}

/******************************************************************************
* Routine to test if given Key exists in HashTable and if so returns its code *
* Returns the Code if key was found, -1 if not.				      *
******************************************************************************/
static int iGIFExistsHashTable(unsigned int *HTable, unsigned int Key)
{
  int HKey = iGIFHashKeyItem(Key);
  unsigned int HTKey;

  while ((HTKey = GIF_HT_GET_KEY(HTable[HKey])) != 0xFFFFFL) 
  {
    if (Key == HTKey) 
      return GIF_HT_GET_CODE(HTable[HKey]);

    HKey = (HKey + 1) & GIF_HT_KEY_MASK;
  }

  return -1;
}

/******************************************************************************
*   This routines buffers the given characters until 255 characters are ready *
* to be output. If Code is equal to -1 the buffer is flushed (EOF).	      *
*   The buffer is Dumped with first byte as its size, as GIF format requires. *
******************************************************************************/
static int iGIFBufferedOutput(imBinFile* handle, unsigned char *Buf, int c)
{
  if (c == GIF_FLUSH_OUTPUT) 
  {
    /* Flush everything out. */
    if (Buf[0] != 0)
      imBinFileWrite(handle, Buf, Buf[0] + 1, 1);

    /* Mark end of compressed data, by an empty block (see GIF doc): */
    Buf[0] = 0;
    imBinFileWrite(handle, Buf, 1, 1);
  }
  else 
  {
    if (Buf[0] == 255) 
    {
      /* Dump out this buffer - it is full: */
      imBinFileWrite(handle, Buf, Buf[0] + 1, 1);
      Buf[0] = 0;
    }

    Buf[++Buf[0]] = (unsigned char)c;
  }

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  return IM_ERR_NONE;
}

static int iGIFWriteNetscapeApplication(imBinFile* handle, short iterations)
{
  /* record type byte */
  imBinFileWrite(handle, (void*)"!", 1, 1);

  /* block label */
  imBinFileWrite(handle, (void*)"\xFF", 1, 1);

  /* block size */
  imBinFileWrite(handle, (void*)"\x0B", 1, 1);

  /* application identifier + athentication code */
  imBinFileWrite(handle, (void*)"NETSCAPE2.0", 11, 1);

  /* sub block size */
  imBinFileWrite(handle, (void*)"\x3", 1, 1);

  /* ??? */
  imBinFileWrite(handle, (void*)"\x1", 1, 1);

  /* iterations */
  imBinFileWrite(handle, &iterations, 1, 2);

  /* block terminator */
  imBinFileWrite(handle, (void*)"\0", 1, 1);

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  return IM_ERR_NONE;
}

/******************************************************************************
*   The LZ compression output routine:					      *
*   This routine is responsable for the compression of the bit stream into    *
* 8 bits (bytes) packets.						      *
******************************************************************************/
static int iGIFCompressOutput(iGIFData* igif, imBinFile* handle, int Code)
{
  int error = IM_ERR_NONE;

  if (Code == GIF_FLUSH_OUTPUT) 
  {
    while (igif->CrntShiftState > 0) 
    {
      /* Get Rid of what is left in DWord, and flush it. */
      error = iGIFBufferedOutput(handle, igif->Buf, igif->CrntShiftDWord & 0xff);
      igif->CrntShiftDWord >>= 8;
      igif->CrntShiftState -= 8;
    }
    igif->CrntShiftState = 0;			   /* For next time. */
    error = iGIFBufferedOutput(handle, igif->Buf, GIF_FLUSH_OUTPUT);
  }
  else 
  {
    igif->CrntShiftDWord |= ((int) Code) << igif->CrntShiftState;
    igif->CrntShiftState += igif->RunningBits;
    while (igif->CrntShiftState >= 8) 
    {
      /* Dump out full bytes: */
      error = iGIFBufferedOutput(handle, igif->Buf, igif->CrntShiftDWord & 0xff);
      igif->CrntShiftDWord >>= 8;
      igif->CrntShiftState -= 8;
    }
  }

  /* If code cannt fit into RunningBits bits, must raise its size. Note */
  /* however that codes above 4095 are used for special signaling.      */
  if (igif->RunningCode >= igif->MaxCode1 && Code <= GIF_LZ_MAX_CODE) 
  {
    igif->MaxCode1 = 1 << ++igif->RunningBits;
  }

  return error;
}

/******************************************************************************
*   The LZ compression routine:						      *
*   This version compress the given buffer Line of length LineLen.	      *
*   This routine can be called few times (one per scan line, for example), in *
* order the complete the whole image.					      *
******************************************************************************/
static int iGIFCompressLine(iGIFData* igif, imBinFile* handle, unsigned char *Line, int LineLen)
{
  int i = 0, CrntCode, NewCode;
  unsigned int NewKey;
  unsigned char Pixel;

  if (igif->CrntCode == GIF_FIRST_CODE)		  /* Its first time! */
    CrntCode = Line[i++];
  else
    CrntCode = igif->CrntCode;     /* Get last code in compression. */

  while (i < LineLen) 
  {			    /* Decode LineLen items. */
    Pixel = Line[i++];		      /* Get next pixel from stream. */
    /* Form a new unique key to search hash table for the code combines  */
    /* CrntCode as Prefix string with Pixel as postfix char.	     */
    NewKey = (((unsigned int) CrntCode) << 8) + Pixel;

    if ((NewCode = iGIFExistsHashTable(igif->HTable, NewKey)) >= 0) 
    {
      /* This Key is already there, or the string is old one, so	     */
      /* simple take new code as our CrntCode:			     */
      CrntCode = NewCode;
    }
    else 
    {
      /* Put it in hash table, output the prefix code, and make our    */
      /* CrntCode equal to Pixel.					     */
      if (iGIFCompressOutput(igif, handle, CrntCode) != IM_ERR_NONE) 
        return IM_ERR_ACCESS;

      CrntCode = Pixel;

      /* If however the HashTable if full, we send a clear first and   */
      /* Clear the hash table.					     */
      if (igif->RunningCode >= GIF_LZ_MAX_CODE) 
      {
        /* Time to do some clearance: */
        if (iGIFCompressOutput(igif, handle, igif->ClearCode) != IM_ERR_NONE) 
          return IM_ERR_ACCESS;

        igif->RunningCode = igif->EOFCode + 1;
        igif->RunningBits = igif->BitsPerPixel + 1;
        igif->MaxCode1 = 1 << igif->RunningBits;
        memset(igif->HTable, 0xFF, GIF_HT_SIZE * sizeof(int));
      }
      else 
      {
        /* Put this unique key with its relative Code in hash table: */
        iGIFInsertHashTable(igif->HTable, NewKey, igif->RunningCode++);
      }
    }
  }

  /* Preserve the current state of the compression algorithm: */
  igif->CrntCode = CrntCode;

  return IM_ERR_NONE;
}

/******************************************************************************
*   This routines read one gif data block at a time and buffers it internally *
* so that the decompression routine could access it.			      *
*   The routine returns the next byte from its internal buffer (or read next  *
* block in if buffer empty).		      *
******************************************************************************/
static int iGIFBufferedInput(imBinFile* handle, unsigned char *Buf, unsigned char *NextByte)
{
  if (Buf[0] == 0) 
  {
    /* Needs to read the next buffer - this one is empty: */
    imBinFileRead(handle, Buf, 1, 1);
    imBinFileRead(handle, &Buf[1], Buf[0], 1);

    if (imBinFileError(handle))
      return IM_ERR_ACCESS;

    *NextByte = Buf[1];
    Buf[1] = 2;	   /* We use now the second place as last char read! */
    Buf[0]--;
  }
  else 
  {
    *NextByte = Buf[Buf[1]++];
    Buf[0]--;
  }

  return IM_ERR_NONE;
}

/******************************************************************************
*   The LZ decompression input routine:					      *
*   This routine is responsable for the decompression of the bit stream from  *
* 8 bits (bytes) packets, into the real codes.				      *
******************************************************************************/
static int iGIFDecompressInput(iGIFData* igif, imBinFile* handle, int *Code)
{
  unsigned char NextByte;
  static unsigned int CodeMasks[] = 
  {
    0x0000, 0x0001, 0x0003, 0x0007,
    0x000f, 0x001f, 0x003f, 0x007f,
    0x00ff, 0x01ff, 0x03ff, 0x07ff,
    0x0fff
  };

  while (igif->CrntShiftState < igif->RunningBits) 
  {
    /* Needs to get more bytes from input stream for next code: */
    if (iGIFBufferedInput(handle, igif->Buf, &NextByte) != IM_ERR_NONE) 
      return IM_ERR_ACCESS;

    igif->CrntShiftDWord |= ((unsigned int) NextByte) << igif->CrntShiftState;
    igif->CrntShiftState += 8;
  }

  *Code = igif->CrntShiftDWord & CodeMasks[igif->RunningBits];

  igif->CrntShiftDWord >>= igif->RunningBits;
  igif->CrntShiftState -= igif->RunningBits;

  /* If code cannt fit into RunningBits bits, must raise its size. Note */
  /* however that codes above 4095 are used for special signaling.      */
  if (++(igif->RunningCode) > igif->MaxCode1 && igif->RunningBits < GIF_LZ_BITS) 
  {
    igif->MaxCode1 <<= 1;
    igif->RunningBits++;
  }

  return IM_ERR_NONE;
}

/******************************************************************************
* Routine to trace the Prefixes linked list until we get a prefix which is    *
* not code, but a pixel value (less than ClearCode). Returns that pixel value.*
* If image is defective, we might loop here forever, so we limit the loops to *
* the maximum possible if image O.k. - LZ_MAX_CODE times.		      *
******************************************************************************/
static int iGIFGetPrefixChar(unsigned int *Prefix, int Code, int ClearCode)
{
  int i = 0;

  while (Code > ClearCode && i++ <= GIF_LZ_MAX_CODE) 
    Code = Prefix[Code];

  return Code;
}

static int iGIFDecompressLine(iGIFData* igif, imBinFile* handle, unsigned char *Line,	int LineLen)
{
  int i = 0, j, CrntCode, EOFCode, ClearCode, CrntPrefix, LastCode, StackPtr;
  unsigned char *Stack, *Suffix;
  unsigned int *Prefix;

  StackPtr = igif->StackPtr;
  Prefix = igif->Prefix;
  Suffix = igif->Suffix;
  Stack = igif->Stack;
  EOFCode = igif->EOFCode;
  ClearCode = igif->ClearCode;
  LastCode = igif->LastCode;

  if (StackPtr != 0) 
  {
    /* Let pop the stack off before continueing to read the gif file: */
    while (StackPtr != 0 && i < LineLen) 
      Line[i++] = Stack[--StackPtr];
  }

  while (i < LineLen) 
  {			    
    /* Decode LineLen items. */
    if (iGIFDecompressInput(igif, handle, &CrntCode))
      return IM_ERR_ACCESS;

    if (CrntCode == EOFCode) 
    {
      /* Note however that usually we will not be here as we will stop */
      /* decoding as soon as we got all the pixel, or EOF code will    */
      /* not be read at all, and DGifGetLine/Pixel clean everything.   */
      if (i != LineLen - 1) 
        return IM_ERR_ACCESS;
  
    i++;
    }
    else if (CrntCode == ClearCode) 
    {
      /* We need to start over again: */
      for (j = 0; j <= GIF_LZ_MAX_CODE; j++) 
        Prefix[j] = GIF_NO_SUCH_CODE;

      igif->RunningCode = igif->EOFCode + 1;
      igif->RunningBits = igif->BitsPerPixel + 1;
      igif->MaxCode1 = 1 << igif->RunningBits;
      LastCode = igif->LastCode = GIF_NO_SUCH_CODE;
    }
    else 
    {
      /* Its regular code - if in pixel range simply add it to output  */
      /* stream, otherwise trace to codes linked list until the prefix */
      /* is in pixel range:					     */
      if (CrntCode < ClearCode) 
      {
        /* This is simple - its pixel scalar, so add it to output:   */
        Line[i++] = (unsigned char)CrntCode;
      }
      else 
      {
        /* Its a code to needed to be traced: trace the linked list  */
        /* until the prefix is a pixel, while pushing the suffix     */
        /* pixels on our stack. If we done, pop the stack in reverse */
        /* (thats what stack is good for!) order to output.	     */
        if (Prefix[CrntCode] == GIF_NO_SUCH_CODE) 
        {
          /* Only allowed if CrntCode is exactly the running code: */
          /* In that case CrntCode = XXXCode, CrntCode or the	     */
          /* prefix code is last code and the suffix char is	     */
          /* exactly the prefix of last code!			     */
          if (CrntCode == igif->RunningCode - 2) 
          {
            CrntPrefix = LastCode;
            Suffix[igif->RunningCode - 2] =
            Stack[StackPtr++] = (unsigned char)iGIFGetPrefixChar(Prefix, LastCode, ClearCode);
          }
          else 
            return IM_ERR_ACCESS;
        }
        else
          CrntPrefix = CrntCode;

        /* Now (if image is O.K.) we should not get an NO_SUCH_CODE  */
        /* During the trace. As we might loop forever, in case of    */
        /* defective image, we count the number of loops we trace    */
        /* and stop if we got LZ_MAX_CODE. obviously we can not      */
        /* loop more than that.					     */
        j = 0;
        while (j++ <= GIF_LZ_MAX_CODE && CrntPrefix > ClearCode && CrntPrefix <= GIF_LZ_MAX_CODE) 
        {
          Stack[StackPtr++] = Suffix[CrntPrefix];
          CrntPrefix = Prefix[CrntPrefix];
        }

        if (j >= GIF_LZ_MAX_CODE || CrntPrefix > GIF_LZ_MAX_CODE) 
            return IM_ERR_ACCESS;

        /* Push the last character on stack: */
        Stack[StackPtr++] = (unsigned char)CrntPrefix;

        /* Now lets pop all the stack into output: */
        while (StackPtr != 0 && i < LineLen)
          Line[i++] = Stack[--StackPtr];
      }

      if (LastCode != GIF_NO_SUCH_CODE) 
      {
        Prefix[igif->RunningCode - 2] = LastCode;

        if (CrntCode == igif->RunningCode - 2) 
        {
          /* Only allowed if CrntCode is exactly the running code: */
          /* In that case CrntCode = XXXCode, CrntCode or the	     */
          /* prefix code is last code and the suffix char is	     */
          /* exactly the prefix of last code!			     */
          Suffix[igif->RunningCode - 2] = (unsigned char)iGIFGetPrefixChar(Prefix, LastCode, ClearCode);
        }
        else 
        {
          Suffix[igif->RunningCode - 2] = (unsigned char)iGIFGetPrefixChar(Prefix, CrntCode, ClearCode);
        }
      }
    
      LastCode = CrntCode;
    }
  }

  igif->LastCode = LastCode;
  igif->StackPtr = StackPtr;

  return IM_ERR_NONE;
}

/*******************************************
*   Skip sub-blocks until terminator found *
********************************************/
static int iGIFSkipSubBlocks(imBinFile* handle)
{
  unsigned char byte_value;
  do
  {
    /* reads the number of bytes of the block or the terminator */
    imBinFileRead(handle, &byte_value, 1, 1);

    if (imBinFileError(handle))
      return IM_ERR_ACCESS;

    /* jump number of bytes, ignores the contents */
    if (byte_value) imBinFileSeekOffset(handle, byte_value);
  }while (byte_value != 0);

  return IM_ERR_NONE;
}

static int iGIFSkipImage(imBinFile* handle, int *image_count, int *terminate)
{
  int found_image = 0;
  unsigned char byte_value;

  *terminate = 0;
  do
  {
    /* reads the record type byte */
    byte_value = 0;
    imBinFileRead(handle, &byte_value, 1, 1);

    switch (byte_value) 
    {
	  case ',': /* image description */
      /* jump 8 bytes */
      imBinFileSeekOffset(handle, 8);

      /* reads the image information byte */
      imBinFileRead(handle, &byte_value, 1, 1);

      if (byte_value & 0x80)
      {
        int bpp = (byte_value & 0x07) + 1;
        int num_colors = 1 << bpp;

        /* skip the color table */
        imBinFileSeekOffset(handle, 3*num_colors);
      }

      /* jump 1 byte (LZW Min Code) */
      imBinFileSeekOffset(handle, 1);

      if (imBinFileError(handle))
        return IM_ERR_ACCESS;

      /* skip sub blocks */
      if (iGIFSkipSubBlocks(handle) != IM_ERR_NONE)
        return IM_ERR_ACCESS;

      /* one more image */
      found_image = 1;
      (*image_count)++;
	    break;
	  case '!': /* extension */
      /* jump 1 byte (label) */
      imBinFileSeekOffset(handle, 1);

      /* skip sub blocks */
      if (iGIFSkipSubBlocks(handle) != IM_ERR_NONE)
        return IM_ERR_ACCESS;
	    break;
	  case ';': /* terminate */
	  default:  /* probably EOF */
      *terminate = 1;
      break;
	  }

  } while (!(*terminate) && (!found_image));

  if (!found_image && *image_count == 0)
    return IM_ERR_FORMAT;

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  return IM_ERR_NONE;
}

static void iGIFReadGraphicsControl(imBinFile* handle, imAttribTable* attrib_table)
{
  unsigned char byte_value;
  unsigned short word_value;

  /* jump 1 bytes (size) */
  imBinFileSeekOffset(handle, 1);

  /* reads the packed descrition */
  imBinFileRead(handle, &byte_value, 1, 1);
  if (imBinFileError(handle))
    return;

  /* user input */
  if (byte_value & 0x02)
    attrib_table->Set("UserInput", IM_BYTE, 1, "\x1");

  /* disposal */
  if (byte_value & 0x1C)
  {
    const char* disposal;
    int disp = (byte_value & 0x1C) >> 2;

    switch (disp)
    {
    default:
      disposal = "UNDEF";
      break;
    case 0x01:
      disposal = "LEAVE";
      break;
    case 0x02:
      disposal = "RBACK";
      break;
    case 0x04:
      disposal = "RPREV";
      break;
    }

    attrib_table->Set("Disposal", IM_BYTE, -1, disposal);
  }

  /* delay time */
  imBinFileRead(handle, &word_value, 1, 2);
  if (word_value)
    attrib_table->Set("Delay", IM_USHORT, 1, &word_value);

  /* transparency index */
  if (byte_value & 0x01)
  {
    imBinFileRead(handle, &byte_value, 1, 1);
    attrib_table->Set("TransparencyIndex", IM_BYTE, 1, &byte_value);
  }
  else
    imBinFileSeekOffset(handle, 1);

  /* jump 1 bytes (terminator) */
  imBinFileSeekOffset(handle, 1);
}

static int iGIFReadApplication(imBinFile* handle, imAttribTable* attrib_table)
{
  char identifier[9];

  /* jump 1 byte (size) */
  imBinFileSeekOffset(handle, 1);

  /* reads the application identifier */
  imBinFileRead(handle, identifier, 8, 1);
  if (identifier[7] != 0)
    identifier[8] = 0;

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  if (imStrEqual(identifier, "NETSCAPE"))
  {
    unsigned char authentication[4];
    /* reads the application authentication code */
    imBinFileRead(handle, authentication, 3, 1);
    authentication[3] = 0;

    if (imBinFileError(handle))
      return IM_ERR_ACCESS;

    if (strcmp((char*)authentication, "2.0") == 0)
    {
      unsigned short word_value;

      /* jump 2 bytes (size + 1) */
      imBinFileSeekOffset(handle, 2);

      /* reads the number of iterations */
      imBinFileRead(handle, &word_value, 1, 2);

      attrib_table->Set("Iterations", IM_USHORT, 1, &word_value);

      /* jump 1 byte (terminator) */
      imBinFileSeekOffset(handle, 1);

      if (imBinFileError(handle))
        return IM_ERR_ACCESS;
    }
    else
    {
      /* Skip remaining blocks */
      if (iGIFSkipSubBlocks(handle) != IM_ERR_NONE)
        return IM_ERR_ACCESS;
    }
  }
  else
  {
    /* jump 3 bytes (authentication code) */
    imBinFileSeekOffset(handle, 3);

    /* Skip remaining blocks */
    if (iGIFSkipSubBlocks(handle) != IM_ERR_NONE)
      return IM_ERR_ACCESS;
  }

  return IM_ERR_NONE;
}

static int iGIFReadComment(imBinFile* handle, imAttribTable* attrib_table)
{
  unsigned char byte_value, buffer[255*100] = "", *buffer_ptr;
  int size = 0;

  buffer_ptr = &buffer[0];

  do
  {
    /* reads the number of bytes of the block or the terminator */
    imBinFileRead(handle, &byte_value, 1, 1);

    if (imBinFileError(handle))
      return IM_ERR_ACCESS;

    /* reads data */
    if (byte_value)
    {
      imBinFileRead(handle, buffer_ptr, byte_value, 1);

      if (buffer_ptr[byte_value-1] == 0)
      {
        size += byte_value-1;
        buffer_ptr += byte_value-1;
      }
      else
      {
        size += byte_value;
        buffer_ptr += byte_value;
      }
    }

  }while (byte_value != 0);

  if (buffer[0] != 0)
    attrib_table->Set("Description", IM_BYTE, size, buffer);

  return IM_ERR_NONE;
}

static int iGIFReadExtension(imBinFile* handle, imAttribTable* attrib_table)
{
  unsigned char byte_value;

  /* read block label */
  imBinFileRead(handle, &byte_value, 1, 1);

  if (byte_value == 0xF9)
  {
    /* Graphics Control Extension */
    iGIFReadGraphicsControl(handle, attrib_table);
  }
  else if (byte_value == 0xFE)
  {
    /* Comment Extension */
    if (iGIFReadComment(handle, attrib_table) != IM_ERR_NONE)
      return IM_ERR_ACCESS;
  }
  else if (byte_value == 0xFF)
  {
    /* Application Extension */
    if (iGIFReadApplication(handle, attrib_table) != IM_ERR_NONE)
      return IM_ERR_ACCESS;
  }
  else
  {
    /* skip sub blocks */
    if (iGIFSkipSubBlocks(handle) != IM_ERR_NONE)
      return IM_ERR_ACCESS;
  }

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  return IM_ERR_NONE;
}

static int iGIFWriteComment(imBinFile* handle, unsigned char *buffer, int size)
{
  unsigned char byte_value;

  /* record type byte */
  imBinFileWrite(handle, (void*)"!", 1, 1);

  /* block label */
  imBinFileWrite(handle, (void*)"\xFE", 1, 1);

  while (size > 0)
  {
    if (size > 255)
      byte_value = 255;
    else
      byte_value = (unsigned char)size;

    /* sub block size */
    imBinFileWrite(handle, &byte_value, 1, 1);

    /* sub block data */
    imBinFileWrite(handle, buffer, byte_value, 1);

    buffer += byte_value;
    size -= byte_value;

    if (imBinFileError(handle))
      return IM_ERR_ACCESS;
  }

  /* block terminator */
  imBinFileWrite(handle, (void*)"\0", 1, 1);

  return IM_ERR_NONE;
}

static int iGIFWriteGraphicsControl(imBinFile* handle, imAttribTable* attrib_table)
{
  const void *attrib_user_input, *attrib_disposal, *attrib_delay, *attrib_transparency;
  unsigned char byte_value;

  attrib_user_input = attrib_table->Get("UserInput");
  attrib_disposal = attrib_table->Get("Disposal");
  attrib_delay = attrib_table->Get("Delay");
  attrib_transparency = attrib_table->Get("TransparencyIndex");

  /* Writes the Graphics Control Extension */
  if (attrib_user_input || attrib_disposal || attrib_delay || attrib_transparency)
  {
    unsigned short word_value;

    /* record type byte */
    imBinFileWrite(handle, (void*)"!", 1, 1);

    /* block label */
    imBinFileWrite(handle, (void*)"\xF9", 1, 1);

    /* block size */
    imBinFileWrite(handle, (void*)"\x04", 1, 1);

    byte_value = 0;

    /* user input flag */
    if (attrib_user_input && *(unsigned char*)attrib_user_input == 1)
      byte_value |= 0x02;

    /* transparency flag */
    if (attrib_transparency)
      byte_value |= 0x01;

    /* disposal flag */
    if (attrib_disposal)
    {
      int disp = 0;
      if (imStrEqual((char*)attrib_disposal, "LEAVE"))
        disp = 0x01;
      else if (imStrEqual((char*)attrib_disposal, "RBACK"))
        disp = 0x02;
      else if (imStrEqual((char*)attrib_disposal, "RPREV"))
        disp = 0x04;

      disp = disp << 2;
      byte_value |= disp;
    }

    /* packed */
    imBinFileWrite(handle, &byte_value, 1, 1);

    /* delay time */
    if (attrib_delay)
      word_value = *(unsigned short*)attrib_delay;
    else
      word_value = 0;

    imBinFileWrite(handle, &word_value, 1, 2);

    /* transparency index */
    if (attrib_transparency)
    {
      byte_value = *(unsigned char*)attrib_transparency;
      imBinFileWrite(handle, &byte_value, 1, 1);
    }
    else
      imBinFileWrite(handle, (void*)"\0", 1, 1);

    /* terminator */
    imBinFileWrite(handle, (void*)"\0", 1, 1);

    if (imBinFileError(handle))
      return IM_ERR_ACCESS;
  }

  return IM_ERR_NONE;
}

static const char* iGIFCompTable[1] = 
{
  "LZW"
};

class imFileFormatGIF: public imFileFormatBase
{
  imBinFile* handle;
  iGIFData gif_data;

  int GIFReadImageInfo();
  int GIFWriteImageInfo();

public:
  imFileFormatGIF(const imFormat* _iformat): imFileFormatBase(_iformat) {}
  ~imFileFormatGIF() {}

  int Open(const char* file_name);
  int New(const char* file_name);
  void Close();
  void* Handle(int index);
  int ReadImageInfo(int index);
  int ReadImageData(void* data);
  int WriteImageInfo();
  int WriteImageData(void* data);
};

class imFormatGIF: public imFormat
{
public:
  imFormatGIF()
    :imFormat("GIF", 
              "Graphics Interchange Format", 
              "*.gif;", 
              iGIFCompTable, 
              1, 
              1)
    {}
  ~imFormatGIF() {}

  imFileFormatBase* Create(void) const { return new imFileFormatGIF(this); }
  int CanWrite(const char* compression, int color_mode, int data_type) const;
};

void imFormatRegisterGIF(void)
{
  imFormatRegister(new imFormatGIF());
}

int imFileFormatGIF::Open(const char* file_name)
{
  this->handle = imBinFileOpen(file_name);
  if (this->handle == NULL)
    return IM_ERR_OPEN;

  imBinFileByteOrder(handle, IM_LITTLEENDIAN); 

  unsigned char sig[4];
  if (!imBinFileRead(this->handle, sig, 3, 1))
  {
    imBinFileClose(handle);
    return IM_ERR_ACCESS;
  }

  sig[3] = 0;
  if (!imStrEqual((char*)sig, GIF_STAMP))
  {
    imBinFileClose(handle);
    return IM_ERR_FORMAT;
  }

  /* ignore version */
  imBinFileSeekOffset(handle, 3);

  strcpy(this->compression, "LZW");

  /* reads screen width and screen height */
  imushort word_value;
  imBinFileRead(handle, &word_value, 1, 2);
  gif_data.screen_width = word_value;

  imBinFileRead(handle, &word_value, 1, 2);
  gif_data.screen_height = word_value;

  /* reads color table information byte */
  imbyte byte_value;
  imBinFileRead(handle, &byte_value, 1, 1);

  /* jump 2 bytes (bgcolor + aspect ratio) */
  imBinFileSeekOffset(handle, 2);

  /* global color table, if exists */
  if (byte_value & 0x80)
  {
    int bpp = (byte_value & 0x07) + 1;
    gif_data.global_num_colors = 1 << bpp;

    /* reads the color palette */
    imBinFileRead(handle, gif_data.global_colors, gif_data.global_num_colors * 3, 1);
  }

  if (imBinFileError(handle))
  {
    imBinFileClose(handle);
    return IM_ERR_ACCESS;
  }

  /* count number of images */
  int error, terminate;
  this->image_count = 0;
  do
  {
    // store each offset before counting images
    gif_data.start_offset[this->image_count] = imBinFileTell(handle);
    error = iGIFSkipImage(handle, &this->image_count, &terminate);
  } while (!terminate && error == IM_ERR_NONE);

  if (this->image_count == 0 || error != IM_ERR_NONE)
  {
    imBinFileClose(handle);
    return error;
  }

  return IM_ERR_NONE;
}

int imFileFormatGIF::New(const char* file_name)
{
  this->handle = imBinFileNew(file_name);
  if (this->handle == NULL)
    return IM_ERR_OPEN;

  imBinFileByteOrder(handle, IM_LITTLEENDIAN); 

  /* writes the GIF STAMP and version - header */
  imBinFileWrite(handle, (void*)GIF_STAMP, 3, 1);   /* identifier */
  imBinFileWrite(handle, (void*)GIF_VERSION, 3, 1); /* format version */

  // File header will be written at the first image

  /* tests if everything was ok */
  if (imBinFileError(handle))
  {
    imBinFileClose(this->handle);
    return IM_ERR_ACCESS;
  }

  strcpy(this->compression, "LZW");
  
  return IM_ERR_NONE;
}

void imFileFormatGIF::Close()
{
  if (this->is_new && !imBinFileError(this->handle))
    imBinFileWrite(this->handle, (void*)";", 1, 1);

  imBinFileClose(this->handle);
}

void* imFileFormatGIF::Handle(int index)
{
  if (index == 0)
    return (void*)this->handle;
  else
    return NULL;
}

int imFileFormatGIF::GIFReadImageInfo()
{
  imbyte byte_value;
  imushort word_value;
  int int_value;

  imAttribTable* attrib_table = AttribTable();

  /* reads the image left position */
  imBinFileRead(handle, &word_value, 1, 2);
  if (word_value)
    attrib_table->Set("XScreen", IM_USHORT, 1, &word_value);

  /* reads the image top position */
  imBinFileRead(handle, &word_value, 1, 2);
  if (word_value)
    attrib_table->Set("YScreen", IM_USHORT, 1, &word_value);

  /* reads the image width */
  imBinFileRead(handle, &word_value, 1, 2);
  this->width = word_value;

  /* reads the image height */
  imBinFileRead(handle, &word_value, 1, 2);
  this->height = word_value;

  /* reads the image information byte */
  imBinFileRead(handle, &byte_value, 1, 1);

  gif_data.interlaced = (byte_value & 0x40)? 1: 0;
  if (gif_data.interlaced)
  {
    int_value = 1;
    attrib_table->Set("Interlaced", IM_INT, 1, &int_value);
  }

  this->file_color_mode = IM_MAP;
  this->file_data_type = IM_BYTE;

  /* local color table */
  int num_colors;
  unsigned char *colors;
  unsigned char local_colors[256 * 3];

  if (byte_value & 0x80)
  {
    int bpp = (byte_value & 0x07) + 1;
    num_colors = 1 << bpp;
    colors = local_colors;

    /* reads the color table */
    imBinFileRead(handle, local_colors, num_colors * 3, 1);
  }
  else if (gif_data.global_num_colors)
  {
    colors = gif_data.global_colors;
    num_colors = gif_data.global_num_colors;
  }
  else
    return IM_ERR_FORMAT;

  long palette[256];
  for (int c = 0; c < num_colors; c++)
  {
    palette[c] = imColorEncode(colors[c*3],
                               colors[c*3+1],
                               colors[c*3+2]);
  }

  imFileSetPalette(this, palette, num_colors);

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  this->file_color_mode |= IM_TOPDOWN;

  return IM_ERR_NONE;
}

int imFileFormatGIF::GIFWriteImageInfo()
{
  this->file_data_type = IM_BYTE;
  this->file_color_mode = imColorModeSpace(this->user_color_mode);
  this->file_color_mode |= IM_TOPDOWN;

  imAttribTable* attrib_table = AttribTable();
  const void* attrib = attrib_table->Get("Interlaced");
  if (attrib)
    gif_data.interlaced = *(int*)attrib;

  imBinFileWrite(handle, (void*)",", 1, 1);  /* Image separator character. */

  imushort word_value;

  attrib = attrib_table->Get("XScreen");
  if (attrib)
    word_value = *(unsigned short*)attrib;
  else
    word_value = 0;
  imBinFileWrite(handle, &word_value, 1, 2); /* image left */

  attrib = attrib_table->Get("YScreen");
  if (attrib)
    word_value = *(unsigned short*)attrib;
  else
    word_value = 0;
  imBinFileWrite(handle, &word_value, 1, 2); /* image top */

  word_value = (unsigned short)this->width;
  imBinFileWrite(handle, &word_value, 1, 2); /* image width */
  word_value = (unsigned short)this->height;
  imBinFileWrite(handle, &word_value, 1, 2); /* image height */

  /* local color table */
  imbyte byte_value = 0x80;
  if (gif_data.interlaced)
    byte_value |= 0x40;

  int num_colors = 256;
  if (imColorModeSpace(this->user_color_mode) == IM_MAP)
  {
    int bpp = 0, c = this->palette_count-1;
    while (c) {c = c >> 1;bpp++;} 
    byte_value |= (bpp-1); 
    num_colors = 1 << bpp;
  }
  else
    byte_value |= 0x07; /* 8 bits = 256 grays */

  imBinFileWrite(handle, &byte_value, 1, 1); /* image information */

  /* write color table */
  unsigned char local_colors[256*3];
  for (int c = 0; c < num_colors; c++) // write all data, even not used colors
  {
    unsigned char r, g, b;
    imColorDecode(&r, &g, &b, this->palette[c]);
    local_colors[c*3] = r;
    local_colors[c*3+1] = g;
    local_colors[c*3+2] = b;
  }
  imBinFileWrite(handle, local_colors, num_colors*3, 1);

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  return IM_ERR_NONE;
}

int imFileFormatGIF::ReadImageInfo(int index)
{
  imAttribTable* attrib_table = AttribTable();

  /* must clear the attribute list, because it can have multiple images and 
     has many attributes that may exists only for specific images. */
  attrib_table->RemoveAll();
  imFileSetBaseAttributes(this);

  if (gif_data.screen_width) 
  {
    imushort word_value = (imushort)gif_data.screen_width;
    attrib_table->Set("ScreenWidth", IM_USHORT, 1, &word_value);
  }

  if (gif_data.screen_height) 
  {
    imushort word_value = (imushort)gif_data.screen_height;
    attrib_table->Set("ScreenHeight", IM_USHORT, 1, &word_value);
  }

  /* jump to start offset of the image */
  imBinFileSeekTo(handle, gif_data.start_offset[index]);

  int found_image = 0;
  imbyte byte_value;

  int terminate = 0;
  do
  {
    /* reads the record type byte */
    byte_value = 0;
    imBinFileRead(handle, &byte_value, 1, 1);

    switch (byte_value) 
    {
	  case '!': /* 0x21 extension (appears before the image) */
      if (iGIFReadExtension(handle, attrib_table) != IM_ERR_NONE)
        return IM_ERR_ACCESS;
	    break;
	  case ',': /* 0x2C image description and color table */
      if (GIFReadImageInfo() != IM_ERR_NONE)
        return IM_ERR_ACCESS;

      /* we will read only this image for now, so break the loop */
      found_image = 1;
	    break;
	  case ';': /* if terminate before find image return error */
	  default:
      terminate = 1;
      break;
	  }
  } while (!terminate && !found_image);

  if (!found_image)
    return IM_ERR_ACCESS;

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  /* reads the LZW Min code byte */
  imBinFileRead(handle, &byte_value, 1, 1);

  /* now initialize the compression control data */

  gif_data.BitsPerPixel = byte_value;
  gif_data.ClearCode = (1 << byte_value);
  gif_data.EOFCode = gif_data.ClearCode + 1;
  gif_data.RunningCode = gif_data.EOFCode + 1;
  gif_data.RunningBits = byte_value + 1;	 /* Number of bits per code. */
  gif_data.MaxCode1 = 1 << gif_data.RunningBits;     /* Max. code + 1. */
  gif_data.StackPtr = 0;		    /* No pixels on the pixel stack. */
  gif_data.LastCode = GIF_NO_SUCH_CODE;
  gif_data.CrntShiftState = 0;	/* No information in CrntShiftDWord. */
  gif_data.CrntShiftDWord = 0;
  gif_data.Buf[0] = 0;			      /* Input Buffer empty. */

  for (int i = 0; i <= GIF_LZ_MAX_CODE; i++) 
    gif_data.Prefix[i] = GIF_NO_SUCH_CODE;

  gif_data.step = 0;

  return IM_ERR_NONE;
}

int imFileFormatGIF::WriteImageInfo()
{
  this->file_color_mode = imColorModeSpace(this->user_color_mode);
  this->file_color_mode |= IM_TOPDOWN;
  this->file_data_type = this->user_data_type;

  imAttribTable* attrib_table = AttribTable();
  const void* attrib_data;
  int attrib_size;

  if (this->image_count == 0)
  {
    imushort word_value;

    // write file header

    /* logical screen descriptor */
    attrib_data = attrib_table->Get("ScreenWidth");
    if (attrib_data) word_value = *(imushort*)attrib_data;
    else             word_value = (imushort)this->width;
    imBinFileWrite(handle, &word_value, 1, 2);

    attrib_data = attrib_table->Get("ScreenHeight");
    if (attrib_data) word_value = *(imushort*)attrib_data;
    else             word_value = (imushort)this->height;
    imBinFileWrite(handle, &word_value, 1, 2);

    imbyte byte_value = 0;  /* no global color table, 0 colors */
    imBinFileWrite(handle, &byte_value, 1, 1); /* screen information */
    imBinFileWrite(handle, (void*)"\0\0", 2, 1);  /* (bgcolor + aspect ratio) */
  }

  attrib_data = attrib_table->Get("Description", NULL, &attrib_size);
  if (attrib_data)
  {
    if (iGIFWriteComment(handle, (imbyte*)attrib_data, attrib_size) != IM_ERR_NONE)
      return IM_ERR_ACCESS;
  }

  attrib_data = attrib_table->Get("Iterations");
  if (attrib_data)
  {
    if (iGIFWriteNetscapeApplication(handle, *(short*)attrib_data) != IM_ERR_NONE)
      return IM_ERR_ACCESS;
  }

  if (iGIFWriteGraphicsControl(handle, attrib_table) != IM_ERR_NONE)
    return IM_ERR_ACCESS;

  if (GIFWriteImageInfo() != IM_ERR_NONE)
    return IM_ERR_ACCESS;

  /* initializes the hash table */
  memset(gif_data.HTable, 0xFF, GIF_HT_SIZE * sizeof(int));

  /* initializes compression data */

  imbyte byte_value = 8;
  imBinFileWrite(handle, &byte_value, 1, 1); /* Write the Code size to file. */

  gif_data.Buf[0] = 0;			  /* Nothing was output yet. */
  gif_data.BitsPerPixel = 8;
  gif_data.ClearCode = (1 << 8);
  gif_data.EOFCode = gif_data.ClearCode + 1;
  gif_data.RunningBits = 8 + 1;	 /* Number of bits per code. */
  gif_data.MaxCode1 = 1 << gif_data.RunningBits;	   /* Max. code + 1. */
  gif_data.CrntCode = GIF_FIRST_CODE;	   /* Signal that this is first one! */
  gif_data.CrntShiftState = 0;      /* No information in CrntShiftDWord. */
  gif_data.CrntShiftDWord = 0;

  gif_data.RunningCode = gif_data.EOFCode + 1;

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  gif_data.step = 0;          /* interlaced step */

  return iGIFCompressOutput(&gif_data, handle, gif_data.ClearCode);
}

int imFileFormatGIF::ReadImageData(void* data)
{
  imCounterTotal(this->counter, this->height, "Reading GIF...");

  int lin = 0, error;
  for (int i = 0; i < this->height; i++)
  {
    error = iGIFDecompressLine(&gif_data, handle, (imbyte*)this->line_buffer, this->width);
    if (error != IM_ERR_NONE)
      return IM_ERR_ACCESS;

    imFileLineBufferRead(this, data, lin, 0);

    if (!imCounterInc(this->counter))
      return IM_ERR_COUNTER;

	  if (gif_data.interlaced)
	  {
      lin += InterlacedJumps[gif_data.step];

      if (lin > this->height-1)
      {
        gif_data.step++;
        lin = InterlacedOffset[gif_data.step];
      }
	  }
	  else
      lin++;
  }

  /* Skip remaining empty blocks of the image data */
  if (iGIFSkipSubBlocks(handle) != IM_ERR_NONE)
    return IM_ERR_ACCESS;

  return IM_ERR_NONE;
}

int imFileFormatGIF::WriteImageData(void* data)
{
  imCounterTotal(this->counter, this->height, "Writing GIF...");

  int lin = 0, error;
  for (int i = 0; i < this->height; i++)
  {
    imFileLineBufferWrite(this, data, lin, 0);

    error = iGIFCompressLine(&gif_data, handle, (imbyte*)this->line_buffer, this->width);

    if (error != IM_ERR_NONE)
      return IM_ERR_ACCESS;

    if (!imCounterInc(this->counter))
      return IM_ERR_COUNTER;

	  if (gif_data.interlaced)
	  {
      lin += InterlacedJumps[gif_data.step];

      if (lin > this->height-1)
      {
        gif_data.step++;
        lin = InterlacedOffset[gif_data.step];
      }
	  }
	  else
      lin++;
  }

  /* writes the end picture code */
  iGIFCompressOutput(&gif_data, handle, gif_data.CrntCode);
  iGIFCompressOutput(&gif_data, handle, gif_data.EOFCode);
  iGIFCompressOutput(&gif_data, handle, GIF_FLUSH_OUTPUT);

  if (imBinFileError(handle))
    return IM_ERR_ACCESS;

  this->image_count++;
  return IM_ERR_NONE;
}

int imFormatGIF::CanWrite(const char* compression, int color_mode, int data_type) const
{
  int color_space = imColorModeSpace(color_mode);

  if (color_space != IM_MAP && color_space != IM_GRAY && color_space != IM_BINARY)
    return IM_ERR_DATA;                       
                                              
  if (data_type != IM_BYTE)
    return IM_ERR_DATA;

  if (!compression || compression[0] == 0)
    return IM_ERR_NONE;

  if (!imStrEqual(compression, "LZW"))
    return IM_ERR_COMPRESS;

  return IM_ERR_NONE;
}

