/* Hey, emacs, this is -*- C -*- code.  */

/* read-gif.c - read a GIF

Copyright (C) 1992 D P Gymer

This is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later
   version.

This program 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 General Public License for
   more details.  */

/* $Id: read-gif.c 0.3.2.0 1992/06/11 22:30:07 dpg Alpha dpg $ */

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

#include "view.h"

/* BUGS - is full of view_message calls which should just be ignored or
   failed.  */

/* Read a little-endian short.  Return 0 iff we succeed.  */
static int
read_little16 (unsigned short *val, FILE *fp)
{
  int low = fgetc (fp);
  int high = fgetc (fp);

  if (low == EOF || high == EOF)
    return 1;
  *val = low + ((high << 8) & 0xff00);
  return 0;
}

/* Read the signature.  Return 0 iff it's correct.  */
static int
read_signature (FILE *fp)
{
  char sig[7];

  if (fread (sig, 6, 1, fp) != 1)
    return 1;
  sig[6] = '\0';
  return strcmp ("GIF87a", sig) && strcmp ("GIF89a", sig);
}

/* Read a colormap.  We append it to the current map, and fill in an index
   table, so that local colormaps can be retained.  (Colors and _not_
   duplicated, hence the need for an index table rather than plain offset.)
   Return 0 iff we succeed.  */
static int
read_colormap (FILE *fp, int count, MAP *map, PALETTE **palette_index, int *colors)
{
  PALETTE **palp = &map->palette;

  /* Now actually read it.  */
  while (count--)
    {
      PALETTE *this;
      unsigned char rgb[3];
      struct color palval;

      if (fread (rgb, 3, 1, fp) != 1)
	return 1;
      palval.red = rgb[0] * MAXRGBVAL / 255;
      palval.green = rgb[1] * MAXRGBVAL / 255;
      palval.blue = rgb[2] * MAXRGBVAL / 255;

      /* See if we've already got the color.  */
      this = *palp;
      while (this)
	{
	  if (COLOREQ (palval, this->color))
	    break;
	  this = this->next;
	}

      /* Didn't already have it, so we create it.  It happens to go at the
         start of the palette list.  */
      if (!this)
	{
	  if (*colors > MAXCOLOR)
	    return 1;
	  this = alloc_palette ();
	  this->number = (*colors)++;
	  this->count = 0;
	  this->color = palval;
	  this->next = *palp;
	  *palp = this;
	}

      *palette_index++ = this;
    }

  return 0;
}

/* These routines were hacked out of giftoppm in the pbmplus package. The
   following copyright notice is taken from giftoppm.c.  */

/* +-------------------------------------------------------------------+ */
/* | Copyright 1990, David Koblas.                                     | */
/* |   Permission to use, copy, modify, and distribute this software   | */
/* |   and its documentation for any purpose and without fee is hereby | */
/* |   granted, provided that the above copyright notice appear in all | */
/* |   copies and that both that copyright notice and this permission  | */
/* |   notice appear in supporting documentation.  This software is    | */
/* |   provided "as is" without express or implied warranty.           | */
/* +-------------------------------------------------------------------+ */

#define pm_error(a,b,c,d,e,f) ({view_message("GIF ERROR",a,b,c,d,e,f);exit(1);})
#define pm_message(a,b,c,d,e,f) view_message("GIF MESSAGE",a,b,c,d,e,f)
#define ReadOK(fp,buf,len) (fread(buf,len,1,fp)==1)
#define TRUE 1
#define FALSE 0

#define	MAX_LWZ_BITS   	12

static int ZeroDataBlock = 0;

static int
GetDataBlock (fd, buf)
     FILE *fd;
     unsigned char *buf;
{
  unsigned char count;

  if (!ReadOK (fd, &count, 1))
    {
      pm_message ("error in getting DataBlock size", 0, 0, 0, 0, 0);
      return -1;
    }

  ZeroDataBlock = count == 0;

  if ((count != 0) && (!ReadOK (fd, buf, count)))
    {
      pm_message ("error in reading DataBlock", 0, 0, 0, 0, 0);
      return -1;
    }

  return count;
}

static int
GetCode (fd, code_size, flag)
     FILE *fd;
     int code_size;
     int flag;
{
  static unsigned char buf[280];
  static int curbit, lastbit, done, last_byte;
  int i, j, ret;
  unsigned char count;

  if (flag)
    {
      curbit = 0;
      lastbit = 0;
      done = FALSE;
      return 0;
    }

  if ((curbit + code_size) >= lastbit)
    {
      if (done)
	{
	  if (curbit >= lastbit)
	    pm_error ("ran off the end of my bits", 0, 0, 0, 0, 0);
	  return -1;
	}
      buf[0] = buf[last_byte - 2];
      buf[1] = buf[last_byte - 1];

      if ((count = GetDataBlock (fd, &buf[2])) == 0)
	done = TRUE;

      last_byte = 2 + count;
      curbit = (curbit - lastbit) + 16;
      lastbit = (2 + count) * 8;
    }

  ret = 0;
  for (i = curbit, j = 0; j < code_size; ++i, ++j)
    ret |= ((buf[i / 8] & (1 << (i % 8))) != 0) << j;

  curbit += code_size;

  return ret;
}

static int
LWZReadByte (fd, flag, input_code_size)
     FILE *fd;
     int flag;
     int input_code_size;
{
  static int fresh = FALSE;
  int code, incode;
  static int code_size, set_code_size;
  static int max_code, max_code_size;
  static int firstcode, oldcode;
  static int clear_code, end_code;
  static int table[2][(1 << MAX_LWZ_BITS)];
  static int stack[(1 << (MAX_LWZ_BITS)) * 2], *sp;
  register int i;

  if (flag)
    {
      set_code_size = input_code_size;
      code_size = set_code_size + 1;
      clear_code = 1 << set_code_size;
      end_code = clear_code + 1;
      max_code_size = 2 * clear_code;
      max_code = clear_code + 2;

      GetCode (fd, 0, TRUE);

      fresh = TRUE;

      for (i = 0; i < clear_code; ++i)
	{
	  table[0][i] = 0;
	  table[1][i] = i;
	}
      for (; i < (1 << MAX_LWZ_BITS); ++i)
	table[0][i] = table[1][0] = 0;

      sp = stack;

      return 0;
    }
  else if (fresh)
    {
      fresh = FALSE;
      do
	{
	  firstcode = oldcode =
	    GetCode (fd, code_size, FALSE);
	}
      while (firstcode == clear_code);
      return firstcode;
    }

  if (sp > stack)
    return *--sp;

  while ((code = GetCode (fd, code_size, FALSE)) >= 0)
    {
      if (code == clear_code)
	{
	  for (i = 0; i < clear_code; ++i)
	    {
	      table[0][i] = 0;
	      table[1][i] = i;
	    }
	  for (; i < (1 << MAX_LWZ_BITS); ++i)
	    table[0][i] = table[1][i] = 0;
	  code_size = set_code_size + 1;
	  max_code_size = 2 * clear_code;
	  max_code = clear_code + 2;
	  sp = stack;
	  firstcode = oldcode =
	    GetCode (fd, code_size, FALSE);
	  return firstcode;
	}
      else if (code == end_code)
	{
	  int count;
	  unsigned char buf[260];

	  if (ZeroDataBlock)
	    return -2;

	  while ((count = GetDataBlock (fd, buf)) > 0)
	    ;

	  if (count != 0)
	    pm_message ("missing EOD in data stream (common occurance)",
			0, 0, 0, 0, 0);
	  return -2;
	}

      incode = code;

      if (code >= max_code)
	{
	  *sp++ = firstcode;
	  code = oldcode;
	}

      while (code >= clear_code)
	{
	  *sp++ = table[1][code];
	  if (code == table[0][code])
	    pm_error ("circular table entry BIG ERROR",
		      0, 0, 0, 0, 0);
	  code = table[0][code];
	}

      *sp++ = firstcode = table[1][code];

      if ((code = max_code) < (1 << MAX_LWZ_BITS))
	{
	  table[0][code] = oldcode;
	  table[1][code] = firstcode;
	  ++max_code;
	  if ((max_code >= max_code_size) &&
	      (max_code_size < (1 << MAX_LWZ_BITS)))
	    {
	      max_code_size *= 2;
	      ++code_size;
	    }
	}

      oldcode = incode;

      if (sp > stack)
	return *--sp;
    }
  return code;
}

static int
read_image (FILE *fp, MAP *map, PALETTE **palette_index, int *colorsp)
{
  unsigned short x;
  unsigned short y;
  unsigned short width;
  unsigned short height;
  int flags;
  int interlace;
  unsigned char c;
  int v;
  int xpos = 0, ypos = 0, pass = 0;

  if (read_little16 (&x, fp) || read_little16 (&y, fp)
      || read_little16 (&width, fp) || read_little16 (&height, fp)
      || (flags = fgetc (fp)) == EOF)
    return 1;
  interlace = flags & 0x40;

  /* Local colormap. */
  if (flags & 0x80)
    {
      int ncolors = 2 << (flags & 7);

      palette_index = alloca (ncolors * sizeof (PALETTE *));
      if (!palette_index)
	{
	  view_message (0, "alloca");
	  exit (1);
	}
      if (read_colormap (fp, ncolors, map, palette_index, colorsp))
	return 1;
    }
  else if (!palette_index)
    return 1;

  /* *  Initialize the Compression routines */
  if (fread (&c, 1, 1, fp) != 1)
    return 1;

  if (LWZReadByte (fp, 1, c) < 0)
    return 1;

  while ((v = LWZReadByte (fp, 0, c)) >= 0)
    {
      map->image[y + ypos][x + xpos] = palette_index[v]->number;
      ++palette_index[v]->count;

      ++xpos;
      if (xpos == width)
	{
	  if (last_sig)
	    return 1;
	  status_bar (ypos + 1, height);
	  xpos = 0;
	  if (interlace)
	    {
	      switch (pass)
		{
		case 0:
		case 1:
		  ypos += 8;
		  break;
		case 2:
		  ypos += 4;
		  break;
		case 3:
		  ypos += 2;
		  break;
		}

	      if (ypos >= height)
		{
		  ++pass;
		  switch (pass)
		    {
		    case 1:
		      ypos = 4;
		      break;
		    case 2:
		      ypos = 2;
		      break;
		    case 3:
		      ypos = 1;
		      break;
		    }
		}
	    }
	  else
	    ++ypos;
	}
    }

  return 0;
}

/* Try to read a GIF.  */
int
maybe_read_gif (FILE *fp, const char *file, MAP *map)
{
  unsigned short width;
  unsigned short height;
  int flags;
  int background;
  int aspect_ratio;
  int colors;
  PALETTE **palette_index;

  if (read_signature (fp) || read_little16 (&width, fp)
      || read_little16 (&height, fp) || (flags = fgetc (fp)) == EOF
  || (background = fgetc (fp)) == EOF || (aspect_ratio = fgetc (fp)) == EOF)
    return 1;
  alloc_image (map, width, height);

  if (aspect_ratio)
    view_message ("warning, non 1:1 aspect ratio", "`%s' (GIF?)", file);

  colors = 0;
  /* Global colormap. */
  if (flags & 0x80)
    {
      int ncolors = 2 << (flags & 7);

      palette_index = alloca (ncolors * sizeof (PALETTE *));
      if (!palette_index)
	{
	  view_message (0, "alloca");
	  exit (1);
	}
      /* BUG: should set the image to the background color here.  */
      if (read_colormap (fp, ncolors, map, palette_index, &colors))
	return 1;
    }
  else
    palette_index = 0;

  for (;;)
    {
      int sep = fgetc (fp);

      switch (sep)
	{
	case ',':
	  if (read_image (fp, map, palette_index, &colors))
	    return 1;
	  break;
	case '!':
	  view_message ("not supported yet", "extension block found in `%s'", file);
	  return 1;
	case ';':
	  return 0;
	default:
	  view_message ("bogus seperator; assuming termination", "0x%x in `%s'", sep, file);
	  return 0;
	}
    }

  return 0;
}
