#include <glib.h>
#include "blitting.h"


static gint BlitRoundIntegerF(gfloat x);

static gpointer BlitGetPixelFuncRGBA(gint pixel_op);
static void BlitPixelSetRGBA(guint32 *t, const guint32 *s);
static void BlitPixelSetAlphaRGBA(guint32 *t, const guint32 *s);
static void BlitPixelAlphaRGBA(guint32 *t, const guint32 *s);
static void BlitPixelAddRGBA(guint32 *t, const guint32 *s);
static void BlitPixelSubtractRGBA(guint32 *t, const guint32 *s);
static void BlitPixelBlendRGBA(guint32 *t, const guint32 *s);
static void BlitPixelMultiplyRGBA(guint32 *t, const guint32 *s);

void BlitClearRGBA(
	gint pixel_op,
	guint32 *tar, gint tar_width, gint tar_height, gint tar_bpl,
	guint32 v
);
void BlitImageScaleRGBA(
	gint pixel_op,
	guint32 *tar,
	gint tar_x, gint tar_y, gint tar_width, gint tar_height,
	gint tar_bpl,
	const guint32 *src,
	gint src_x, gint src_y, gint src_width, gint src_height,
	gint src_bpl,
	gfloat zoom_x, gfloat zoom_y
);


#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define ABSOLUTE(x)     (((x) < 0) ? ((x) * -1) : (x))


/*
 *	Returns x as the rounded off value to the nearest integer value.
 */
static gint BlitRoundIntegerF(gfloat x)
{
	return((gint)(x + 0.5));
}

/*
 *	Returns an opaque pointer to the BlitPixel*RGBA() function based
 *	on pixel_op.
 *
 *	This function always returns a valid function pointer.
 */
static gpointer BlitGetPixelFuncRGBA(gint pixel_op)
{
	switch(pixel_op)
	{
	  case BLIT_PIXEL_OP_SET:
	    return((gpointer)BlitPixelSetRGBA);
	    break;
	  case BLIT_PIXEL_OP_SET_ALPHA:
	    return((gpointer)BlitPixelSetAlphaRGBA);
	    break;
	  case BLIT_PIXEL_OP_ALPHA:
	    return((gpointer)BlitPixelAlphaRGBA);
	    break;
	  case BLIT_PIXEL_OP_ADD:
	    return((gpointer)BlitPixelAddRGBA);
	    break;
	  case BLIT_PIXEL_OP_SUBTRACT:
	    return((gpointer)BlitPixelSubtractRGBA);
	    break;
	  case BLIT_PIXEL_OP_BLEND:
	    return((gpointer)BlitPixelBlendRGBA);
	    break;
	  case BLIT_PIXEL_OP_MULTIPLY:
	    return((gpointer)BlitPixelMultiplyRGBA);
	    break;
	  default:
	    return((gpointer)BlitPixelSetRGBA);
	    break;
	}
}

/*
 *	Set target's value to the source's value.
 */
static void BlitPixelSetRGBA(guint32 *t, const guint32 *s)
{
	*t = *s;
}

/*
 *	Set target's value to the source's value using the source's
 *	alpha value.  The target's alpha value is not modified.
 */
static void BlitPixelSetAlphaRGBA(guint32 *t, const guint32 *s)
{
	guint8          *t8 = (guint8 *)t;
	const guint8    *s8 = (guint8 *)s;
	gfloat          ac_s = (gfloat)s8[3] / (gfloat)0xff,
			ac_t = 1.0f - ac_s;

	*t = (guint32)(
	    ((guint32)((s8[0] * ac_s) + (t8[0] * ac_t)) << 0) |
	    ((guint32)((s8[1] * ac_s) + (t8[1] * ac_t)) << 8) |
	    ((guint32)((s8[2] * ac_s) + (t8[2] * ac_t)) << 16) |
	    ((guint32)t8[3] << 24)
	);
}

/*
 *	Sets target's alpha value to the source's alpha value, RGB
 *	values are not modified.
 */
static void BlitPixelAlphaRGBA(guint32 *t, const guint32 *s)
{
	const guint8 *s8 = (guint8 *)s;
	*t = (guint32)(
	    ((*t) & 0x00ffffff) |
	    ((guint32)s8[3] << 24)
	);
}

/*
 *	Adds the source's RGB values to the target's RGB values, the
 *	alpha value is not modified.
 */
static void BlitPixelAddRGBA(guint32 *t, const guint32 *s)
{
	guint8          *t8 = (guint8 *)t;
	const guint8    *s8 = (guint8 *)s;
	gfloat          ac_s = (gfloat)s8[3] / (gfloat)0xff;

	*t = (guint32)(
((guint32)MIN((gint)(s8[0] * ac_s) + (gint)t8[0], 0xff) << 0) |
((guint32)MIN((gint)(s8[1] * ac_s) + (gint)t8[1], 0xff) << 8) |
((guint32)MIN((gint)(s8[2] * ac_s) + (gint)t8[2], 0xff) << 16) |
	    ((guint32)t8[3] << 24)
	);
}

/*
 *	Subtracts the source's RGB values from the target's RGB values,
 *	the alpha value is not modified.
 */
static void BlitPixelSubtractRGBA(guint32 *t, const guint32 *s)
{
	guint8          *t8 = (guint8 *)t;
	const guint8    *s8 = (guint8 *)s;
	gfloat          ac_s = (gfloat)s8[3] / (gfloat)0xff;

	*t = (guint32)(
((guint32)MAX((gint)t8[0] - (gint)(s8[0] * ac_s), 0x00) << 0) |
((guint32)MAX((gint)t8[1] - (gint)(s8[1] * ac_s), 0x00) << 8) |
((guint32)MAX((gint)t8[2] - (gint)(s8[2] * ac_s), 0x00) << 16) |
	    ((guint32)t8[3] << 24)
	);
}

/*
 *	Averages the source's RGB values with the target's RGB values,
 *	the source's alpha value is taken into consideration but the
 *	target's alpha value is not modified.
 */
static void BlitPixelBlendRGBA(guint32 *t, const guint32 *s)
{
	guint8          *t8 = (guint8 *)t;
	const guint8    *s8 = (guint8 *)s;
	gfloat          ac_s = (gfloat)s8[3] / (gfloat)0xff;

	*t = (guint32)(
	    ((((guint32)(s8[0] * ac_s) + (guint32)t8[0]) / 2) << 0) |
	    ((((guint32)(s8[1] * ac_s) + (guint32)t8[1]) / 2) << 8) |
	    ((((guint32)(s8[2] * ac_s) + (guint32)t8[2]) / 2) << 16) |
	    ((guint32)t8[3] << 24)
	);
}

/*
 *	Multiplies the source's RGB values with the target's RGB values,
 *	the source's alpha value is taken into consideration but the
 *	target's alpha value is not modified.
 */
static void BlitPixelMultiplyRGBA(guint32 *t, const guint32 *s)
{
	guint8          *t8 = (guint8 *)t;
	const guint8    *s8 = (guint8 *)s;
	gfloat          ac_s = (gfloat)s8[3] / (gfloat)0xff;

	*t = (guint32)(
	    (((guint32)(s8[0] * ac_s) * (guint32)t8[0] / 0xff) << 0) |
	    (((guint32)(s8[1] * ac_s) * (guint32)t8[1] / 0xff) << 8) |
	    (((guint32)(s8[2] * ac_s) * (guint32)t8[2] / 0xff) << 16) |
	    ((guint32)t8[3] << 24)
	);
}


/*
 *	Clear entire image buffer with the given value v, no blending.
 *
 *	RGBA 32 bits (4 bytes).
 */
void BlitClearRGBA(
	gint pixel_op,
	guint32 *tar, gint tar_width, gint tar_height, gint tar_bpl,
	guint32 v
)
{
	void (*pixel_func)(guint32 *, const guint32 *) =
	    BlitGetPixelFuncRGBA(pixel_op);


	if(tar == NULL)
	    return;

	if(tar_bpl <= 0)
	{
	    gint len = tar_width * tar_height;
	    guint32	*ptr = tar,
			*end = tar + len;
	    for(; ptr < end; ptr++)
		pixel_func(ptr, &v);
	}
	else
	{
	    gint x, y;
	    guint8	*tar8 = (guint8 *)tar,
			*ptr;

	    for(y = 0; y < tar_height; y++)
	    {
		for(x = 0; x < tar_width; x++)
		{
		    ptr = &(tar8[
			(y * tar_bpl) + (x * 4)
		    ]);
		    pixel_func((guint32 *)ptr, &v);
		}
	    }
	}
}

/*
 *	Copy two image buffer's rectangular areas with scaling.
 *
 *	RGBA 32 bits (4 bytes).
 */
void BlitImageScaleRGBA(
	gint pixel_op,
	guint32 *tar,
	gint tar_x, gint tar_y, gint tar_width, gint tar_height,
	gint tar_bpl,
	const guint32 *src,
	gint src_x, gint src_y, gint src_width, gint src_height,
	gint src_bpl,
	gfloat zoom_x, gfloat zoom_y
)
{
	void (*pixel_func)(guint32 *, const guint32 *) =
	    BlitGetPixelFuncRGBA(pixel_op);
	gint	src_sk_col,	/* Skips, in * 256 */
		src_sk_row;
	gint	src_cx,		/* Current position, in * 256 */
		src_cy,
		src_w,		/* Desired zoomed size, in * 256 */
		src_h,
		src_tw,		/* Allocation size, in * 256 */
		src_th;
	gint	tar_cx,		/* Current position, in * 256 */
		tar_cy,
		tar_w,		/* Desired zoomed size, in * 256 */
		tar_h,
		tar_tw,		/* Allocation size, in * 256 */
		tar_th;

	if((zoom_x <= 0.0f) || (zoom_y <= 0.0f) ||
	   (tar == NULL) || (src == NULL) ||
	   (tar_width <= 0) || (tar_height <= 0) ||
	   (src_width <= 0) || (src_height <= 0)
	)
	    return;

	/* Convert target and source starting positions to units of
	 * 256 and make sure they are not negative.
	 */
	tar_x = MAX(tar_x * 256, 0);
	tar_y = MAX(tar_y * 256, 0);
	src_x = MAX(src_x * 256, 0);
	src_y = MAX(src_y * 256, 0);

	/* Calculate column and row skips in units of 256, the skips
	 * skips must be positive or else no incrementation will
	 * occure and the loop will run infinately
	 */
	src_sk_col = MAX((gint)BlitRoundIntegerF(256.0f / zoom_x), 1);
	src_sk_row = MAX((gint)BlitRoundIntegerF(256.0f / zoom_y), 1);

	/* Calculate source and target buffer allocation size in
	 * units of 256
	 */
	src_tw = src_width * 256;
	src_th = src_height * 256;
	tar_tw = tar_width * 256;
	tar_th = tar_height * 256;

	/* Calculate visible source bounds based on the target
	 * bounds with zoom applied, in units of 256
	 */
	src_w = MIN((gint)(tar_tw / zoom_x) + src_x, src_tw);
	src_h = MIN((gint)(tar_th / zoom_y) + src_y, src_th);

	/* Calculate visible target bounds based on the source
	 * bounds with zoom applied, in units of 256
	 */
	tar_w = MIN((gint)(src_tw * zoom_x) + tar_x, tar_tw);
	tar_h = MIN((gint)(src_th * zoom_y) + tar_y, tar_th);

	/* Set source and target starting positions in units of 256 */
	src_cx = src_x;
	src_cy = src_y;
	tar_cx = tar_x;
	tar_cy = tar_y;

	/* Check if target and source rectangles are in bounds */
	if(((src_x + src_w) >= 0) && ((src_y + src_h) >= 0) &&
	   (src_x < src_w) && (src_y < src_h) &&
	   ((tar_x + tar_w) >= 0) && ((tar_y + tar_h) >= 0) &&
	   (tar_x < tar_w) && (tar_y < tar_h)
	)
	{
	    gint		src_bpp = 4,
				tar_bpp = 4;
	    const guint8	*src_ptr,
				*src_buf = (const guint8 *)src;
	    guint8		*tar_ptr,
				*tar_buf = (guint8 *)tar;


	    if(src_bpl <= 0)
		src_bpl = src_width * src_bpp;
	    if(tar_bpl <= 0)
		tar_bpl = tar_width * tar_bpp;

	    while((src_cy < src_h) &&
		  (tar_cy < tar_h)
	    )
	    {
		/* Get pointers to source and target pixels */
		src_ptr = &(src_buf[
		    ((src_cy >> 8) * src_bpl) +
		    ((src_cx >> 8) * src_bpp)
		]);
		tar_ptr = &(tar_buf[
		    ((tar_cy >> 8) * tar_bpl) +
		    ((tar_cx >> 8) * tar_bpp)
		]);

		/* Set pixel */
		pixel_func(
		    (guint32 *)tar_ptr, (const guint32 *)src_ptr
		);

		/* Increment target x column */
		tar_cx += 256;
		/* Go to next target line? */
		if(tar_cx >= tar_w)
		{
		    tar_cy += 256;
		    tar_cx = tar_x;

		    src_cy += src_sk_row;
		    src_cx = src_x;
		    continue;
		}

		/* Increment source x column */
		src_cx += src_sk_col;
		/* Go to next source line? */
		if(src_cx >= src_w)
		{
		    tar_cy += 256;
		    tar_cx = tar_x;

		    src_cy += src_sk_row;
		    src_cx = src_x;
		    continue;
		}
	    }
	}
}
