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

#ifdef	CHECKED_BUILD

/*
 * FIXME: there must be common place for such procs.
 */

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

#else	/* !CHECKED_BUILD */

#define	img_complain_no_memory(caller,failed_request)

#endif	/* !CHECKED_BUILD */

/*
 * 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)
	{
		img_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)
	{
		img_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;
}

V_LOCAL void
rgba_extract_one_channel(matrix_p mat,u8_p src)
{
	u32_t row;
	vec_p dst_ptr, end_ptr;
	vec_t scale = (vec_t)1 / (vec_t)255;
	for(row = 0;row < mat->rows;row++)
	{
		dst_ptr = mat->cell[row];
		end_ptr = dst_ptr + mat->cols;
		while(dst_ptr < end_ptr)
		{
			*dst_ptr = (vec_t)(*src) * scale;
			src += 4;
			dst_ptr++;
		}
	}
}

V_LOCAL void
rgba_insert_one_channel(matrix_p mat,u8_p dst)
{
	u32_t row;
	vec_p src_ptr, end_ptr;
	vec_t val, scale = 255;
	for(row = 0;row < mat->rows;row++)
	{
		src_ptr = mat->cell[row];
		end_ptr = src_ptr + mat->cols;
		while(src_ptr < end_ptr)
		{
			val = *src_ptr * scale;
			if(val < 0)
				val = 0;
			else if(val > 255)
				val = 255;
			*dst = (u8_t)val;
			dst += 4;
			src_ptr++;
		}
	}
}

/*
 * rgbaSplitChannels()
 */
vboolean V_GLOBAL
rgbaSplitChannels(matrix_p r,matrix_p g,matrix_p b,matrix_p a,
	u8_p src,u32_t width,u32_t height)
{
	CALLER_NAME(rgbaSplitChannels)
	DEBUG_MESSAGE_INCORRECT()
	DEBUG_MESSAGE(wrong_dimensions,"wrong matix dimensions")
	if(!src || !width || !height)
	{
		ERROR_GENERIC(incorrect_parameters);
		return false;
	}
	if((r && (r->rows != height || r->cols != width)) ||
		(g && (g->rows != height || g->cols != width)) ||
		(b && (b->rows != height || b->cols != width)) ||
		(a && (a->rows != height || a->cols != width)))
	{
		ERROR_GENERIC(wrong_dimensions);
		return false;
	}
	if(r)
		rgba_extract_one_channel(r, src);
	if(g)
		rgba_extract_one_channel(g, src + 1);
	if(b)
		rgba_extract_one_channel(b, src + 2);
	if(a)
		rgba_extract_one_channel(a, src + 3);
	return true;
}

/*
 * rgbaCombineChannels()
 */
vboolean V_GLOBAL
rgbaCombineChannels(matrix_p r,matrix_p g,matrix_p b,matrix_p a,
	u8_p dst,u32_t width,u32_t height)
{
	CALLER_NAME(rgbaCombineChannels)
	DEBUG_MESSAGE_INCORRECT()
	DEBUG_MESSAGE(wrong_dimensions,"wrong matix dimensions")
	if(!dst || !width || !height)
	{
		ERROR_GENERIC(incorrect_parameters);
		return false;
	}
	if((r && (r->rows != height || r->cols != width)) ||
		(g && (g->rows != height || g->cols != width)) ||
		(b && (b->rows != height || b->cols != width)) ||
		(a && (a->rows != height || a->cols != width)))
	{
		ERROR_GENERIC(wrong_dimensions);
		return false;
	}
	if(r)
		rgba_insert_one_channel(r, dst);
	if(g)
		rgba_insert_one_channel(g, dst + 1);
	if(b)
		rgba_insert_one_channel(b, dst + 2);
	if(a)
		rgba_insert_one_channel(a, dst + 3);
	return true;
}

/*
 * rgbaScale()
 */

u8_p V_GLOBAL
rgbaScale(u8_t *src,u32_t width,u32_t height,
	u32_t new_width,u32_t new_height,vboolean keepon)
{
	CALLER_NAME(rgbaScale)
	DEBUG_MESSAGE_INCORRECT()
	vhandle_t resampler;
	matrix_p r,g,b,a,rr,rg,rb,ra;
	if(!src || !width || !height || !new_width || !new_height)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s(%X,(%ux%u)->(%ux%u)): %s.\n",
			caller_name,src,width,height,new_width,new_height,
			incorrect_parameters);
#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)
		{
			img_complain_no_memory("imgScale",src_size);
			return NULL;
		}
		memCopy(src_copy,src,src_size);
		src = src_copy;
	}
	if((new_width << 1) == width && (new_height << 1) == height)
		return rgba8_halve_2D(src,width,height);
	if((height == 1) && (new_height == 1) && (new_width << 1) == width)
		return rgba8_halve_1D(src,width);
	if((width == 1) && (new_width == 1) && (new_height << 1) == height)
		return rgba8_halve_1D(src,height);
	/*
	 * Slow general-case resampling
	 */
	resampler = matCreateResampleContext(height,width,new_height,new_width);
	if(resampler == LIBV_INVALID_HANDLE)
	{
		memFree(src);
		return NULL;
	}
	r = matAlloc(height, width);
	g = matAlloc(height, width);
	b = matAlloc(height, width);
	a = matAlloc(height, width);
	rr = matAlloc(new_height, new_width);
	rg = matAlloc(new_height, new_width);
	rb = matAlloc(new_height, new_width);
	ra = matAlloc(new_height, new_width);
	rgbaSplitChannels(r,g,b,a,src,width,height);
	matResample(resampler,rr,r);
	matResample(resampler,rg,g);
	matResample(resampler,rb,b);
	matResample(resampler,ra,a);
	matDestroyResampleContext(resampler);
	memFree(r);
	memFree(g);
	memFree(b);
	memFree(a);
	memFree(src);
	src = (u8_p)memAlloc(4 * new_width * new_height);
	rgbaCombineChannels(rr,rg,rb,ra,src,new_width,new_height);
	memFree(rr);
	memFree(rg);
	memFree(rb);
	memFree(ra);
	return src;
}

vboolean V_GLOBAL
rgbaLoadChannels(char *file_name,
	matrix_p *r_ret,matrix_p *g_ret,matrix_p *b_ret,matrix_p *a_ret)
{
	CALLER_NAME(rgbaLoadChannels)
	DEBUG_MESSAGE_INCORRECT()
	u8_p pixels;
	u32_t width, height;
	if(!file_name)
	{
		ERROR_GENERIC(incorrect_parameters);
		return false;
	}
	pixels = rgbaLoad(file_name,&width,&height);
	if(!pixels)
		return false;
	if(r_ret)
	{
		*r_ret = matAlloc(height,width);
		rgba_extract_one_channel(*r_ret, pixels);
	}
	if(g_ret)
	{
		*g_ret = matAlloc(height,width);
		rgba_extract_one_channel(*g_ret, pixels + 1);
	}
	if(b_ret)
	{
		*b_ret = matAlloc(height,width);
		rgba_extract_one_channel(*b_ret, pixels + 2);
	}
	if(a_ret)
	{
		*a_ret = matAlloc(height,width);
		rgba_extract_one_channel(*a_ret, pixels + 3);
	}
	memFree(pixels);
	return true;
}

vboolean V_GLOBAL
rgbaSaveChannels(char *file_name,matrix_p r,matrix_p g,matrix_p b,matrix_p a)
{
	CALLER_NAME(rgbaSaveChannels)
	DEBUG_MESSAGE_INCORRECT()
	DEBUG_MESSAGE(different_channels,"different dimensions of source channels")
	u8_p pixels;
	matrix_p rgba[4];
	vboolean result;
	u32_t i, width = 0, height = 0;
	rgba[0] = r;
	rgba[1] = g;
	rgba[2] = b;
	rgba[3] = a;
	for(i = 0;i < 4;i++)
	{
		if(!rgba[i])
			continue;
		if(!width)
		{
			width = rgba[i]->cols;
			height = rgba[i]->rows;
		}
		else
		{
			if((width != rgba[i]->cols) || (height != rgba[i]->rows))
			{
				ERROR_GENERIC(different_channels);
				return false;
			}
		}
	}
	if(!file_name || !width || !height)
	{
		ERROR_GENERIC(incorrect_parameters);
		return false;
	}
	pixels = (u8_p)memAllocZ(width * height * 4);
	for(i = 0;i < 4;i++)
	{
		if(!rgba[i])
			continue;
		rgba_insert_one_channel(rgba[i],pixels + i);
	}
	result = rgbaSave(file_name,width,height,pixels);
	memFree(pixels);
	return result;
}

