/*
   Copyright (C) 2004 by James Gregory
   Part of the GalaxyHack project
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.
 
   See the COPYING file for more details.
*/

#include "JSDL.h"
#include "Logging.h"
#include "Globals.h"

#include <boost/filesystem/operations.hpp>
#include <SDL_image.h>
#include <SDL_mixer.h>

#include <cstdlib>
#include <fstream>
#include <stdexcept>

using std::runtime_error;
using std::memcpy;

//defines from SDL_gfx
#define clip_xmin(surface) surface->clip_rect.x
#define clip_xmax(surface) surface->clip_rect.x+surface->clip_rect.w-1
#define clip_ymin(surface) surface->clip_rect.y
#define clip_ymax(surface) surface->clip_rect.y+surface->clip_rect.h-1

typedef struct tColorRGBA {
	Uint8 r;
	Uint8 g;
	Uint8 b;
	Uint8 a;
} tColorRGBA;

typedef struct tColorY {
	Uint8 y;
} tColorY;

void JamesSDL::Init() {	
	//it seems like really you should init the timer system even if in batch mode, but nevermind it seems to work
	//I guess just calling GetTicks() doesn't really require any initialization
	if (!globalSettings.batch) {
		if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) == -1) {
			char output[120];
			sprintf(output, SDL_GetError());
			throw runtime_error(output);
		}
		
		if (!globalSettings.disableSound) {
			if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
				char error[120];
				sprintf(error, SDL_GetError());
				string errorStr = error;
				errorStr += ", continuing without initialising sound.";
				WriteLog(errorStr);
				//set to false in SettingsStruct constructor
				globalSettings.dontWriteSound = true;
				globalSettings.disableSound = true;
			}
		}

		ResetVideo();
	}

	SDL_WM_SetCaption("GalaxyHack", 0);

	//keyboard
	SDL_EnableUNICODE(1);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
	keyboard = SDL_GetKeyState(0);

	//no mouse cursor with DGA so we may as well always do it ourself
	SDL_ShowCursor(SDL_DISABLE);

	if (!globalSettings.disableSound) {
		//4096 is rather a large chunksize, should be smaller if I add sound effects
		if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 4096) == -1) {
			char output[100];
			sprintf(output, Mix_GetError());
			throw runtime_error(output);
		}
	}
}

void JamesSDL::ResetVideo() {
	screenRect.x = 0;
	screenRect.y = 0;
	screenRect.w = globalSettings.screenWidth;
	screenRect.h = globalSettings.screenHeight;

	if (globalSettings.fullScreen)
		screen = SDL_SetVideoMode(globalSettings.screenWidth, globalSettings.screenHeight, screenBPP, SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_FULLSCREEN);
	else
		screen = SDL_SetVideoMode(globalSettings.screenWidth, globalSettings.screenHeight, screenBPP, SDL_HWSURFACE | SDL_DOUBLEBUF);

	if (screen == NULL) {
		SDL_Quit();
		char output[120];
		sprintf(output, SDL_GetError());
		throw runtime_error(output);
	}
}

void JamesSDL::BitmapHDtoFile(const string& filename) {
	if (globalSettings.batch)
		return;
		
	loadingSurface = IMG_Load(filename.c_str());

	if (loadingSurface == NULL) {
		char output[100];
		sprintf(output, IMG_GetError());
		throw runtime_error(output);
	}

	if (loadingSurface->format->BitsPerPixel != screenBPP)
		loadingSurface = SDL_ConvertSurface(loadingSurface, screen->format, SDL_SWSURFACE);
}

void JamesSDL::BitmapColorConvert(Uint16 newColor) {
	if (globalSettings.batch)
		return;
		
	Uint16 pink = MapRGB(255, 0, 255);

	SDL_LockSurface(loadingSurface);

	Uint16* castedBuffer = reinterpret_cast<Uint16*>(loadingSurface->pixels);

	for (int i = 0; i != (loadingSurface->pitch / 2) * loadingSurface->h; ++i) {
		if (castedBuffer[i] == pink)
			castedBuffer[i] = newColor;
	}

	SDL_UnlockSurface(loadingSurface);
}

void JamesSDL::FlipHorizontal() {
	if (globalSettings.batch)
		return;
		
	int nBytes = loadingSurface->pitch * loadingSurface->h * 2;
	char* buffer = new char[nBytes];

	SDL_LockSurface(loadingSurface);

	//copy image to work area
	//we rely here on the fact that memCpy increments the pointer it uses for copying
	//using ++ or some such rather than +ing it by 1 byte, even though it expects char
	//pointers
	//this means that we must give it the number of pixels rather than the number of bytes
	memcpy(buffer, loadingSurface->pixels, nBytes / 2);

	Uint16* pSource = reinterpret_cast<Uint16*>(buffer);
	Uint16* pDest = reinterpret_cast<Uint16*>(loadingSurface->pixels);

	//slightly strange indices
	//flip horizontally
	for (int i = 1; i != loadingSurface->h + 1; ++i) {
		for (int j = 1; j != loadingSurface->w + 1; ++j) {
			*pDest = pSource[i * loadingSurface->w - j];
			++pDest;
		}
	}

	SDL_UnlockSurface(loadingSurface);

	// release the memory
	delete[] buffer;
}

void JamesSDL::BitmapCloseFile() {
	if (globalSettings.batch)
		return;
		
	SDL_FreeSurface(loadingSurface);
	loadingSurface = 0;
}

SDL_Surface* JamesSDL::BitmapFiletoSurface(int x, int y, int width, int height, Uint16 transparency) {
	SDL_Surface* ret = SDL_CreateRGBSurface(SDL_HWSURFACE, width, height, screenBPP,  0, 0, 0, 0);
	
	if (globalSettings.batch)
		return ret;

	SDL_Rect tempRect = {x, y, width, height};

	if (width - x != loadingSurface->w || height - y != loadingSurface->h) {
		double zoomx = static_cast<double>(width - x) / loadingSurface->w;
		double zoomy = static_cast<double>(height - y) / loadingSurface->h;

		//FIXME FIXME FIXME utter hackery of the highest order, without this
		//medium capital ships end up being 511*111
		zoomx+= 0.00000000005;
		zoomy+= 0.00000000005;

		SDL_Surface* tempSurface = zoomSurface (loadingSurface, zoomx, zoomy, 1);

		SDL_BlitSurface(tempSurface, &tempRect, ret, 0);

		SDL_FreeSurface(tempSurface);
	} else
		//Just blit across
		SDL_BlitSurface(loadingSurface, &tempRect, ret, 0);


	SDL_SetColorKey(ret, SDL_SRCCOLORKEY | SDL_RLEACCEL, transparency);

	return ret;
}

SDL_Surface* JamesSDL::BitmapHDtoSurface(const string& filename, int x, int y, int w, int h, Uint16 transparency) {
	BitmapHDtoFile(filename);
	SDL_Surface* ret = BitmapFiletoSurface(x, y, w, h, transparency);
	BitmapCloseFile();

	return ret;
}

//////

void JamesSDL::BltFill(SDL_Rect destRect, Uint16 fillColor) {
	if (skipDisplayFrame || globalSettings.batch)
		return;		
	if (SDL_FillRect(screen, &destRect, fillColor) == -1)
		throw runtime_error("Failed to BltFill");
}

void JamesSDL::Blt(SDL_Surface* src, SDL_Rect destRect) {
	if (skipDisplayFrame || globalSettings.batch)
		return;	
	if (SDL_BlitSurface(src, 0, screen, &destRect) == -1)
		throw runtime_error("Failed to blit");
}


void JamesSDL::BltPart(SDL_Surface* src, SDL_Rect& srcRect, SDL_Rect destRect) {
	if (skipDisplayFrame || globalSettings.batch)
		return;	
	if (SDL_BlitSurface(src, &srcRect, screen, &destRect) == -1)
		throw runtime_error("BltPart fails");
}

void JamesSDL::Flip() {
	if (skipDisplayFrame || globalSettings.batch)
		return;	
	SDL_Flip(screen);
}

void JamesSDL::LockBack() {
	if (globalSettings.batch)
		return;	
	if (SDL_LockSurface(screen) == -1)
		throw runtime_error("Failed to lock back surface");
}

void JamesSDL::UnlockBack() {
	if (globalSettings.batch)
		return;	
	SDL_UnlockSurface(screen);
}

void JamesSDL::Shutdown() {
	if (theMusic)
		Mix_FreeMusic(theMusic);

	Mix_CloseAudio();

	//this automatically frees the screen
	SDL_Quit();
}

Uint16 JamesSDL::MapRGB(int r, int g, int b) {
	return SDL_MapRGB(screen->format, r, g, b);
}

void JamesSDL::UshortToRGB(Uint16 theColor, Uint8& giveR, Uint8& giveG, Uint8& giveB) {
	SDL_GetRGB(theColor, screen->format, &giveR, &giveG, &giveB);
}

void JamesSDL::LoadMusic(const string& filename) {
	if (globalSettings.disableSound)
		return;

	musicName = filename;

	if (theMusic)
		Mix_FreeMusic(theMusic);

	theMusic = Mix_LoadMUS(filename.c_str());

	if (!theMusic) {
		char output[100];
		sprintf(output, Mix_GetError());
		throw runtime_error(output);
	}
}

void JamesSDL::PlayMusic() {
	if (!globalSettings.bMusic || globalSettings.disableSound)
		return;

	if (Mix_PlayMusic(theMusic, -1) == -1) {
		char output[100];
		sprintf(output, Mix_GetError());
		throw runtime_error(output);
	}
}

void JamesSDL::StopMusic() {
	Mix_HaltMusic();
}

/* ----- Circle */

/* Note: Based on algorithm from sge library, modified by A. Schiffler and James Gregory*/
/* with multiple pixel-draw removal and other minor speedup changes.   */

int JamesSDL::CircleColor(SDL_Surface * dst, Sint16 x, Sint16 y, Sint16 r, Uint32 color) {
    Sint16 left, right, top, bottom;
    int result;
    Sint16 x1, y1, x2, y2;
    Sint16 cx = 0;
    Sint16 cy = r;
    Sint16 ocx = (Sint16) 0xffff;
    Sint16 ocy = (Sint16) 0xffff;
    Sint16 df = 1 - r;
    Sint16 d_e = 3;
    Sint16 d_se = -2 * r + 5;
    Sint16 xpcx, xmcx, xpcy, xmcy;
    Sint16 ypcy, ymcy, ypcx, ymcx;
    Uint8 *colorptr;

    /*
     * Sanity check radius 
     */
    if (r < 0) {
	return (-1);
    }

    /*
     * Special case for r=0 - draw a point 
     */
    if (r == 0) {
	return (pixelColor(dst, x, y, color));
    }

    /*
     * Get clipping boundary 
     */
    left = dst->clip_rect.x;
    right = dst->clip_rect.x + dst->clip_rect.w - 1;
    top = dst->clip_rect.y;
    bottom = dst->clip_rect.y + dst->clip_rect.h - 1;

    /*
     * Test if bounding box of circle is visible 
     */
    x1 = x - r;
    x2 = x + r;
    y1 = y - r;
    y2 = y + r;
    if ((x1<left) && (x2<left)) {
     return(0);
    } 
    if ((x1>right) && (x2>right)) {
     return(0);
    } 
    if ((y1<top) && (y2<top)) {
     return(0);
    } 
    if ((y1>bottom) && (y2>bottom)) {
     return(0);
    } 

    /*
     * Draw circle 
     */
    result = 0;

	/*
	 * No Alpha - direct memory writes 
	 */

	/*
	 * Setup color 
	 */
	colorptr = (Uint8 *) & color;
	if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
	    color = SDL_MapRGBA(dst->format, colorptr[0], colorptr[1], colorptr[2], colorptr[3]);
	} else {
	    color = SDL_MapRGBA(dst->format, colorptr[3], colorptr[2], colorptr[1], colorptr[0]);
	}

	/*
	 * Draw 
	 */
	do {
	    if ((ocy != cy) || (ocx != cx)) {
		xpcx = x + cx;
		xmcx = x - cx;
		if (cy > 0) {
		    ypcy = y + cy;
		    ymcy = y - cy;
		    result |= fastPixelColorNolock(dst, xmcx, ypcy, color);
		    result |= fastPixelColorNolock(dst, xpcx, ypcy, color);
		    result |= fastPixelColorNolock(dst, xmcx, ymcy, color);
		    result |= fastPixelColorNolock(dst, xpcx, ymcy, color);
		} else {
		    result |= fastPixelColorNolock(dst, xmcx, y, color);
		    result |= fastPixelColorNolock(dst, xpcx, y, color);
		}
		ocy = cy;
		xpcy = x + cy;
		xmcy = x - cy;
		if (cx > 0) {
		    ypcx = y + cx;
		    ymcx = y - cx;
		    result |= fastPixelColorNolock(dst, xmcy, ypcx, color);
		    result |= fastPixelColorNolock(dst, xpcy, ypcx, color);
		    result |= fastPixelColorNolock(dst, xmcy, ymcx, color);
		    result |= fastPixelColorNolock(dst, xpcy, ymcx, color);
		} else {
		    result |= fastPixelColorNolock(dst, xmcy, y, color);
		    result |= fastPixelColorNolock(dst, xpcy, y, color);
		}
		ocx = cx;
	    }
	    /*
	     * Update 
	     */
	    if (df < 0) {
		df += d_e;
		d_e += 2;
		d_se += 2;
	    } else {
		df += d_se;
		d_e += 2;
		d_se += 4;
		cy--;
	    }
	    cx++;
	} while (cx <= cy);


    return (result);
}

int JamesSDL::fastPixelColorNolock(SDL_Surface * dst, Sint16 x, Sint16 y, Uint32 color) {
    int bpp;
    Uint8 *p;

    /*
     * Honor clipping setup at pixel level 
     */
    if ((x >= clip_xmin(dst)) && (x <= clip_xmax(dst)) && (y >= clip_ymin(dst)) && (y <= clip_ymax(dst))) {

	/*
	 * Get destination format 
	 */
	bpp = dst->format->BytesPerPixel;
	p = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp;
	switch (bpp) {
	case 1:
	    *p = color;
	    break;
	case 2:
	    *(Uint16 *) p = color;
	    break;
	case 3:
	    if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
		p[0] = (color >> 16) & 0xff;
		p[1] = (color >> 8) & 0xff;
		p[2] = color & 0xff;
	    } else {
		p[0] = color & 0xff;
		p[1] = (color >> 8) & 0xff;
		p[2] = (color >> 16) & 0xff;
	    }
	    break;
	case 4:
	    *(Uint32 *) p = color;
	    break;
	}			/* switch */


    }

    return (0);
}

int JamesSDL::pixelColor(SDL_Surface * dst, Sint16 x, Sint16 y, Uint32 color)
{
    Uint8 alpha;
    Uint32 mcolor;
    int result = 0;

    /*
     * Setup color 
     */
    alpha = color & 0x000000ff;
    mcolor =
	SDL_MapRGBA(dst->format, (color & 0xff000000) >> 24,
		    (color & 0x00ff0000) >> 16, (color & 0x0000ff00) >> 8, alpha);

    /*
     * Draw 
     */
    result = _putPixelAlpha(dst, x, y, mcolor, alpha);

    return (result);
}

/* New, faster routine - default blending pixel */

int JamesSDL::_putPixelAlpha(SDL_Surface * surface, Sint16 x, Sint16 y, Uint32 color, Uint8 alpha) {
    Uint32 Rmask = surface->format->Rmask, Gmask =
	surface->format->Gmask, Bmask = surface->format->Bmask, Amask = surface->format->Amask;
    Uint32 R, G, B, A = 0;

    if (x >= clip_xmin(surface) && x <= clip_xmax(surface)
	&& y >= clip_ymin(surface) && y <= clip_ymax(surface)) {

	switch (surface->format->BytesPerPixel) {
	case 1:{		/* Assuming 8-bpp */
		if (alpha == 255) {
		    *((Uint8 *) surface->pixels + y * surface->pitch + x) = color;
		} else {
		    Uint8 *pixel = (Uint8 *) surface->pixels + y * surface->pitch + x;

		    Uint8 dR = surface->format->palette->colors[*pixel].r;
		    Uint8 dG = surface->format->palette->colors[*pixel].g;
		    Uint8 dB = surface->format->palette->colors[*pixel].b;
		    Uint8 sR = surface->format->palette->colors[color].r;
		    Uint8 sG = surface->format->palette->colors[color].g;
		    Uint8 sB = surface->format->palette->colors[color].b;

		    dR = dR + ((sR - dR) * alpha >> 8);
		    dG = dG + ((sG - dG) * alpha >> 8);
		    dB = dB + ((sB - dB) * alpha >> 8);

		    *pixel = SDL_MapRGB(surface->format, dR, dG, dB);
		}
	    }
	    break;

	case 2:{		/* Probably 15-bpp or 16-bpp */
		if (alpha == 255) {
		    *((Uint16 *) surface->pixels + y * surface->pitch / 2 + x) = color;
		} else {
		    Uint16 *pixel = (Uint16 *) surface->pixels + y * surface->pitch / 2 + x;
		    Uint32 dc = *pixel;

		    R = ((dc & Rmask) + (((color & Rmask) - (dc & Rmask)) * alpha >> 8)) & Rmask;
		    G = ((dc & Gmask) + (((color & Gmask) - (dc & Gmask)) * alpha >> 8)) & Gmask;
		    B = ((dc & Bmask) + (((color & Bmask) - (dc & Bmask)) * alpha >> 8)) & Bmask;
		    if (Amask)
			A = ((dc & Amask) + (((color & Amask) - (dc & Amask)) * alpha >> 8)) & Amask;

		    *pixel = R | G | B | A;
		}
	    }
	    break;

	case 3:{		/* Slow 24-bpp mode, usually not used */
		Uint8 *pix = (Uint8 *) surface->pixels + y * surface->pitch + x * 3;
		Uint8 rshift8 = surface->format->Rshift / 8;
		Uint8 gshift8 = surface->format->Gshift / 8;
		Uint8 bshift8 = surface->format->Bshift / 8;
		Uint8 ashift8 = surface->format->Ashift / 8;


		if (alpha == 255) {
		    *(pix + rshift8) = color >> surface->format->Rshift;
		    *(pix + gshift8) = color >> surface->format->Gshift;
		    *(pix + bshift8) = color >> surface->format->Bshift;
		    *(pix + ashift8) = color >> surface->format->Ashift;
		} else {
		    Uint8 dR, dG, dB, dA = 0;
		    Uint8 sR, sG, sB, sA = 0;

		    pix = (Uint8 *) surface->pixels + y * surface->pitch + x * 3;

		    dR = *((pix) + rshift8);
		    dG = *((pix) + gshift8);
		    dB = *((pix) + bshift8);
		    dA = *((pix) + ashift8);

		    sR = (color >> surface->format->Rshift) & 0xff;
		    sG = (color >> surface->format->Gshift) & 0xff;
		    sB = (color >> surface->format->Bshift) & 0xff;
		    sA = (color >> surface->format->Ashift) & 0xff;

		    dR = dR + ((sR - dR) * alpha >> 8);
		    dG = dG + ((sG - dG) * alpha >> 8);
		    dB = dB + ((sB - dB) * alpha >> 8);
		    dA = dA + ((sA - dA) * alpha >> 8);

		    *((pix) + rshift8) = dR;
		    *((pix) + gshift8) = dG;
		    *((pix) + bshift8) = dB;
		    *((pix) + ashift8) = dA;
		}
	    }
	    break;

	case 4:{		/* Probably 32-bpp */
		if (alpha == 255) {
		    *((Uint32 *) surface->pixels + y * surface->pitch / 4 + x) = color;
		} else {
		    Uint32 *pixel = (Uint32 *) surface->pixels + y * surface->pitch / 4 + x;
		    Uint32 dc = *pixel;

		    R = ((dc & Rmask) + (((color & Rmask) - (dc & Rmask)) * alpha >> 8)) & Rmask;
		    G = ((dc & Gmask) + (((color & Gmask) - (dc & Gmask)) * alpha >> 8)) & Gmask;
		    B = ((dc & Bmask) + (((color & Bmask) - (dc & Bmask)) * alpha >> 8)) & Bmask;
		    if (Amask)
			A = ((dc & Amask) + (((color & Amask) - (dc & Amask)) * alpha >> 8)) & Amask;

		    *pixel = R | G | B | A;
		}
	    }
	    break;
	}
    }

    return (0);
}

/* 
 
 zoomSurface()

 Zoomes a 32bit or 8bit 'src' surface to newly created 'dst' surface.
 'zoomx' and 'zoomy' are scaling factors for width and height. If 'smooth' is 1
 then the destination 32bit surface is anti-aliased. If the surface is not 8bit
 or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly.

*/

#define VALUE_LIMIT	0.001

void JamesSDL::zoomSurfaceSize(int width, int height, double zoomx, double zoomy, int *dstwidth, int *dstheight)
{
    /*
     * Sanity check zoom factors 
     */
    if (zoomx < VALUE_LIMIT) {
	zoomx = VALUE_LIMIT;
    }
    if (zoomy < VALUE_LIMIT) {
	zoomy = VALUE_LIMIT;
    }

    /*
     * Calculate target size 
     */
    *dstwidth = (int) ((double) width * zoomx);
    *dstheight = (int) ((double) height * zoomy);
    if (*dstwidth < 1) {
	*dstwidth = 1;
    }
    if (*dstheight < 1) {
	*dstheight = 1;
    }
}

SDL_Surface* JamesSDL::zoomSurface(SDL_Surface * src, double zoomx, double zoomy, int smooth)
{
    SDL_Surface *rz_src;
    SDL_Surface *rz_dst;
    int dstwidth, dstheight;
    int is32bit;
    int src_converted;
    int flipx, flipy;

    /*
     * Sanity check 
     */
    if (src == NULL)
	return (NULL);

    /*
     * Determine if source surface is 32bit or 8bit 
     */
    is32bit = (src->format->BitsPerPixel == 32);
    if ((is32bit) || (src->format->BitsPerPixel == 8)) {
	/*
	 * Use source surface 'as is' 
	 */
	rz_src = src;
	src_converted = 0;
    } else {
	/*
	 * New source surface is 32bit with a defined RGBA ordering 
	 */
	rz_src =
	    SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
	SDL_BlitSurface(src, NULL, rz_src, NULL);
	src_converted = 1;
	is32bit = 1;
    }

    flipx = (zoomx<0);
    if (flipx) zoomx = -zoomx;
    flipy = (zoomy<0);
    if (flipy) zoomy = -zoomy;

    /* Get size if target */
    zoomSurfaceSize(rz_src->w, rz_src->h, zoomx, zoomy, &dstwidth, &dstheight);

    /*
     * Alloc space to completely contain the zoomed surface 
     */
    rz_dst = NULL;

	/*
	 * Target surface is 32bit with source RGBA/ABGR ordering 
	 */
	rz_dst =
	    SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight, 32,
				 rz_src->format->Rmask, rz_src->format->Gmask,
				 rz_src->format->Bmask, rz_src->format->Amask);
    
    /*
     * Lock source surface 
     */
    SDL_LockSurface(rz_src);

	/*
	 * Call the 32bit transformation routine to do the zooming (using alpha) 
	 */
	zoomSurfaceRGBA(rz_src, rz_dst, flipx, flipy, smooth);
	/*
	 * Turn on source-alpha support 
	 */
	SDL_SetAlpha(rz_dst, SDL_SRCALPHA, 255);

    /*
     * Unlock source surface 
     */
    SDL_UnlockSurface(rz_src);

    /*
     * Cleanup temp surface 
     */
    if (src_converted) {
	SDL_FreeSurface(rz_src);
    }

    /*
     * Return destination surface 
     */
    return (rz_dst);
}

/* 
 
 32bit Zoomer with optional anti-aliasing by bilinear interpolation.

 Zoomes 32bit RGBA/ABGR 'src' surface to 'dst' surface.
 
*/

int JamesSDL::zoomSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int flipx, int flipy, int smooth)
{
    int x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy, ex, ey, t1, t2, sstep;
    tColorRGBA *c00, *c01, *c10, *c11;
    tColorRGBA *sp, *csp, *dp;
    int dgap;

    /*
     * Variable setup 
     */
    if (smooth) {
	/*
	 * For interpolation: assume source dimension is one pixel 
	 */
	/*
	 * smaller to avoid overflow on right and bottom edge.     
	 */
	sx = (int) (65536.0 * (float) (src->w - 1) / (float) dst->w);
	sy = (int) (65536.0 * (float) (src->h - 1) / (float) dst->h);
    } else {
	sx = (int) (65536.0 * (float) src->w / (float) dst->w);
	sy = (int) (65536.0 * (float) src->h / (float) dst->h);
    }

    /*
     * Allocate memory for row increments 
     */
    if ((sax = (int *) malloc((dst->w + 1) * sizeof(Uint32))) == NULL) {
	return (-1);
    }
    if ((say = (int *) malloc((dst->h + 1) * sizeof(Uint32))) == NULL) {
	free(sax);
	return (-1);
    }

    /*
     * Precalculate row increments 
     */
    sp = csp = (tColorRGBA *) src->pixels;
    dp = (tColorRGBA *) dst->pixels;

    if (flipx) csp += (src->w-1);
    if (flipy) csp  = (tColorRGBA*)( (Uint8*)csp + src->pitch*(src->h-1) );

    csx = 0;
    csax = sax;
    for (x = 0; x <= dst->w; x++) {
	*csax = csx;
	csax++;
	csx &= 0xffff;
	csx += sx;
    }
    csy = 0;
    csay = say;
    for (y = 0; y <= dst->h; y++) {
	*csay = csy;
	csay++;
	csy &= 0xffff;
	csy += sy;
    }

    dgap = dst->pitch - dst->w * 4;

    /*
     * Switch between interpolating and non-interpolating code 
     */
    if (smooth) {

	/*
	 * Interpolating Zoom 
	 */

	/*
	 * Scan destination 
	 */
	csay = say;
	for (y = 0; y < dst->h; y++) {
	    /*
	     * Setup color source pointers 
	     */
	    c00 = csp;
	    c01 = csp;
	    c01++;
	    c10 = (tColorRGBA *) ((Uint8 *) csp + src->pitch);
	    c11 = c10;
	    c11++;
	    csax = sax;
	    for (x = 0; x < dst->w; x++) {

		/*
		 * Interpolate colors 
		 */
		ex = (*csax & 0xffff);
		ey = (*csay & 0xffff);
		t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff;
		t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff;
		dp->r = (((t2 - t1) * ey) >> 16) + t1;
		t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff;
		t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff;
		dp->g = (((t2 - t1) * ey) >> 16) + t1;
		t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff;
		t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff;
		dp->b = (((t2 - t1) * ey) >> 16) + t1;
		t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff;
		t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff;
		dp->a = (((t2 - t1) * ey) >> 16) + t1;

		/*
		 * Advance source pointers 
		 */
		csax++;
		sstep = (*csax >> 16);
		c00 += sstep;
		c01 += sstep;
		c10 += sstep;
		c11 += sstep;
		/*
		 * Advance destination pointer 
		 */
		dp++;
	    }
	    /*
	     * Advance source pointer 
	     */
	    csay++;
	    csp = (tColorRGBA *) ((Uint8 *) csp + (*csay >> 16) * src->pitch);
	    /*
	     * Advance destination pointers 
	     */
	    dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
	}

    } else {

	/*
	 * Non-Interpolating Zoom 
	 */

	csay = say;
	for (y = 0; y < dst->h; y++) {
	    sp = csp;
	    csax = sax;
	    for (x = 0; x < dst->w; x++) {
		/*
		 * Draw 
		 */
		*dp = *sp;
		/*
		 * Advance source pointers 
		 */
		csax++;
		sstep = (*csax >> 16);
		if (flipx) sstep = -sstep;
		sp += sstep;
		/*
		 * Advance destination pointer 
		 */
		dp++;
	    }
	    /*
	     * Advance source pointer 
	     */
	    csay++;
	    sstep = (*csay >> 16) * src->pitch;
	    if (flipy) sstep = -sstep;
	    csp = (tColorRGBA *) ((Uint8 *) csp + sstep);

	    /*
	     * Advance destination pointers 
	     */
	    dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
	}

    }

    /*
     * Remove temp arrays 
     */
    free(sax);
    free(say);

    return (0);
}