#include "PixPort.h"

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

#include "displayer_sdl.h"
#include "SDL_prim.h"

#include "RectUtils.h"

#include "libmfl.h"

long		PixPort::sTempSize			= 0;
char*		PixPort::sTemp				= 0;

PixPort::PixPort()
{
	mBM = NULL;
	mWorld = 0;
	surface = NULL;
	mLineWidth = 1;
	mBackColor = 0;
	mCurFontID = 0;
	mDeviceLineHeight = 0;
	sdl_song_text_surface = NULL;
	last_text = NULL;

}

PixPort::~PixPort()
{
	PixTextStyle* font;
	unsigned int i;

	Un_Init();

	for ( i = 0; i < mFonts.Count(); i++ ) {
		font = (PixTextStyle*) mFonts[ i ];
		mfl_DestroyFont((mfl_font)font->mOSFontID);
	}

	// Delete any info structures we may have created
	for ( i = 0; i < mFonts.Count(); i++ ) {
		font = (PixTextStyle*) mFonts[ i ];
		delete font;
	}

	if ( sTemp ) {
		delete []sTemp;
		sTemp = 0;
		sTempSize = 0;
	}
}

void PixPort::Un_Init()
{
	// Destroy font context
	if (mWorld) {
		mfl_DestroyContext(mWorld);
		mWorld = 0;
	}

	// Free buffer
	if (surface && !is_video_surface) {
		SDL_FreeSurface(surface);
		surface = 0;
	}

	if (sdl_song_text_surface) {
		SDL_FreeSurface(sdl_song_text_surface);
		sdl_song_text_surface = NULL;
	}

	if (last_text) {
		g_free(last_text);
		last_text = NULL;
	}

	// Invalidate the selected text style
	mCurFontID = -1;
}

void PixPort::SetClipRect( const Rect* inRect )
{
	mClipRect.top = 0;
	mClipRect.left = 0;
	mClipRect.right = surface->w;
	mClipRect.bottom = mHeight;

	if ( inRect )
		SectRect( inRect, &mClipRect, &mClipRect );

}

void PixPort::SetClipRect( long inSX, long inSY, long inEX, long inEY )
{
	Rect r;

	SetRect( &r, inSX, inSY, inEX, inEY );
	SetClipRect( &r );
}

void PixPort::Init(SDL_Surface *inSurface)
{
	if (inSurface == surface) { return; }

	Un_Init();

	surface = inSurface;
	is_video_surface = (surface == SDL_GetVideoSurface());
	mHeight = surface->h;

	if (!is_video_surface) { mHeight -= 2; }

	// Setup font data

	__SDL_PRIM_LOCKSURFACE(surface);
	mWorld = mfl_CreateContext(surface->pixels, surface->format->BitsPerPixel,
		surface->pitch, surface->w, mHeight);
	__SDL_PRIM_UNLOCKSURFACE(surface);

	EraseRect();
	SetClipRect();
}

void PixPort::Init( int inWidth, int inHeight, int inDepth )
{
	if ( inWidth < 0 ) inWidth = 0;
	if ( inHeight < 0 ) inHeight = 0;

	// Catch any invalid depth levels.
	if ( inDepth != 32 && inDepth != 16 && inDepth != 8 )
		inDepth = 8;

	// If we don't need to do anything, then don't do anything!
	if ( mWorld && surface->format->BitsPerPixel == inDepth && inWidth == surface->w && inHeight == surface->h )
		return;

	Init(SDL_CreateRGBSurface(SDL_SWSURFACE, inWidth, inHeight + 2, inDepth, 0, 0, 0, 0));
}

#define __clipPt( x, y )	\
	if ( x < mClipRect.left )			\
		x = mClipRect.left;				\
	else if ( x > mClipRect.right )		\
		x = mClipRect.right;			\
	if ( y < mClipRect.top )			\
		y = mClipRect.top;				\
	else if ( y > mClipRect.bottom )	\
		y = mClipRect.bottom;



#define __clipRect( inRect )			\
	Rect r = inRect;					\
	__clipPt( r.left, r.top )			\
	__clipPt( r.right, r.bottom )		\
	long width 	= r.right - r.left;		\
	long height = r.bottom - r.top;


long PixPort::GetPortColor( long inR, long inG, long inB )
{
	long c;

	if ( inR > 0xFFFF )	inR = 0xFFFF;
	if ( inG > 0xFFFF )	inG = 0xFFFF;
	if ( inB > 0xFFFF )	inB = 0xFFFF;
	if ( inR < 0 )		inR = 0;
	if ( inG < 0 )		inG = 0;
	if ( inB < 0 )		inB = 0;

	if ( surface->format->BitsPerPixel == 32 )
		c = __Clr32( inR, inG, inB );
	else if ( surface->format->BitsPerPixel == 16 )
		c = __Clr16( inR, inG, inB );
	else
		c = __Clr8( inR, inG, inB );

	return c;
}

long PixPort::SetBackColor( const RGBColor& RGB )
{
	mBackColor = GetPortColor( RGB );

	return mBackColor;
}

long PixPort::SetBackColor( long inR, long inG, long inB )
{
	mBackColor = GetPortColor( inR, inG, inB );

	return mBackColor;
}

void PixPort::SetPalette( PixPalEntry inPal[ 256 ] ) {

	if ( surface->format->BytesPerPixel != 1 )
		return;

	SDL_Color colors[256];

	for (int pp = 0; pp < 256 ; pp++) {
//		colors[pp].r = inPal[pp].red >> 2;
//		colors[pp].g = inPal[pp].green >> 2;
//		colors[pp].b = inPal[pp].blue >> 2;
		colors[pp].r = inPal[pp].red;
		colors[pp].g = inPal[pp].green;
		colors[pp].b = inPal[pp].blue;
	}

	SDL_SetPalette(surface, (SDL_LOGPAL | SDL_PHYSPAL), colors, 0, 256);
}


void PixPort::GaussBlur( int inBoxWidth, const Rect& inRect, void* inDestBits )
{
	bool need_lock = (inDestBits == NULL);

	// Don't let us draw in random parts of memory -- clip inRect
	__clipRect( inRect )

	if ( inBoxWidth <= 1 )
		return;

	// 3 box convolutions, 3 colors per pixel, 4 bytes per color
	long 	boxTempSize	= 36 * inBoxWidth;
	char*	tempBits	= 0;
	unsigned long*	boxTemp;
	long	imgOffset	= surface->format->BytesPerPixel * r.left + r.top * surface->pitch;
	long	bytesNeeded	= surface->pitch * surface->h + boxTempSize;


	// Resort to app's heap for temp mem if failed temp mem attempt or in win32
	tempBits = mBlurTemp.Dim( bytesNeeded );

	// Have the box temp and the pixel temp rgns use the same handle
	boxTemp = (unsigned long*) tempBits;
	tempBits += boxTempSize;

	if (need_lock) {
		__SDL_PRIM_LOCKSURFACE(surface);
		inDestBits = surface->pixels;
	}

	// Do a box blur on the x axis, transposing the source rgn to the dest rgn
	// Then o a box blur on the transposed image, effectively blurring the y cords, transposing it to the dest
	if ( surface->format->BytesPerPixel == 2 )  {
		BoxBlur16( ( (char*) surface->pixels + imgOffset), tempBits, inBoxWidth, width, height, surface->pitch, surface->format->BytesPerPixel*height, boxTemp, mBackColor );
		BoxBlur16( tempBits, ((char*) inDestBits + imgOffset), inBoxWidth, height, width, surface->format->BytesPerPixel*height, surface->pitch, boxTemp, mBackColor );  }
	else if ( surface->format->BytesPerPixel == 4 ) {
		BoxBlur32( ( (char*) surface->pixels + imgOffset), tempBits, inBoxWidth, width, height, surface->pitch, surface->format->BytesPerPixel*height, boxTemp, mBackColor );
		BoxBlur32( tempBits, ((char*) inDestBits + imgOffset), inBoxWidth, height, width, surface->format->BytesPerPixel*height, surface->pitch, boxTemp, mBackColor );
	}

	if (need_lock)
		__SDL_PRIM_UNLOCKSURFACE(surface);
}

void PixPort::CrossBlur( const Rect& inRect )
{
	// Don't let us draw in random parts of memory -- clip inRect
	__clipRect( inRect )

	// Don't access pixels directly without lock
	__SDL_PRIM_LOCKSURFACE(surface);

	// 3 box convolutions, 3 colors per pixel, 4 bytes per color
	long	imgOffset	= surface->format->BytesPerPixel * r.left + r.top * surface->pitch;

	unsigned char* tempBits = (unsigned char*) mBlurTemp.Dim( surface->w * 3 );

	if ( surface->format->BytesPerPixel == 2 )
		CrossBlur16( ( (char*) surface->pixels + imgOffset), width, height, surface->pitch, tempBits );
	else if ( surface->format->BytesPerPixel == 4 )
		CrossBlur32( ( (char*) surface->pixels + imgOffset), width, height, surface->pitch, tempBits );

	__SDL_PRIM_UNLOCKSURFACE(surface);

}

void PixPort::CopyBits( GrafPtr inPort, const Rect* inSrce, const Rect* inDest )
{
#define sf ((PixPort*) inPort)->surface
	SDL_Rect srcrect, destrect;
	int result;

	if ( (sf == SDL_GetVideoSurface()) &&
		(inSrce -> left <= inSrce -> right && inSrce -> top <= inSrce -> bottom &&
		inDest -> left <= inDest -> right && inDest -> top <= inDest -> bottom))
	{
		srcrect.x = inSrce -> left;
		srcrect.y = inSrce -> top;
		srcrect.w = inSrce -> right - inSrce -> left;
		srcrect.h = inSrce -> bottom - inSrce -> top;

		destrect.x = inDest -> left;
		destrect.y = inDest -> top;
		destrect.w = inDest -> right - inDest -> left;
		destrect.h = inDest -> bottom - inDest -> top;

		result = SDL_BlitSurface(surface, &srcrect, sf, &destrect);
	}
#undef sf
}


void PixPort::Line( int sx, int sy, int ex, int ey, long inColor ) {

	if ( surface->format->BytesPerPixel == 2 )
		Line16( sx, sy, ex, ey, inColor );
	else if ( surface->format->BytesPerPixel == 1 )
		Line8 ( sx, sy, ex, ey, inColor );
	else if ( surface->format->BytesPerPixel == 4 )
		Line32( sx, sy, ex, ey, inColor );
}


#define __ABS( n )  ( ( n > 0 ) ? (n) : (-n) )
#define CLR_LINE_THR	520

void PixPort::Line( int sx, int sy, int ex, int ey, const RGBColor& inS, const RGBColor& inE ) {
	long R, G, B, dR, dG, dB;

	R = inS.red;
	G = inS.green;
	B = inS.blue;
	dR = inE.red - R;
	dG = inE.green - G;
	dB = inE.blue - B;

	// If the endpoints have the same color, run the faster line procs (that just use one color)
	if (	dR > - CLR_LINE_THR && dR < CLR_LINE_THR &&
			dG > - CLR_LINE_THR && dG < CLR_LINE_THR &&
			dB > - CLR_LINE_THR && dB < CLR_LINE_THR ) {
		long color;

		if ( surface->format->BytesPerPixel == 2 ) {
			color = __Clr16( R, G, B );
			Line16( sx, sy, ex, ey, color ); }
		else if ( surface->format->BytesPerPixel == 4 ) {
			color = __Clr32( R, G, B );
			Line32( sx, sy, ex, ey, color ); }
		else if ( surface->format->BytesPerPixel == 1 ) {
			color = __Clr8( R, G, B );
			Line8 ( sx, sy, ex, ey, color );
		} }
	else {
		if ( surface->format->BytesPerPixel == 2 )
			Line16( sx, sy, ex, ey, inS, dR, dG, dB );
		else if ( surface->format->BytesPerPixel == 4 )
			Line32( sx, sy, ex, ey, inS, dR, dG, dB );
		else if ( surface->format->BytesPerPixel == 1 )
			Line8 ( sx, sy, ex, ey, inS.red, dR );
	}
}

long PixPort::CreateFont()
{
	PixTextStyle* newFont = new PixTextStyle;

	mFonts.Add( newFont );

	newFont -> mOSFontID = 0;

	return (long) newFont;
}

void PixPort::AssignFont(long inPixFontID, const char* inFontName, long inSize, long inStyleFlags )
{
	PixTextStyle* font = (PixTextStyle*) inPixFontID;

	font -> mFontName.Assign( inFontName );
	font -> mPointSize			= inSize;
	font -> mStyle				= inStyleFlags;
	font -> mOSStyle			= 0;
	font -> mDeviceLineHeight	= inSize;

	font -> mOSFontID = (long)mfl_LoadRawFont(SINGIT_DATA_DIR "/deffont");
//	g_print("%s: %li\n", SINGIT_DATA_DIR "/deffont", font -> mOSFontID);
}


void PixPort::SelectFont( long inPixFontID )
{
	// Exit if we're already in in this text face
	if ( inPixFontID == mCurFontID )
		return;

	mCurFontID = inPixFontID;
	PixTextStyle* font = (PixTextStyle*) inPixFontID;
	mDeviceLineHeight = font -> mDeviceLineHeight;

	mfl_SetFont(mWorld, (mfl_font) font->mOSFontID);
}

void PixPort::SetTextMode( PixDrawMode inMode )
{
	int mfl_mode;

	if (inMode == SRC_OR) { mfl_mode = MFL_OR; }
	else if (inMode == SRC_XOR) { mfl_mode = MFL_XOR; }
	else { mfl_mode = MFL_NORMAL; }

//	else if ( inMode == SRC_BIC )
//	  int mode = MFL_SETALL;

	mfl_SetDrawMode(mWorld, mfl_mode);
}

void PixPort::SetTextColor( RGBColor& inColor )
{
	mfl_SetTextColor(mWorld, 255);
}

void PixPort::SetTextColor( PixPalEntry& inColor )
{
	/* Palette index got stored */
	mfl_SetTextColor(mWorld, *((long *)&inColor) >> 24);
}

void PixPort::TextRect( const char* inStr, long& outWidth, long& outHeight )
{
	long width, pos;
	char c;

	outWidth  = 0;
	outHeight = 0;

	while ( *inStr ) {
		c = inStr[ 0 ];
		pos = 0;

		while ( c != '\r' && c ) {
			pos++;
			c = inStr[ pos ];
		}

		width = mfl_GetTextWidthL(mWorld, inStr, pos);

		if ( width > outWidth )
			outWidth = width;

		outHeight += mDeviceLineHeight;

		if ( c == 0 )
			break;

		inStr += pos + 1;
	}
}

void PixPort::DrawText( long inX, long inY, const char* inStr )
{
	long pos;
	char c;

	while ( *inStr ) {
		c = inStr[ 0 ];
		pos = 0;

		while ( c != '\r' && c ) {
			pos++;
			c = inStr[ pos ];
		}

		mfl_OutText8L(mWorld, inX, inY, inStr, pos);

		if ( c == 0 )
			break;

		inY += mDeviceLineHeight;
		inStr += pos + 1;
	}
}

void PixPort::SetLineWidth( long inLineWidth )
{
	if ( inLineWidth <= 0 )
		mLineWidth = 1;
	else if ( inLineWidth > MAX_LINE_WIDTH )
		mLineWidth = MAX_LINE_WIDTH;
	else
		mLineWidth = inLineWidth;
}

void PixPort::EraseRect( const Rect* inRect )
{
	SDL_Color black = { 0 };
	SDL_FillRect(surface, NULL, *((Uint32*) &black));
	SetClipRect();
}


#define P_SZ	1
#include "DrawXX.inc"


#undef P_SZ
#define P_SZ	2
#include "DrawXX.inc"


#undef P_SZ
#define P_SZ	4
#include "DrawXX.inc"


#define HALFCORD	0x007F  /* 16 bits per cord, 8 bits for fixed decimal, 8 bits for whole number */
#define FIXED_BITS	8



// Assembly note w/ branch prediction:  the first block is chosen to be more probable

#include <stdio.h>
void PixPort::Fade( const char* inSrce, char* inDest, long inBytesPerRow, long inX, long inY, unsigned long* grad )
{
	long x, y;
	unsigned long u, v, u1, v1, P1, P2, P3, P4, p;
	const char* srceMap;
	const char* srce;

	// Setup the source row base address and offset to allow for negative grad components
	srce = inSrce - HALFCORD * inBytesPerRow - HALFCORD;

	// Start writing to the image...
	for ( y = 0; y < inY; y++ ) {

		for ( x = 0; x < inX; x++ ) {

			// Format of each long:
			// High byte: x (whole part), High-low byte: x (frac part)
			// Low-high byte: y (whole part), Low byte: y (frac part)
			u1 = *grad;
			grad ++;

			p = 0;

			// 0xFFFFFFFF is a signal that this pixel is black.
			if ( u1 != 0xFFFFFFFF )	{

				// Note that we use casting 3 times as an unsigned char to (smartly) get the compiler to do masking for us
			  // FIXME: BUG: Assumption that bytes per row equals width.  Also see _G-Force\ Common/DeltaField.*
				srceMap = srce + ( u1 >> 14 );
				v = ( u1 >> 7 ) & 0x7F;		// frac part of x
				u = ( u1      ) & 0x7F;		// frac part of y

				// In the end, the pixel intensity will be 31/32 of its current (interpolated) value
				v *= 31;

				/* Bilinear interpolation to approximate the source pixel value... */
				/* P1 - P2  */
				/* |     |  */
				/* P3 - P4  */
				P1  = ( (unsigned char*) srceMap )[0];
				P2  = ( (unsigned char*) srceMap )[1];
				u1	= 0x80 - u;
				P1  *= u1;
				P2  *= u1;
				v1 	=  3968 - v;  //  3968 == 31 * 0x80
				P3  = ( (unsigned char*) srceMap )[ inBytesPerRow ];
				P4  = ( (unsigned char*) srceMap )[ inBytesPerRow + 1 ];
				P3 *= u;
				P4 *= u;

				/* We can now calc the intensity of the pixel (truncating the fraction part of the pix value)  */
				/* We divide by (7+7+5) decimal places because p is units squared (7 places per decimal) and 5 more dec places cuz of the mult by 31 */
				p  = ( v * ( P2 + P4 ) + v1 * ( P1 + P3 ) ) >> 19;
			}
			( (unsigned char*) inDest )[ x ] = p;
		}

		inDest	+= inBytesPerRow;
		srce	+= inBytesPerRow;
	}
}

void PixPort::Fade( PixPort& inDest, DeltaFieldData* inGrad )
{
	if ((!surface) || (!inDest.surface))
		{ return; }

	__SDL_PRIM_LOCKSURFACE(surface);
	__SDL_PRIM_LOCKSURFACE(inDest.surface);

	Fade((char*) surface->pixels, (char*) inDest.surface->pixels,
		surface->pitch, surface->w, mHeight, (unsigned long*) inGrad -> mField );

	__SDL_PRIM_UNLOCKSURFACE(inDest.surface);
	__SDL_PRIM_UNLOCKSURFACE(surface);
}
