/******************************************************************************
 * $Id: gfx_tga.cc,v 1.6 2006/04/30 23:29:49 devel Exp $
 * @file
 * @brief	Loader for Truevision Targa images
 * @author	Dan Maas <dmaas@dcine.com>,
 *          Piotr Pawlow <pp@siedziba.pl>,
 *          Jim E. Brooks http://www.jimbrooks.org
 * @remarks	- Based on TGA specifications available at: 
 *			  http://www.wotsit.org/cgi-bin/search.cgi?TGA
 *			- header/footer structures courtesy of the GIMP Targa plugin 
 *			- Modified 2004/06/13 by Jim Brooks http://www.jimbrooks.org:
 *			  - Modified to process a buffer (for precompiled TGA data)
 *			    rather than a file directly.
 ******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "gfx_tga.hh"
namespace gfx {

// log/debug level, look at common.cpp
#define LOG_NONE 0 // do not log anything
#define LOG_FATAL 1 // only show fatal (GL-117 will exit) errors (stderr)
#define LOG_ERROR 2 // show errors too (stderr)
#define LOG_WARN 3 // show warnings too (stdout)
#define LOG_MOST 4 // show important messages too (stdout)
#define LOG_ALL 5 // log everything (stdout)
void display (char *str, int level) { }

//#define DATA8   unsigned char
//#define DATA32  unsigned int
typedef unsigned int DATA32;
typedef unsigned char DATA8;

static inline void
WRITE_RGBA(DATA32 *p, DATA8 r, DATA8 g, DATA8 b, DATA8 a)
{
  DATA8 *dp = (DATA8 *)p;

#ifndef WORDS_BIGENDIAN

  dp[0] = r;
  dp[1] = g;
  dp[2] = b;
  dp[3] = a;
//#define A_VAL(p) ((DATA8 *)(p))[3]
//#define R_VAL(p) ((DATA8 *)(p))[0]
//#define G_VAL(p) ((DATA8 *)(p))[1]
//#define B_VAL(p) ((DATA8 *)(p))[2]

#else

  dp[0] = a;
  dp[1] = b;
  dp[2] = g;
  dp[3] = r;
//#define A_VAL(p) ((DATA8 *)(p))[0]
//#define R_VAL(p) ((DATA8 *)(p))[3]
//#define G_VAL(p) ((DATA8 *)(p))[2]
//#define B_VAL(p) ((DATA8 *)(p))[1]

#endif

#if 0
//#define WRITE_RGBA(p, r, g, b, a) R_VAL(p) = (r); G_VAL(p) = (g); B_VAL(p) = (b);  A_VAL(p) = (a);
#endif
}

/* flip an inverted image - see RLE reading below */
static DATA32* flip(DATA32* in, int w, int h);

/* TGA pixel formats */
#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

/* TGA header flags */
#define TGA_DESC_ABITS      0x0f 
#define TGA_DESC_HORIZONTAL 0x10 
#define TGA_DESC_VERTICAL   0x20 

#define TGA_SIGNATURE "TRUEVISION-XFILE" 
#define TGA_FOOTER_LEN 26

typedef struct { 
   unsigned char    idLength; 
   unsigned char    colorMapType; 
   unsigned char    imageType; 
   unsigned char    colorMapIndexLo, colorMapIndexHi; 
   unsigned char    colorMapLengthLo, colorMapLengthHi; 
   unsigned char    colorMapSize; 
   unsigned char    xOriginLo, xOriginHi; 
   unsigned char    yOriginLo, yOriginHi; 
   unsigned char    widthLo, widthHi; 
   unsigned char    heightLo, heightHi; 
   unsigned char    bpp; 
   unsigned char    descriptor; 
} tga_header; 

typedef struct { 
   unsigned int extensionAreaOffset;
   unsigned int developerDirectoryOffset;
   char signature[16]; 
   char dot; 
   char null; 
} tga_footer; 

/* Load up a TGA file 
 * 
 * As written this function only recognizes the following types of Targas: 
 *		Type 02 - Uncompressed RGB, 24 or 32 bits 
 *		Type 03 - Uncompressed grayscale, 8 bits
 *		Type 10 - RLE-compressed RGB, 24 or 32 bits
 *		Type 11 - RLE-compressed grayscale, 8 bits  
 * There are several other (uncommon) Targa formats which this function can't currently handle
 */

unsigned char* tga_load( unsigned int* olen, const unsigned char* ibuf, int ibufLen, int* w, int* h )
{ 
   int         idx = 0; // index to ibuf[]
   int         bpp, vinverted = 0;
   int         rle = 0, footer_present = 0; 
   DATA32      *im_data;   
   int         im_w,im_h;
   *olen = 0;

   if ( ibuf == NULL || ibufLen <= 0 || ibufLen > (50*1000*1000) ) return 0;

   tga_header header; 
   tga_footer footer; 
   
   /* read the footer first */
   idx = ibufLen - TGA_FOOTER_LEN;
   if ( idx <= 0 || idx+TGA_FOOTER_LEN > ibufLen  ) return 0;
   memcpy( &footer, &ibuf[idx], TGA_FOOTER_LEN );

   /* check the footer to see if we have a v2.0 TGA file */
   if (memcmp(footer.signature, TGA_SIGNATURE, sizeof (footer.signature)) == 0) 
      footer_present = 1; 
   
   /* now read the header */
   idx = 0;
   memcpy( &header, &ibuf[idx], sizeof(header) );
   idx += sizeof(header);

   /* skip over alphanumeric ID field */
   idx += header.idLength;

   /* now parse the header */
   
   /* this flag indicated bottom-up pixel storage */
   vinverted = header.descriptor ^ TGA_DESC_VERTICAL; 
   
   switch (header.imageType) 
     { 
     case TGA_TYPE_COLOR_RLE:
     case TGA_TYPE_GRAY_RLE:
	rle = 1; 
	break; 
	
     case TGA_TYPE_COLOR: 
     case TGA_TYPE_GRAY:
	rle = 0; 
	break; 
	
     default: 
	return 0; 
     } 

   /* bits per pixel */
   bpp = header.bpp; 
   
   if( ! ((bpp == 32) || (bpp == 24) || (bpp == 8)) ) 
	return 0;
   
   /* endian-safe loading of 16-bit sizes */
   im_w = (header.widthHi << 8) | header.widthLo; 
   im_h = (header.heightHi << 8) | header.heightLo;    
   
   if ((im_w > 32767) || (im_w < 1) || (im_h > 32767) || (im_h < 1))
     {
	im_w = 0;
	return 0;
     }
   
     {
	unsigned long datasize;
	unsigned char *buf, *bufptr;
	DATA32 *dataptr; 
	
	int   y;
	
	/* allocate the destination buffer */
	*olen = im_w * im_h * sizeof(DATA32);
	im_data = (DATA32*)malloc( *olen );
	if(!im_data)
	  {
	     im_w = 0;
       display ("Out of memory", LOG_FATAL);
	     return 0;
	  }
	
	/* first we read the file data into a buffer for parsing */
	/* then we decode from RAM */
	
	/* find out how much data must be read from the file */
	/* (this is NOT simply width*height*4, due to compression) */
	
//	stat(im_file, &ss);
	datasize = ibufLen - sizeof(tga_header) - header.idLength -
	   (footer_present ? TGA_FOOTER_LEN : 0);
	
	buf = (unsigned char*)malloc(datasize); 
	if(!buf) 
	  {
	     im_w = 0;
       display ("Out of memory", LOG_FATAL);
	     return 0;
	  }
	
	/* copy input buffer to temp buffer */
    memcpy( buf, &ibuf[idx], datasize );

	/* buffer is ready for parsing */
	
	/* bufptr is the next byte to be read from the buffer */
	bufptr = buf; 
	
	/* dataptr is the next 32-bit pixel to be filled in */
	dataptr = im_data;
	
	/* decode uncompressed BGRA data */
	if(!rle) 
	  { 
	     for(y = 0; y < im_h; y++) /* for each row */
	       { 
		  int x;
		  
		  /* point dataptr at the beginning of the row */
		  if(!vinverted)
		     /* some TGA's are stored upside-down! */
		     dataptr = im_data + (im_h - (y+1)) * im_w;
		  else
		     dataptr = im_data + y * im_w;
		  
		  
		  for(x = 0; x < im_w; x++) /* for each pixel in the row */
		    {
		       switch(bpp) {
			  
			  /* 32-bit BGRA pixels */
		       case 32: 
			  WRITE_RGBA(dataptr, 
				     *(bufptr + 2),  /* R */
				     *(bufptr + 1),  /* G */
				     *(bufptr + 0),  /* B */
				     *(bufptr + 3)   /* A */
				     );
			  dataptr++;
			  bufptr += 4;
			  break;
			  
			  /* 24-bit BGR pixels */
		       case 24: 
			  WRITE_RGBA(dataptr,
				     *(bufptr + 2),  /* R */
				     *(bufptr + 1),  /* G */
				     *(bufptr + 0),  /* B */
				     (char) 0xff     /* A */
				     );
			  dataptr++;
			  bufptr += 3;
			  break;
			  
			  /* 8-bit grayscale */
		       case 8:
			  WRITE_RGBA(dataptr,
				     *bufptr,      /* grayscale */
				     *bufptr,
				     *bufptr,
				     (char) 0xff
				     );
			  dataptr++;
			  bufptr += 1;
			  break;
		       }  
		       
		    } /* end for (each pixel) */
		  
	       } /* end for (each row) */
	     
	  } /* end if (RLE) */
	
	/* decode RLE compressed data */
	else 
	  { 
	     unsigned char curbyte, red, green, blue, alpha; 
	     DATA32 *final_pixel = dataptr + im_w * im_h; 
	     
	     /* loop until we've got all the pixels */
	     while(dataptr < final_pixel)	
	       { 
		  int count; 
		  
		  curbyte = *bufptr++; 
		  count = (curbyte & 0x7F) + 1; 
		  
		  if(curbyte & 0x80)  /* RLE packet */
		    { 
		       int i;
		       
		       switch(bpp) {
		       case 32:
			  blue = *bufptr++; green = *bufptr++; red = *bufptr++; 
			  alpha = *bufptr++;
			  for(i = 0; i < count; i++) {
			     WRITE_RGBA(dataptr, red, green, blue, alpha);
			     dataptr++;
			  }
			  break;
			  
		       case 24:
			  blue = *bufptr++; green = *bufptr++; red = *bufptr++; 
			  for(i = 0; i < count; i++) {
			     WRITE_RGBA(dataptr, red, green, blue, (char) 0xff);
			     dataptr++;
			  }
			  break;
			  
		       case 8:
			  alpha = *bufptr++;
			  for(i = 0; i < count; i++) {
			     WRITE_RGBA(dataptr, alpha, alpha, alpha, (char) 0xff);
			     dataptr++;
			  }
			  break;
		       }
		       
		    } /* end if (RLE packet) */
		  
		  else  /* raw packet */
		    { 
		       int i;
		       
		       for(i = 0; i < count; i++) 
			 { 
			    switch(bpp) {
			       
			       /* 32-bit BGRA pixels */
			    case 32: 
			       WRITE_RGBA(dataptr, 
					  *(bufptr + 2),  /* R */
					  *(bufptr + 1),  /* G */
					  *(bufptr + 0),  /* B */
					  *(bufptr + 3)   /* A */
					  );
			       dataptr++;
			       bufptr += 4;
			       break;
			       
			       /* 24-bit BGR pixels */
			    case 24: 
			       WRITE_RGBA(dataptr,
					  *(bufptr + 2),  /* R */
					  *(bufptr + 1),  /* G */
					  *(bufptr + 0),  /* B */
					  (char) 0xff     /* A */
					  );
			       dataptr++;
			       bufptr += 3;
			       break;
			       
			       /* 8-bit grayscale */
			    case 8:
			       WRITE_RGBA(dataptr,
					  *bufptr,      /* pseudo-grayscale */
					  *bufptr,
					  *bufptr,
					  (char) 0xff
					  );
			       dataptr++;
			       bufptr += 1;
			       break;
			    }  
			 } 
		    } /* end if (raw packet) */ 
		  
	       } /* end for (each packet) */
	     
	     /* must now flip a bottom-up image */
	     
	     /* This is the best of several ugly implementations
	      * I considered. It's not very good since the image
	      * will be upside-down throughout the loading process.
	      * This could be done in-line with the de-RLE code
	      * above, but that would be messy to code. There's
	      * probably a better way... */
	     
	     if(!vinverted) {
		im_data = flip(im_data, im_w, im_h);
		if(!im_data) {
		   free(buf);
		   return 0;
		}
	     }

	  } /* end if (image is RLE) */ 

	free(buf); 
     }
   
   *w=im_w; *h=im_h;
   return (unsigned char*)im_data;
}

/**********************/

/* flip a DATA32 image block vertically
 * by allocating a new block, then copying
 * the rows in reverse order
 */

static DATA32*
flip(DATA32* in, int w, int h)
{
   int adv, adv2, i;
   DATA32* out;
   
   out = (DATA32*)malloc(w * h * sizeof(DATA32));
   if(!out)
   {
      display ("Out of memory", LOG_FATAL);
      return NULL;
   }
   
   adv = 0; adv2 = w * h;
   
   for(i = 0; i < h; i++) {
      adv2 -= w;
      memmove(out + adv, in + adv2, w * sizeof(DATA32));
      adv += w;
   }
   
   free(in);
   return out;
}

} // namespace gfx
