/*
    SDL_image:  An example image loading library for use with SDL
    Copyright (C) 1997-2009 Sam Lantinga

    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/

#if !defined(__APPLE__) || defined(SDL_IMAGE_USE_COMMON_BACKEND)

/* This is a Targa image file loading framework */

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

#include "SDL_endian.h"

#include "SDL_image.h"

#ifdef LOAD_TGA

/*
 * A TGA loader for the SDL library
 * Supports: Reading 8, 15, 16, 24 and 32bpp images, with alpha or colourkey,
 *           uncompressed or RLE encoded.
 *
 * 2000-06-10 Mattias Engdegård <f91-men@nada.kth.se>: initial version
 * 2000-06-26 Mattias Engdegård <f91-men@nada.kth.se>: read greyscale TGAs
 * 2000-08-09 Mattias Engdegård <f91-men@nada.kth.se>: alpha inversion removed
 */

struct TGAheader {
    Uint8 infolen;		/* length of info field */
    Uint8 has_cmap;		/* 1 if image has colormap, 0 otherwise */
    Uint8 type;

    Uint8 cmap_start[2];	/* index of first colormap entry */
    Uint8 cmap_len[2];		/* number of entries in colormap */
    Uint8 cmap_bits;		/* bits per colormap entry */

    Uint8 yorigin[2];		/* image origin (ignored here) */
    Uint8 xorigin[2];
    Uint8 width[2];		/* image size */
    Uint8 height[2];
    Uint8 pixel_bits;		/* bits/pixel */
    Uint8 flags;
};

enum tga_type {
    TGA_TYPE_INDEXED = 1,
    TGA_TYPE_RGB = 2,
    TGA_TYPE_BW = 3,
    TGA_TYPE_RLE_INDEXED = 9,
    TGA_TYPE_RLE_RGB = 10,
    TGA_TYPE_RLE_BW = 11
};

#define TGA_INTERLEAVE_MASK	0xc0
#define TGA_INTERLEAVE_NONE	0x00
#define TGA_INTERLEAVE_2WAY	0x40
#define TGA_INTERLEAVE_4WAY	0x80

#define TGA_ORIGIN_MASK		0x30
#define TGA_ORIGIN_LEFT		0x00
#define TGA_ORIGIN_RIGHT	0x10
#define TGA_ORIGIN_LOWER	0x00
#define TGA_ORIGIN_UPPER	0x20

/* read/write unaligned little-endian 16-bit ints */
#define LE16(p) ((p)[0] + ((p)[1] << 8))
#define SETLE16(p, v) ((p)[0] = (v), (p)[1] = (v) >> 8)

/* Load a TGA type image from an SDL datasource */
SDL_Surface *IMG_LoadTGA_RW(SDL_RWops *src)
{
    int start;
    const char *error = NULL;
    struct TGAheader hdr;
    int rle = 0;
    int alpha = 0;
    int indexed = 0;
    int grey = 0;
    int ckey = -1;
    int ncols, w, h;
    SDL_Surface *img = NULL;
    Uint32 rmask, gmask, bmask, amask;
    Uint8 *dst;
    int i;
    int bpp;
    int lstep;
    Uint32 pixel;
    int count, rep;

    if ( !src ) {
        /* The error message has been set in SDL_RWFromFile */
        return NULL;
    }
    start = SDL_RWtell(src);

    if(!SDL_RWread(src, &hdr, sizeof(hdr), 1)) {
        error = "Error reading TGA data";
	goto error;
    }
    ncols = LE16(hdr.cmap_len);
    switch(hdr.type) {
    case TGA_TYPE_RLE_INDEXED:
	rle = 1;
	/* fallthrough */
    case TGA_TYPE_INDEXED:
	if(!hdr.has_cmap || hdr.pixel_bits != 8 || ncols > 256)
	    goto unsupported;
	indexed = 1;
	break;

    case TGA_TYPE_RLE_RGB:
	rle = 1;
	/* fallthrough */
    case TGA_TYPE_RGB:
	indexed = 0;
	break;

    case TGA_TYPE_RLE_BW:
	rle = 1;
	/* fallthrough */
    case TGA_TYPE_BW:
	if(hdr.pixel_bits != 8)
	    goto unsupported;
	/* Treat greyscale as 8bpp indexed images */
	indexed = grey = 1;
	break;

    default:
        goto unsupported;
    }

    bpp = (hdr.pixel_bits + 7) >> 3;
    rmask = gmask = bmask = amask = 0;
    switch(hdr.pixel_bits) {
    case 8:
	if(!indexed) {
            goto unsupported;
	}
	break;

    case 15:
    case 16:
	/* 15 and 16bpp both seem to use 5 bits/plane. The extra alpha bit
	   is ignored for now. */
	rmask = 0x7c00;
	gmask = 0x03e0;
	bmask = 0x001f;
	break;

    case 32:
	alpha = 1;
	/* fallthrough */
    case 24:
	if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
	    int s = alpha ? 0 : 8;
	    amask = 0x000000ff >> s;
	    rmask = 0x0000ff00 >> s;
	    gmask = 0x00ff0000 >> s;
	    bmask = 0xff000000 >> s;
	} else {
	    amask = alpha ? 0xff000000 : 0;
	    rmask = 0x00ff0000;
	    gmask = 0x0000ff00;
	    bmask = 0x000000ff;
	}
	break;

    default:
        goto unsupported;
    }

    if((hdr.flags & TGA_INTERLEAVE_MASK) != TGA_INTERLEAVE_NONE
       || hdr.flags & TGA_ORIGIN_RIGHT) {
        goto unsupported;
    }
    
    SDL_RWseek(src, hdr.infolen, RW_SEEK_CUR); /* skip info field */

    w = LE16(hdr.width);
    h = LE16(hdr.height);
    img = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
			       bpp * 8,
			       rmask, gmask, bmask, amask);
    if(img == NULL) {
        error = "Out of memory";
        goto error;
    }

    if(hdr.has_cmap) {
	int palsiz = ncols * ((hdr.cmap_bits + 7) >> 3);
	if(indexed && !grey) {
	    Uint8 *pal = malloc(palsiz), *p = pal;
	    SDL_Color *colors = img->format->palette->colors;
	    img->format->palette->ncolors = ncols;
	    SDL_RWread(src, pal, palsiz, 1);
	    for(i = 0; i < ncols; i++) {
		switch(hdr.cmap_bits) {
		case 15:
		case 16:
		    {
			Uint16 c = p[0] + (p[1] << 8);
			p += 2;
			colors[i].r = (c >> 7) & 0xf8;
			colors[i].g = (c >> 2) & 0xf8;
			colors[i].b = c << 3;
		    }
		    break;
		case 24:
		case 32:
		    colors[i].b = *p++;
		    colors[i].g = *p++;
		    colors[i].r = *p++;
		    if(hdr.cmap_bits == 32 && *p++ < 128)
			ckey = i;
		    break;
		}
	    }
	    free(pal);
	    if(ckey >= 0)
		SDL_SetColorKey(img, SDL_SRCCOLORKEY, ckey);
	} else {
	    /* skip unneeded colormap */
	    SDL_RWseek(src, palsiz, RW_SEEK_CUR);
	}
    }

    if(grey) {
	SDL_Color *colors = img->format->palette->colors;
	for(i = 0; i < 256; i++)
	    colors[i].r = colors[i].g = colors[i].b = i;
	img->format->palette->ncolors = 256;
    }

    if(hdr.flags & TGA_ORIGIN_UPPER) {
	lstep = img->pitch;
	dst = img->pixels;
    } else {
	lstep = -img->pitch;
	dst = (Uint8 *)img->pixels + (h - 1) * img->pitch;
    }

    /* The RLE decoding code is slightly convoluted since we can't rely on
       spans not to wrap across scan lines */
    count = rep = 0;
    for(i = 0; i < h; i++) {
	if(rle) {
	    int x = 0;
	    for(;;) {
		Uint8 c;

		if(count) {
		    int n = count;
		    if(n > w - x)
			n = w - x;
		    SDL_RWread(src, dst + x * bpp, n * bpp, 1);
		    count -= n;
		    x += n;
		    if(x == w)
			break;
		} else if(rep) {
		    int n = rep;
		    if(n > w - x)
			n = w - x;
		    rep -= n;
		    while(n--) {
			memcpy(dst + x * bpp, &pixel, bpp);
			x++;
		    }
		    if(x == w)
			break;
		}

		SDL_RWread(src, &c, 1, 1);
		if(c & 0x80) {
		    SDL_RWread(src, &pixel, bpp, 1);
		    rep = (c & 0x7f) + 1;
		} else {
		    count = c + 1;
		}
	    }

	} else {
	    SDL_RWread(src, dst, w * bpp, 1);
	}
	if(SDL_BYTEORDER == SDL_BIG_ENDIAN && bpp == 2) {
	    /* swap byte order */
	    int x;
	    Uint16 *p = (Uint16 *)dst;
	    for(x = 0; x < w; x++)
		p[x] = SDL_Swap16(p[x]);
	}
	dst += lstep;
    }
    return img;

unsupported:
    error = "Unsupported TGA format";

error:
    SDL_RWseek(src, start, RW_SEEK_SET);
    if ( img ) {
        SDL_FreeSurface(img);
    }
    IMG_SetError(error);
    return NULL;
}

#else

/* dummy TGA load routine */
SDL_Surface *IMG_LoadTGA_RW(SDL_RWops *src)
{
	return(NULL);
}

#endif /* LOAD_TGA */

#endif /* !defined(__APPLE__) || defined(SDL_IMAGE_USE_COMMON_BACKEND) */
