/* input-tga.c:	reads tga files 
 
   Copyright (C) 1999, 2000, 2001 Martin Weber

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation; either version 2.1 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser 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. */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* Def: HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* #include <unistd.h> */

#include "bitmap.h"
#include "message.h"
#include "xstd.h"
#include "input-bmp.h"

/* TODO:
   - Handle loading images that aren't 8 bits per channel. 
*/

/* Round up a division to the nearest integer. */
#define ROUNDUP_DIVIDE(n,d) (((n) + (d - 1)) / (d))

#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))

#define INDEXED 1
#define INDEXEDA 2
#define GRAY 3
#define RGB 5
#define INDEXED_IMAGE 1
#define INDEXEDA_IMAGE 2
#define GRAY_IMAGE 3
#define GRAYA_IMAGE 4
#define RGB_IMAGE 5
#define RGBA_IMAGE 6

struct tga_header
{
  unsigned char idLength;
  unsigned char colorMapType;

  /* The image type. */
#define TGA_TYPE_MAPPED      1
#define TGA_TYPE_COLOR       2
#define TGA_TYPE_GRAY        3
#define TGA_TYPE_MAPPED_RLE  9
#define TGA_TYPE_COLOR_RLE  10
#define TGA_TYPE_GRAY_RLE   11
  unsigned char imageType;

  /* Color Map Specification. */
  /* We need to separately specify high and low bytes to avoid endianness
     and alignment problems. */
  unsigned char colorMapIndexLo, colorMapIndexHi;
  unsigned char colorMapLengthLo, colorMapLengthHi;
  unsigned char colorMapSize;

  /* Image Specification. */
  unsigned char xOriginLo, xOriginHi;
  unsigned char yOriginLo, yOriginHi;

  unsigned char widthLo, widthHi;
  unsigned char heightLo, heightHi;

  unsigned char bpp;

  /* Image descriptor.
     3-0: attribute bpp
     4:   left-to-right ordering
     5:   top-to-bottom ordering
     7-6: zero
     */
#define TGA_DESC_ABITS      0x0f
#define TGA_DESC_HORIZONTAL 0x10
#define TGA_DESC_VERTICAL   0x20
  unsigned char descriptor;
};

static struct
{
  unsigned int extensionAreaOffset;
  unsigned int developerDirectoryOffset;
#define TGA_SIGNATURE "TRUEVISION-XFILE"
  char signature[16];
  char dot;
  char null;
} tga_footer;


static at_bitmap_type ReadImage (FILE *fp,
				 struct tga_header *hdr,
				 at_exception_type * exp);
at_bitmap_type
input_tga_reader (at_string filename,
		  at_input_opts_type * opts,
		  at_msg_func msg_func, 
		  at_address msg_data)
{
  FILE *fp;
  struct tga_header hdr;

  at_bitmap_type image = at_bitmap_init(0, 0, 0, 1);
  at_exception_type exp = at_exception_new(msg_func, msg_data);

  fp = fopen (filename, "rb");
  if (!fp)
    {
      LOG1 ("TGA: can't open \"%s\"\n", filename);
      at_exception_fatal(&exp, "Cannot open input tga file");
    }

  /* Check the footer. */
  if (fseek (fp, 0L - (sizeof (tga_footer)), SEEK_END)
      || fread (&tga_footer, sizeof (tga_footer), 1, fp) != 1)
    {
      LOG1 ("TGA: Cannot read footer from \"%s\"\n", filename);
      at_exception_fatal(&exp, "TGA: Cannot read footer");
      goto cleanup;
    }

  /* Check the signature. */

  if (fseek (fp, 0, SEEK_SET) ||
      fread (&hdr, sizeof (hdr), 1, fp) != 1)
    {
      LOG1 ("TGA: Cannot read header from \"%s\"\n", filename);
      at_exception_fatal(&exp, "TGA: Cannot read header");
      goto cleanup;
    }

  /* Skip the image ID field. */
  if (hdr.idLength && fseek (fp, hdr.idLength, SEEK_CUR))
    {
      LOG1 ("TGA: Cannot skip ID field in \"%s\"\n", filename);
      at_exception_fatal(&exp, "TGA: Cannot skip ID field");
      goto cleanup;
    }

  image = ReadImage (fp, &hdr, &exp);
 cleanup:  
  fclose (fp);
  return image;
}


static int
std_fread (unsigned char *buf, 
           int     datasize, 
           int     nelems, 
           FILE   *fp)
{

  return fread (buf, datasize, nelems, fp);
}

#define RLE_PACKETSIZE 0x80

/* Decode a bufferful of file. */
static int
rle_fread (unsigned char *buf, 
           int     datasize, 
           int     nelems, 
           FILE   *fp)
{
  static unsigned char *statebuf = 0;
  static int statelen = 0;
  static int laststate = 0;

  int j, k;
  int buflen, count, bytes;
  unsigned char *p;

  /* Scale the buffer length. */
  buflen = nelems * datasize;

  j = 0;
  while (j < buflen)
    {
      if (laststate < statelen)
        {
          /* Copy bytes from our previously decoded buffer. */
          bytes = MIN (buflen - j, statelen - laststate);
          memcpy (buf + j, statebuf + laststate, bytes);
          j += bytes;
          laststate += bytes;

          /* If we used up all of our state bytes, then reset them. */
          if (laststate >= statelen)
            {
              laststate = 0;
              statelen = 0;
            }

          /* If we filled the buffer, then exit the loop. */
          if (j >= buflen)
            break;
        }

      /* Decode the next packet. */
      count = fgetc (fp);
      if (count == EOF)
        {
	    return j / datasize;
        }

      /* Scale the byte length to the size of the data. */
      bytes = ((count & ~RLE_PACKETSIZE) + 1) * datasize;

      if (j + bytes <= buflen)
        {
          /* We can copy directly into the image buffer. */
          p = buf + j;
        }
      else {
	  /* Allocate the state buffer if we haven't already. */
        if (!statebuf)
          statebuf = (unsigned char *) malloc (RLE_PACKETSIZE * datasize);
        p = statebuf;
      }

      if (count & RLE_PACKETSIZE)
        {
          /* Fill the buffer with the next value. */
          if (fread (p, datasize, 1, fp) != 1)
            {
		  				return j / datasize;
            }

          /* Optimized case for single-byte encoded data. */
          if (datasize == 1)
            memset (p + 1, *p, bytes - 1);
          else
            for (k = datasize; k < bytes; k += datasize)
              memcpy (p + k, p, datasize);
        }
      else
        {
          /* Read in the buffer. */
          if (fread (p, bytes, 1, fp) != 1)
            {
	       return j / datasize;
            }
        }

      /* We may need to copy bytes from the state buffer. */
      if (p == statebuf)
        statelen = bytes;
      else
        j += bytes;
    }

return nelems;
}

static at_bitmap_type
ReadImage (FILE              *fp, 
           struct tga_header *hdr,
	   at_exception_type * exp)
{
  at_bitmap_type image = at_bitmap_init(0, 0, 0, 1);
  unsigned char *buffer;
  unsigned char *alphas;

  int width, height, bpp, abpp, pbpp, nalphas;
  int j, k;
  int pelbytes, wbytes, bsize, npels, pels;
  int rle, badread;
  int itype, dtype;
  unsigned char *cmap = NULL;
  int (*myfread)(unsigned char *, int, int, FILE *);

  /* Find out whether the image is horizontally or vertically reversed. */
  char horzrev = (char) (hdr->descriptor & TGA_DESC_HORIZONTAL);
  char vertrev = (char) (!(hdr->descriptor & TGA_DESC_VERTICAL));

  image.bitmap = NULL;
  
  /* Reassemble the multi-byte values correctly, regardless of
     host endianness. */
  width = (hdr->widthHi << 8) | hdr->widthLo;
  height = (hdr->heightHi << 8) | hdr->heightLo;

  bpp = hdr->bpp;
  abpp = hdr->descriptor & TGA_DESC_ABITS;

  if (hdr->imageType == TGA_TYPE_COLOR ||
      hdr->imageType == TGA_TYPE_COLOR_RLE)
    pbpp = MIN (bpp / 3, 8) * 3;
  else if (abpp < bpp)
    pbpp = bpp - abpp;
  else
    pbpp = bpp;

  if (abpp + pbpp > bpp)
    {
      LOG3 ("TGA: %d bit image, %d bit alpha is greater than %d total bits per pixel\n",
	    pbpp, abpp, bpp);
      at_exception_warning (exp, "TGA: alpha bit is too great");

      /* Assume that alpha bits were set incorrectly. */
      abpp = bpp - pbpp;
      LOG1("TGA: reducing to %d bit alpha\n", abpp);
      at_exception_warning (exp, "TGA: alpha bit is reduced");
    }
  else if (abpp + pbpp < bpp)
    {
      LOG3 ("TGA: %d bit image, %d bit alpha is less than %d total bits per pixel\n",
	    pbpp, abpp, bpp);
      at_exception_warning(exp, "TGA: alpha bit is too little");

      /* Again, assume that alpha bits were set incorrectly. */
      abpp = bpp - pbpp;
      LOG1 ("TGA: increasing to %d bit alpha\n", abpp);
      at_exception_warning (exp, "TGA: alpha bit is increased");
    }

  rle = 0;
  switch (hdr->imageType)
    {
    case TGA_TYPE_MAPPED_RLE:
      rle = 1;
    case TGA_TYPE_MAPPED:
      itype = INDEXED;

      /* Find the size of palette elements. */
      pbpp = MIN (hdr->colorMapSize / 3, 8) * 3;
      if (pbpp < hdr->colorMapSize)
        abpp = hdr->colorMapSize - pbpp;
      else
        abpp = 0;


      if (bpp != 8)
	{ /* We can only cope with 8-bit indices. */
          LOG ("TGA: index sizes other than 8 bits are unimplemented\n");
	  at_exception_fatal(exp, "TGA: index sizes other than 8 bits are unimplemented");
	  return image;
	}

      if (abpp)
        dtype = INDEXEDA_IMAGE;
      else
        dtype = INDEXED_IMAGE;
      break;

    case TGA_TYPE_GRAY_RLE:
      rle = 1;
    case TGA_TYPE_GRAY:
      itype = GRAY;

      if (abpp)
        dtype = GRAYA_IMAGE;
      else
        dtype = GRAY_IMAGE;
      break;

    case TGA_TYPE_COLOR_RLE:
      rle = 1;
    case TGA_TYPE_COLOR:
      itype = RGB;

      if (abpp)
        dtype = RGBA_IMAGE;
      else
        dtype = RGB_IMAGE;
      break;

    default:
      {
	LOG1 ("TGA: unrecognized image type %d\n", hdr->imageType);
	at_exception_fatal (exp, "TGA: unrecognized image type");
	return image;
      }
    }

  if ((abpp && abpp != 8) ||
      ((itype == RGB || itype == INDEXED) && pbpp != 24) ||
      (itype == GRAY && pbpp != 8))
    {
      /* FIXME: We haven't implemented bit-packed fields yet. */
      LOG ("TGA: channel sizes other than 8 bits are unimplemented\n");
      at_exception_fatal (exp, 
			  "TGA: channel sizes other than 8 bits are unimplemented");
      return image;
    }

  /* Check that we have a color map only when we need it. */
  if (itype == INDEXED)
    {
      if (hdr->colorMapType != 1)
	{
	  LOG1 ("TGA: indexed image has invalid color map type %d\n",
		hdr->colorMapType);
	  at_exception_fatal (exp,
			      "TGA: indexed image has invalid color map type");
	  return image;
	}
    }
  else if (hdr->colorMapType != 0)
    {
      LOG1("TGA: non-indexed image has invalid color map type %d\n",
	   hdr->colorMapType);
      at_exception_fatal (exp, "TGA: non-indexed image has invalid color map type");
      return image;
    }

  alphas = 0;
  nalphas = 0;
  if (hdr->colorMapType == 1)
    {
      /* We need to read in the colormap. */
      int index, colors;
	  unsigned int length;

      index = (hdr->colorMapIndexHi << 8) | hdr->colorMapIndexLo;
      length = (hdr->colorMapLengthHi << 8) | hdr->colorMapLengthLo;

      if (length == 0)
	{
	  LOG1 ("TGA: invalid color map length %d\n", length);
	  at_exception_fatal(exp, "TGA: invalid color map length");
	  return image;
	}

      pelbytes = ROUNDUP_DIVIDE (hdr->colorMapSize, 8);
      colors = length + index;
      cmap = (unsigned char *) malloc (colors * pelbytes);

      /* Zero the entries up to the beginning of the map. */
      memset (cmap, 0, index * pelbytes);

      /* Read in the rest of the colormap. */
      if (fread (cmap + (index * pelbytes), pelbytes, length, fp) != length)
	{
	  LOG1 ("TGA: error reading colormap (ftell == %ld)\n", ftell (fp));
	  at_exception_fatal(exp, "TGA: error reading colormap");
	  return image;
	}

      /* If we have an alpha channel, then create a mapping to the alpha
         values. */
      if (pelbytes > 3)
        alphas = (unsigned char *) malloc (colors);

      k = 0;
      for (j = 0; j < colors * pelbytes; j += pelbytes)
        {
          /* Swap from BGR to RGB. */
          unsigned char tmp = cmap[j];
          cmap[k ++] = cmap[j + 2];
          cmap[k ++] = cmap[j + 1];
          cmap[k ++] = tmp;

          /* Take the alpha values out of the colormap. */
          if (alphas)
            alphas[nalphas ++] = cmap[j + 3];
        }

      /* If the last color was transparent, then omit it from the
         mapping. */
      if (nalphas && alphas[nalphas - 1] == 0)
        colors --;
 
      /* Now pretend as if we only have 8 bpp. */
      abpp = 0;
      pbpp = 8;
	  pelbytes = 1;
    }
  else
	  pelbytes = 3;

  image = at_bitmap_init(NULL, width, height, 3);

   /* Calculate TGA bytes per pixel. */
  bpp = ROUNDUP_DIVIDE (pbpp + abpp, 8);
 
  /* Maybe we need to reverse the data. */
  buffer = NULL;
  if (horzrev || vertrev)
    buffer = (unsigned char *) malloc (width * height * pelbytes * sizeof (unsigned char));  
  if (rle)
    myfread = rle_fread;        
  else
    myfread = std_fread;
 
  wbytes = width * pelbytes;
  badread = 0;

  npels = width * height;
  bsize = wbytes * height;
 
  /* Suck in the data one height at a time. */
  if (badread)
    pels = 0;
  else
    pels = (*myfread) (image.bitmap, bpp, npels, fp);
 
  if (pels != npels)
    {
      if (!badread)
        {
          /* Probably premature end of file. */
	  LOG1 ("TGA: error reading (ftell == %ld)\n", ftell (fp));
	  at_exception_warning(exp, "TGA: eroor reading file");
          badread = 1;
        }
 

      /* Fill the rest of this tile with zeros. */
      memset (image.bitmap + (pels * bpp), 0, ((npels - pels) * bpp));
    }
  /* If we have indexed alphas, then set them. */
  if (nalphas)
    {
      /* Start at the end of the buffer, and work backwards. */
      k = (npels - 1) * bpp;
      for (j = bsize - pelbytes; j >= 0; j -= pelbytes)
        {
          /* Find the alpha for this index. */
          image.bitmap[j + 1] = alphas[image.bitmap[k]];
          image.bitmap[j] = image.bitmap[k --];
        }
    }

   if (itype == GRAY)
      for (j = bsize/3 - 1; j >= 0; j -= 1)
        {
          /* Find the alpha for this index. */
          image.bitmap[3*j] = image.bitmap[j];
          image.bitmap[3*j+1] = image.bitmap[j];
          image.bitmap[3*j+2] = image.bitmap[j];
        }


  if (pelbytes >= 3)
    {
      /* Rearrange the colors from BGR to RGB. */
      for (j = 0; j < bsize; j += pelbytes)
        {
          unsigned char tmp = image.bitmap[j];
          image.bitmap[j] = image.bitmap[j + 2];
          image.bitmap[j + 2] = tmp;
        }
    }
 
  
 
  if (horzrev || vertrev)
    {
      unsigned char *tmp;
      if (vertrev)
        {
          /* We need to mirror only vertically. */
          for (j = 0; j < bsize; j += wbytes)
            memcpy (buffer + j,
              image.bitmap + bsize - (j + wbytes), wbytes);
        }
      else if (horzrev)
        {
          /* We need to mirror only horizontally. */
          for (j = 0; j < bsize; j += wbytes)
            for (k = 0; k < wbytes; k += pelbytes)
              memcpy (buffer + k + j,
                image.bitmap + (j + wbytes) - (k + pelbytes), pelbytes);
        }
      else
        {
          /* Completely reverse the pixels in the buffer. */
          for (j = 0; j < bsize; j += pelbytes)
            memcpy (buffer + j,
              image.bitmap + bsize - (j + pelbytes), pelbytes);
        }
  
       /* Swap the buffers because we modified them. */
      tmp = buffer;
      buffer = image.bitmap;
      image.bitmap = tmp;
    }

  if (fgetc (fp) != EOF)
    {
      LOG ("TGA: too much input data, ignoring extra...\n");
      at_exception_warning(exp, "TGA: too much input data, ignoring extra datum");
    }

  free (buffer);

  if (hdr->colorMapType == 1)
    {
      unsigned char *temp, *temp2, *temp3;
      unsigned char index;
      int xpos, ypos;

      temp2 = temp = image.bitmap;
      image.bitmap = temp3 = (unsigned char *) malloc (width * height * 3 * sizeof (unsigned char));
	  
      for (ypos = 0; ypos < height; ypos++)
        {
          for (xpos = 0; xpos < width; xpos++)
            {
               index = *temp2++;
               *temp3++ = cmap[3*index+0];
               *temp3++ = cmap[3*index+1];
               *temp3++ = cmap[3*index+2];
	    }
        }
      free (temp);
      free (cmap);
    }
 
  if (alphas)
    free (alphas);
 
  return image;
}  /* read_image */
