/* This code is ripped from Autotrace-0.29. Small modifications by pts. */

/* input-tga.ci reads tga files */

#ifdef __GNUC__
#ifndef __clang__
#pragma implementation
#endif
#endif

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

#include "at_bitmap.h"
/* #include "message.h" */
/* #include "xstd.h" */
/* #include "input-tga.h" */ /* BUGFIX by pts: bmp -> tga */

/* 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 TGA_RGB 5
#define INDEXED_IMAGE 1
#define INDEXEDA_IMAGE 2
#define GRAY_IMAGE 3
#define GRAYA_IMAGE 4
#define TGA_RGB_IMAGE 5
#define TGA_RGBA_IMAGE 6

typedef struct _TgaSaveVals
{
 int rle;
} TgaSaveVals;

typedef struct _TgaSaveInterface
{
 int run;
} TgaSaveInterface;

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;
};

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


static bitmap_type ReadImage (FILE *fp,
                        struct tga_header *hdr);
#if PTS_SAM2P /**** pts ****/
bitmap_type tga_load_image (FILE* filename)
#else
bitmap_type tga_load_image (at_string filename)
#endif
{
 FILE *fp;
 struct tga_header hdr;

 bitmap_type image;

 image.bitmap = NULL;

 #if PTS_SAM2P /**** pts ****/
   fp=filename;
 #else
   fp = fopen (filename, "rb");
   if (!fp)
       FATAL1 ("TGA: can't open \"%s\"\n", filename);
 #endif

 memset(&hdr, '\0', sizeof(hdr));

#if 0
 /* Check the footer. */
 if (fseek (fp, 0L - (sizeof (tga_footer)), SEEK_END)
     || fread (&tga_footer, sizeof (tga_footer), 1, fp) != 1)
#if PTS_SAM2P
     FATALP("TGA: Cannot read footer");
#else
     FATAL1 ("TGA: Cannot read footer from \"%s\"\n", filename);
#endif
 /* Check the signature. */
 if (fseek (fp, 0, SEEK_SET) ||
#else
 if (0 ||
#endif
     fread (&hdr, sizeof (hdr), 1, fp) != 1)
#if PTS_SAM2P
     FATALP("TGA: Cannot read header");
#else
     FATAL1 ("TGA: Cannot read header from \"%s\"\n", filename);
#endif

 /* Skip the image ID field. */
 {
#if 0
   if (hdr.idLength && fseek (fp, hdr.idLength, SEEK_CUR))
#else
   char buf[256];
   if (hdr.idLength!=fread(buf, 1, hdr.idLength, fp))
#endif
#if PTS_SAM2P
     FATALP("TGA: Cannot skip ID field");
#else
     FATAL1 ("TGA: Cannot skip ID field in \"%s\"\n", filename);
#endif
 }

 image = ReadImage (fp, &hdr);
 /* 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 bitmap_type
ReadImage (FILE              *fp,
          struct tga_header *hdr)
{
 bitmap_type image;
 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=0;
 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)
   {
     WARNINGP3 ("TGA: ",pbpp," bit image, ",abpp," bit alpha is greater than ",bpp," total bits per pixel");

     /* Assume that alpha bits were set incorrectly. */
     abpp = bpp - pbpp;
     WARNINGP1 ("TGA: reducing to * bit alpha: ", abpp);
   }
 else if (abpp + pbpp < bpp)
   {
     WARNINGP3 ("TGA: ",pbpp," bit image, ",abpp," bit alpha is less than ",bpp," total bits per pixel");

     /* Again, assume that alpha bits were set incorrectly. */
     abpp = bpp - pbpp;
     WARNINGP1 ("TGA: increasing to * bit alpha: ", abpp);
   }

 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. */
         FATALP ("TGA: index sizes other than 8 bits are unimplemented");
     break;

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

   case TGA_TYPE_COLOR_RLE:
     rle = 1;
   case TGA_TYPE_COLOR:
     itype = TGA_RGB;
     break;

   default:
     FATALP1 ("TGA: unrecognized image type ", (unsigned)hdr->imageType);
}

 if ((abpp && abpp != 8) ||
     ((itype == TGA_RGB || itype == INDEXED) && pbpp != 24) ||
     (itype == GRAY && pbpp != 8))
     /* FIXME: We haven't implemented bit-packed fields yet. */
     FATALP ("TGA: channel sizes other than 8 bits are unimplemented");

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

 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)
       FATALP1 ("TGA: invalid color map length ", length);

     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)
       FATALP1 ("TGA: error reading colormap; ftell == ", ftell (fp));

     /* 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 TGA_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.bitmap = (unsigned char *) malloc (width * height * 3 * sizeof(unsigned char));
 BITMAP_WIDTH (image) = (unsigned short) width;
 BITMAP_HEIGHT (image) = (unsigned short) height;
 BITMAP_PLANES (image) = (unsigned short) 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. */
         WARNINGP1 ("TGA: error reading; ftell == ", ftell (fp));
         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 TGA_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)
   WARNINGP ("TGA: too much input data, ignoring extra...");

 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 */