#include <../source/TAL/objbase.h>

#ifdef	CHECKED_BUILD

V_LOCAL void
complain_no_memory(char *caller,vsize_t failed_request)
{
	conlPrintf(LOG_ERROR,"%s(): memAlloc(%u) failed.\n",
		caller,failed_request);
}

#else	/* !CHECKED_BUILD */

#define	complain_no_memory(caller,failed_request)

#endif	/* !CHECKED_BUILD */

/*
 * Bilinear magnification routines
 */

/*
 * Replaces m x n image by m x (2n-1) image.
 */
V_LOCAL u8_p
rgba8_increase_width(u8_t *src,u32_t width,u32_t height)
{
	u8_p src_ptr, dst_ptr;
	u32_t nw = 2 * width - 1;
	u8_p dst = (u8_p)memAlloc(nw * height * 4);
	u32_t r, c;
	u8_p c1, c2;
	u32_t stride = nw << 2;
	if(!dst)
	{
		complain_no_memory("rgba8_increase_width",nw * height * 4);
		memFree(src);
		return NULL;
	}
	/* fill nodes */
	for(r = 0;r < height;r++)
	{
		src_ptr = src + (r * width << 2);
		dst_ptr = dst + (r * stride);
		for(c = 0;c < width; c++)
		{
			*((u32_p)dst_ptr) = *((u32_p)src_ptr);
			dst_ptr += 8;
			src_ptr += 4;
		}
	}
	/* column interpolation */
	for(r = 0;r < height;r++)
	{
		c1 = dst + (r * stride);
		dst_ptr = c1 + 4;
		for(c = 1;c < width; c++)
		{
			c2 = dst_ptr + 4;
			/* lerp color */
			dst_ptr[0] = (u8_t)((c1[0] + c2[0]) / 2);
			dst_ptr[1] = (u8_t)((c1[1] + c2[1]) / 2);
			dst_ptr[2] = (u8_t)((c1[2] + c2[2]) / 2);
			dst_ptr[3] = (u8_t)((c1[3] + c2[3]) / 2);
			c1 = c2;
			dst_ptr = c1 + 4;
		}
	}
	memFree(src);
	return dst;
}

/*
 * Replaces m x n image by (2m-1) x n image
 */
V_LOCAL u8_p
rgba8_increase_height(u8_t *src,u32_t width,u32_t height)
{
	u8_p src_ptr, dst_ptr;
	u32_t nh = 2 * height - 1;
	u8_p dst = (u8_p)memAlloc(width * nh * 4);
	u32_t r, c;
	u8_p c1, c2;
	u32_t stride = width << 2;
	if(!dst)
	{
		complain_no_memory("rgba8_increase_height",width * nh * 4);
		memFree(src);
		return NULL;
	}
	/* fill nodes */
	for(r = 0;r < height;r++)
	{
		src_ptr = src + (r * stride);
		dst_ptr = dst + ((r << 1) * stride);
		for(c = 0;c < width; c++)
		{
			*((u32_p)dst_ptr) = *((u32_p)src_ptr);
			dst_ptr += 4;
			src_ptr += 4;
		}
	}
	/* row interpolation */
	for(c = 0;c < width;c++)
	{
		for(r = 1;r < nh;r += 2)
		{
			dst_ptr = dst + ((r * width + c) << 2);
			c1 = dst_ptr - stride;
			c2 = dst_ptr + stride;
			dst_ptr[0] = (u8_t)((c1[0] + c2[0]) / 2);
			dst_ptr[1] = (u8_t)((c1[1] + c2[1]) / 2);
			dst_ptr[2] = (u8_t)((c1[2] + c2[2]) / 2);
			dst_ptr[3] = (u8_t)((c1[3] + c2[3]) / 2);
		}
	}
	memFree(src);
	return dst;
}

/*
 * Replaces m x n image by (2m-1) x (2n-1) image
 */
V_LOCAL u8_p
rgba8_increase_width_height(u8_t *src,u32_t width,u32_t height)
{
	u8_p src_ptr, dst_ptr;
	u32_t nw = 2 * width - 1, nh = 2 * height - 1;
	u8_p dst = (u8_p)memAlloc(nw * nh * 4);
	u32_t r, c;
	u8_p c1, c2;
	u8_p box[9];
	u32_t stride = nw << 2;
	if(!dst)
	{
		complain_no_memory("rgba8_increase_width_height",nw * nh * 4);
		memFree(src);
		return NULL;
	}
	/* fill nodes */
	for(r = 0;r < height;r++)
	{
		src_ptr = src + ((r * width) << 2);
		dst_ptr = dst + ((r << 1) * stride);
		for(c = 0;c < width; c++)
		{
			*((u32_p)dst_ptr) = *((u32_p)src_ptr);
			dst_ptr += 8;
			src_ptr += 4;
		}
	}
	/* column interpolation */
	for(r = 0;r < height;r++)
	{
		c1 = dst + ((r << 1) * stride);
		dst_ptr = c1 + 4;
		for(c = 1;c < width; c++)
		{
			c2 = dst_ptr + 4;
			dst_ptr[0] = (u8_t)((c1[0] + c2[0]) / 2);
			dst_ptr[1] = (u8_t)((c1[1] + c2[1]) / 2);
			dst_ptr[2] = (u8_t)((c1[2] + c2[2]) / 2);
			dst_ptr[3] = (u8_t)((c1[3] + c2[3]) / 2);
			c1 = c2;
			dst_ptr = c1 + 4;
		}
	}
	/* row interpolation */
	for(c = 0;c < nw;c += 2)
	{
		for(r = 1;r < nh;r += 2)
		{
			dst_ptr = dst + ((r * nw + c) << 2);
			c1 = dst_ptr - stride;
			c2 = dst_ptr + stride;
			dst_ptr[0] = (u8_t)((c1[0] + c2[0]) / 2);
			dst_ptr[1] = (u8_t)((c1[1] + c2[1]) / 2);
			dst_ptr[2] = (u8_t)((c1[2] + c2[2]) / 2);
			dst_ptr[3] = (u8_t)((c1[3] + c2[3]) / 2);
		}
	}
	/* midpoint interpolation */
	for(r = 1;r < nh;r += 2)
	{
		for(c = 0;c < nw; c+= 2)
		{
			box[3] = dst + ((r * nw + c) << 2);
			box[4] = box[3] + 4;
			box[5] = box[4] + 4;
			box[0] = box[3] - stride;
			box[1] = box[4] - stride;
			box[2] = box[5] - stride;
			box[6] = box[3] + stride;
			box[7] = box[4] + stride;
			box[8] = box[5] + stride;
			/*
			 * The filter coefficients are taken from discrete
			 * Laplace operator (i.e. "no color source" - color
			 * field must satisfy the Laplace equation).
			 */
			box[4][0] = (u8_t)
				((4 * (box[1][0]+box[3][0]+box[5][0]+box[7][0]) +
				 (box[0][0]+box[2][0]+box[6][0]+box[8][0]))/20);
			box[4][1] = (u8_t)
				((4 * (box[1][1]+box[3][1]+box[5][1]+box[7][1]) +
				 (box[0][1]+box[2][1]+box[6][1]+box[8][1]))/20);
			box[4][2] = (u8_t)
				((4 * (box[1][2]+box[3][2]+box[5][2]+box[7][2]) +
				 (box[0][2]+box[2][2]+box[6][2]+box[8][2]))/20);
			box[4][3] = (u8_t)
				((4 * (box[1][3]+box[3][3]+box[5][3]+box[7][3]) +
				 (box[0][3]+box[2][3]+box[6][3]+box[8][3]))/20);
		}
	}
	memFree(src);
	return dst;
}

/*
 * Box-filtered minification routines
 */

/*
 * Replaces 1 x n image by 1 x (n/2) image; n must be even.
 */
V_LOCAL u8_p
rgba8_halve_1D(u8_t *src,u32_t width)
{
	u32_t r,g,b,a;
	u8_p dst, dptr, sptr, src_endptr;
	V_ASSERT(!(width & 1) && width>1);
	src_endptr = src + width * 4;
	width >>= 1;
	dst = (u8_p)memAlloc(width * 4);
	if(!dst)
	{
		complain_no_memory("rgba8_halve_1D",width * 4);
		memFree(src);
		return NULL;
	}
	dptr = dst;
	for(sptr = src;sptr < src_endptr;sptr+=8,dptr+=4)
	{
		r = sptr[0] + sptr[4];
		g = sptr[1] + sptr[5];
		b = sptr[2] + sptr[6];
		a = sptr[3] + sptr[7];
		dptr[0]=(u8_t)(r >> 1);
		dptr[1]=(u8_t)(g >> 1);
		dptr[2]=(u8_t)(b >> 1);
		dptr[3]=(u8_t)(a >> 1);
	}
	memFree(src);
	return dst;
}

/*
 * Replaces m x n image by (m/2) x (n/2) image.
 * Both m and n _must_ be even.
 */
V_LOCAL u8_p
rgba8_halve_2D(u8_t *src,u32_t width,u32_t height)
{
	u32_t dest_r,dest_c,dst_stride,src_stride;
	u8_p sptr1, sptr2,dptr,dest;
	u32_t r,g,b,a;
	V_ASSERT(!(width & 1) && !(height & 1));
	V_ASSERT(width>1 && height>1);
	src_stride = width * 4;
	dst_stride = width;
	/* four times smaller than original */
	dest = (u8_p)memAlloc(width * height);
	if(!dest)
	{
		complain_no_memory("rgba8_halve_2D",width * height);
		memFree(src);
		return NULL;
	}
	width >>= 1;
	height >>= 1;
	dptr = dest;
	for(dest_r = 0;dest_r < height;dest_r++)
	{
		sptr1 = src + (dest_r << 1) * src_stride;
		sptr2 = sptr1 + src_stride;
		for(dest_c = 0;dest_c < width;dest_c++)
		{
			r = sptr1[0] + sptr1[4] + sptr2[0] + sptr2[4];
			g = sptr1[1] + sptr1[5] + sptr2[1] + sptr2[5];
			b = sptr1[2] + sptr1[6] + sptr2[2] + sptr2[6];
			a = sptr1[3] + sptr1[7] + sptr2[3] + sptr2[7];

			dptr[0] = (u8_t)(r >> 2);
			dptr[1] = (u8_t)(g >> 2);
			dptr[2] = (u8_t)(b >> 2);
			dptr[3] = (u8_t)(a >> 2);

			dptr += 4;
			sptr1 += 8;
			sptr2 += 8;
		}
	}
	memFree(src);
	return dest;
}

/*
 * I tried SGI code (si-glu/libutil/mipmap.c) for this purpose,
 * but fragment
	for(;;)
	{
		nw = (unsigned int)(w * 1.1);
		nh = (unsigned int)(h * 1.1);
		if(nw >= target_width || nh >= target_height)
			break;
		printf("%ux%u -> %ux%u\n",w,h,nw,nh);
		scale_internal_ubyte(4,width,height,src,nw,nh,dest,1,width*4,4);
		... realloc 'src' and 'dest' ...
		w = nw;
		h = nh;
	}
 * sooner or later causes SIGSEGV. It seems that SGI
 * implementation of scale_internal_ubyte() is buggy;
 * later I encounter exactly the same problem in my
 * implementation. Adding extra checks to avoid access
 * past the last row fixes problem.
 */

typedef struct box_filter_s
{
	u8_p	src_pixels;
	u32_t	src_width;
	u32_t	src_height;
	vec_t	x1, y1;
	vec_t	x2, y2;
	vec_t	sx, sy, r_area; /* r_area = 1 / (sx * sy) */
	u32_t	first_col, last_col, first_row, last_row;
	u32_t	r,  c;
	vec_t	coverage;
	vec4_t	filtered;
}	box_filter_t, *box_filter_p;

V_LOCAL	void
box_filter_coverage(box_filter_p arg)
{
	vec_t c1 = arg->c;
	vec_t r1 = arg->r;
	vec_t c2 = c1 + 1;
	vec_t r2 = r1 + 1;
	vec_t l,t,r,b;
	l = (arg->x1 < c1) ? c1 : arg->x1;
	r = (arg->x2 > c2) ? c2 : arg->x2;
	t = (arg->y1 < r1) ? r1 : arg->y1;
	b = (arg->y2 > r2) ? r2 : arg->y2;
	arg->coverage = (b-t) * (r-l);
}

V_LOCAL void
rgba8_box_filter(box_filter_p arg)
{
	u8_p ptr;
	vec4_t rgba, sum;
	arg->x2 = arg->x1 + arg->sx;
	arg->y2 = arg->y1 + arg->sy;
	arg->first_col = (u32_t)(floor(arg->x1));
	arg->first_row = (u32_t)(floor(arg->y1));
	arg->last_col = (u32_t)(floor(arg->x2));
	if(IsEqualEpsilon(arg->x2,arg->last_col,(vec_t)0.01))
	{
		/* the same checks have to be added to SGI code */
		if(arg->last_col >= arg->src_width)
			arg->last_col = arg->src_width - 1;
		else
			arg->last_col--;
		if(arg->first_col > arg->last_col)
			arg->first_col = arg->last_col;
	}
	arg->last_row = (u32_t)(floor(arg->y2));
	if(IsEqualEpsilon(arg->y2,arg->last_row,(vec_t)0.01))
	{
		/* the same checks have to be added to SGI code */
		if(arg->last_row >= arg->src_height)
			arg->last_row = arg->src_height - 1;
		else
			arg->last_row--;
		if(arg->first_row > arg->last_row)
			arg->first_row = arg->last_row;
	}
	vec4Zero(sum);
	for(arg->r=arg->first_row;arg->r<=arg->last_row;arg->r++)
	{
		ptr = arg->src_pixels + 
			((arg->r * arg->src_width + arg->first_col) << 2);
		for(arg->c=arg->first_col;arg->c<=arg->last_col;arg->c++,ptr+=4)
		{
			box_filter_coverage(arg);
			rgbaFromByte(rgba,ptr);
			vec4Scale(rgba,rgba,arg->coverage);
			vec4Add(sum,sum,rgba);
		}
	}
	vec4Scale(arg->filtered,sum,arg->r_area);
}

V_LOCAL u8_p
rgba8_resize(u8_p src,u32_t width,u32_t height,
	u32_t new_width,u32_t new_height)
{
	u8_p ptr;
	u32_t dest_r, dest_c;
	box_filter_t arg;
	u32_t cksize = new_width * new_height * 4;
	u8_p pixels = (u8_p)memAlloc(cksize);
	arg.src_pixels = src;
	arg.src_width = width;
	arg.src_height = height;
	arg.sx = (vec_t)width / (vec_t)new_width;
	arg.sy = (vec_t)height / (vec_t)new_height;
	arg.r_area = 1 / (arg.sx * arg.sy);
	arg.y1 = 0;
	if(!pixels)
	{
		complain_no_memory("rgba8_resize",cksize);
		memFree(src);
		return NULL;
	}
	for(dest_r=0;dest_r < new_height;dest_r++)
	{
		ptr = pixels + ((dest_r * new_width) << 2);
		arg.x1 = 0;
		for(dest_c=0;dest_c<new_width;dest_c++)
		{
			rgba8_box_filter(&arg);
			rgbaByteFromFloat(ptr,arg.filtered);
			ptr += 4;
			arg.x1 += arg.sx;
		}
		arg.y1 += arg.sy;
	}
	memFree(src);
	return pixels;
}

u8_p V_GLOBAL
imgScale(u8_t *src,u32_t width,u32_t height,
	u32_t new_width,u32_t new_height,vboolean keepon)
{
	u32_t upsample_width_steps, upsample_height_steps;
	if(!src || !width || !height || !new_width || !new_height)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"imgScale(%X,(%ux%u)->(%ux%u)): incorrect parameter(s).\n",
			src,width,height,new_width,new_height);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	if(keepon)
	{
		u32_t src_size = width * height * 4;
		u8_p src_copy = (u8_p)memAlloc(src_size);
		if(!src_copy)
		{
			complain_no_memory("imgScale",src_size);
			return NULL;
		}
		memCopy(src_copy,src,src_size);
		src = src_copy;
	}
	/* Check if 1D case */
	if(height == 1 && new_height == 1)
	{
		if((new_width << 1) == width)
			src = rgba8_halve_1D(src,width);
		else if(new_width != width)
			src = rgba8_resize(src,width,height,new_width,new_height);
		return src;
	}
	if(width == 1 && new_width == 1)
	{
		if((new_height << 1) == height)
			src = rgba8_halve_1D(src,height);
		else if(new_height != height)
			src = rgba8_resize(src,width,height,new_width,new_height);
		return src;
	}
	/*
	 * Apply minification filters
	 */
    while((width >> 1) >= new_width && (height >> 1) >= new_height)
    {
    	src = rgba8_halve_2D(src,width,height);
    	width >>= 1;
    	height >>= 1;
    	if(!src)
    		return NULL;
    }
	/*
	 * Apply magnification filters
	 */
	upsample_width_steps = 1;
	while((width + upsample_width_steps) < new_width && 
		(height + upsample_width_steps) < new_height)
	{
		src = rgba8_increase_width_height(src,width,height);
		width = 2 * width - 1;
		height = 2 * height - 1;
    	if(!src)
    		return NULL;
    	upsample_width_steps++;
	}
	upsample_height_steps = upsample_width_steps;
	while((width + upsample_width_steps) < new_width)
	{
		src = rgba8_increase_width(src,width,height);
		width = 2 * width - 1;
    	if(!src)
    		return NULL;
    	upsample_width_steps++;
	}
	while((height + upsample_height_steps) < new_height)
	{
		src = rgba8_increase_height(src,width,height);
		height = 2 * height - 1;
    	if(!src)
    		return NULL;
    	upsample_height_steps++;
	}
	/* Final adjustment */
	if(width != new_width || height != new_height)
		src = rgba8_resize(src,width,height,new_width,new_height);
	return src;
}
