#include <v_lib.h>
#include "eval.h"
#include "cspace.h"
#include "messages.h"

typedef struct image_input_s
{
	char		*id;
	matrix_p	rgba[4];
	matrix_p	hsv[3];
	matrix_p	cie[3];
} image_input_t, *image_input_p;

V_LOCAL vp_stack_t	image_cache = TYPED_STACK_EMPTY_INITIALIZER;

V_LOCAL void
rgbFromCie(vec3_t rgb_ret,vec3_t cie)
{
	static matrix3_t cie2rgb_transform={
		{2.739,-1.145,-0.424},
		{-1.11,2.029,0.033},
		{0.138,-0.333,1.105}};
	mat3VecMul(rgb_ret,cie2rgb_transform,cie);
}

V_LOCAL void
cieFromRgb(vec3_t cie_ret,vec3_t rgb)
{
	static matrix3_t rgb2cie_transform={
        {0.477,0.298,0.174},
        {0.261,0.653,0.081},
        {0.019,0.160,0.907}};
	mat3VecMul(cie_ret,rgb2cie_transform,rgb);
}

V_LOCAL void
ctransCieFromRgb(matrix_p cie[],matrix_p rgb[])
{
	u32_t i, j;
	vec3_t rgbv, ciev;
	for(i = 0;i < 3;i++)
	{
		if(cie[i])
			memFree(cie[i]);
		cie[i] = matAlloc(rgb[0]->rows,rgb[0]->cols);
	}
	for(i = 0;i < rgb[0]->rows;i++)
	{
		for(j = 0;j < rgb[0]->cols;j++)
		{
			rgbv[0] = rgb[0]->cell[i][j];
			rgbv[1] = rgb[1]->cell[i][j];
			rgbv[2] = rgb[2]->cell[i][j];
			cieFromRgb(ciev,rgbv);
			cie[0]->cell[i][j] = ciev[0];
			cie[1]->cell[i][j] = ciev[1];
			cie[2]->cell[i][j] = ciev[2];
		}
	}
}

V_LOCAL void
ctransRgbFromCie(matrix_p rgb[],matrix_p cie[])
{
	u32_t i, j;
	vec3_t rgbv, ciev;
	for(i = 0;i < 3;i++)
	{
		if(rgb[i])
			memFree(rgb[i]);
		rgb[i] = matAlloc(cie[0]->rows,cie[0]->cols);
	}
	for(i = 0;i < cie[0]->rows;i++)
	{
		for(j = 0;j < cie[0]->cols;j++)
		{
			ciev[0] = cie[0]->cell[i][j];
			ciev[1] = cie[1]->cell[i][j];
			ciev[2] = cie[2]->cell[i][j];
			rgbFromCie(rgbv,ciev);
			rgb[0]->cell[i][j] = rgbv[0];
			rgb[1]->cell[i][j] = rgbv[1];
			rgb[2]->cell[i][j] = rgbv[2];
		}
	}
}

V_LOCAL void
ctransHsvFromRgb(matrix_p hsv[],matrix_p rgb[])
{
	u32_t i, j;
	vec3_t rgbv, hsvv;
	for(i = 0;i < 3;i++)
	{
		if(hsv[i])
			memFree(hsv[i]);
		hsv[i] = matAlloc(rgb[0]->rows,rgb[0]->cols);
	}
	for(i = 0;i < rgb[0]->rows;i++)
	{
		for(j = 0;j < rgb[0]->cols;j++)
		{
			rgbv[0] = rgb[0]->cell[i][j];
			rgbv[1] = rgb[1]->cell[i][j];
			rgbv[2] = rgb[2]->cell[i][j];
			hsvFromRgb(hsvv,rgbv);
			hsv[0]->cell[i][j] = hsvv[0];
			hsv[1]->cell[i][j] = hsvv[1];
			hsv[2]->cell[i][j] = hsvv[2];
		}
	}
}

V_LOCAL void
ctransRgbFromHsv(matrix_p rgb[],matrix_p hsv[])
{
	u32_t i, j;
	vec3_t rgbv, hsvv;
	for(i = 0;i < 3;i++)
	{
		if(rgb[i])
			memFree(rgb[i]);
		rgb[i] = matAlloc(hsv[0]->rows,hsv[0]->cols);
	}
	for(i = 0;i < hsv[0]->rows;i++)
	{
		for(j = 0;j < hsv[0]->cols;j++)
		{
			hsvv[0] = hsv[0]->cell[i][j];
			hsvv[1] = hsv[1]->cell[i][j];
			hsvv[2] = hsv[2]->cell[i][j];
			rgbFromHsv(rgbv,hsvv);
			rgb[0]->cell[i][j] = rgbv[0];
			rgb[1]->cell[i][j] = rgbv[1];
			rgb[2]->cell[i][j] = rgbv[2];
		}
	}
}

void
outputSaveChannels(char *file_name,u32_t orig_cspace,
	matrix_p channels[],eval_state_p state)
{
	CALLER_NAME(outputSaveChannels)
	int i;
   	vhandle_t resampler;
   	u32_t target_width, target_height;
	matrix_p rgba[4] = {NULL,NULL,NULL,NULL};
   	matrix_p resampled[4] = {NULL,NULL,NULL,NULL};
	matrix_p *cptr = rgba;
	switch(orig_cspace)
	{
		case CSPACE_HSV:
			ctransRgbFromHsv(rgba,channels);
			break;
		case CSPACE_CIE:
			ctransRgbFromCie(rgba,channels);
			break;
		case CSPACE_RGB:
			cptr = channels;
			break;
		default:
			ERROR_GENERIC(ipl_wrong_colorspace);
			return;
			break;
	}
	/* resampling */
    if(state->custom_width || state->custom_height)
    {
    	if(state->custom_width)
    		target_width = state->custom_width;
    	else
    		target_width = cptr[0]->cols;
    	if(state->custom_height)
    		target_height = state->custom_height;
    	else
    		target_height = cptr[0]->rows;
    	resampler = matCreateResampleContext(cptr[0]->rows,cptr[0]->cols,
    		target_height,target_width);
    	for(i = 0;i < 4;i++)
    	{
    		if(!cptr[i])
    		{
    			resampled[i] = NULL;
    			continue;
    		}
    		resampled[i] = matAlloc(target_height,target_width);
    		matResample(resampler,resampled[i],cptr[i]);
    	}
    	matDestroyResampleContext(resampler);
    	cptr = resampled;
    }
	rgbaSaveChannels(file_name,cptr[0],cptr[1],cptr[2],cptr[3]);
	for(i = 0;i < 4;i++)
	{
		if(rgba[i])
			memFree(rgba[i]);
		if(resampled[i])
			memFree(resampled[i]);
	}
}

V_LOCAL vboolean
inputResizePic(image_input_p pic,eval_state_p state)
{
	u32_t i;
	matrix_p m;
	vhandle_t resampler;
	u32_t target_width = pic->rgba[0]->cols;
	u32_t target_height = pic->rgba[0]->rows;
	if(state->custom_width)
		target_width = state->custom_width;
	if(state->custom_height)
		target_height = state->custom_height;
	if(state->input_square)
	{
		u32_t max_size = (target_width > target_height) ? target_width : target_height;
		target_width = target_height = max_size;
	}
	if(state->input_powers2)
	{
		NOT_LESS_POWER_TWO(target_width,target_width,u32_t);
		NOT_LESS_POWER_TWO(target_height,target_height,u32_t);
	}
	if(target_height != pic->rgba[0]->rows ||
		target_width != pic->rgba[0]->cols)
	{
		resampler = matCreateResampleContext(pic->rgba[0]->rows,
			pic->rgba[0]->cols,target_height,target_width);
		if(resampler == LIBV_INVALID_HANDLE)
			return false;
		for(i = 0;i < 4;i++)
		{
			m = matAlloc(target_height, target_width);
			matResample(resampler,m,pic->rgba[i]);
			memFree(pic->rgba[i]);
			pic->rgba[i] = m;
		}
		matDestroyResampleContext(resampler);
	}
	return true;
}

V_LOCAL image_input_p
inputFindCachePic(eval_state_p state,char *image_name)
{
	CALLER_NAME(inputFindCachePic)
	u32_t i;
	image_input_p pic, *pics;
	pics = (image_input_p*)vpsGetStorage(&image_cache);
	for(i = 0;i < vpsCount(&image_cache);i++)
	{
		pic = pics[i];
		if(STR_EQ(pic->id,image_name))
		{
			if(state->custom_width && 
				state->custom_width != pic->rgba[0]->cols)
			{
				WARNING_GENERIC(ipl_size_mismatch);
				continue;
			}
			if(state->custom_height && 
				state->custom_height != pic->rgba[0]->rows)
			{
				WARNING_GENERIC(ipl_size_mismatch);
				continue;
			}
			if(state->input_square && 
				pic->rgba[0]->cols != pic->rgba[0]->rows)
			{
				WARNING_GENERIC(ipl_not_square);
				continue;
			}
			if(state->input_powers2 && 
				(!IS_POWER_TWO(pic->rgba[0]->cols) || 
				  IS_POWER_TWO(pic->rgba[0]->rows)))
			{
				WARNING_GENERIC(ipl_not_powers2);
				continue;
			}
			return pic;
		}
	}
	pic = (image_input_p)memAllocZ(sizeof(image_input_t));
	V_ASSERT(pic);
	pic->id = strDup(image_name);
	if(!rgbaLoadChannels(pic->id,pic->rgba,pic->rgba+1,pic->rgba+2,pic->rgba+3) ||
		!inputResizePic(pic,state))
	{
		memFree(pic->id);
		memFree(pic);
		return NULL;
	}
	vpsPush(&image_cache,pic);
	return pic;
}

matrix_p
inputGetMatrix(eval_state_p state,char *image_name,char *channel_id)
{
	CALLER_NAME(inputGetMatrix)
	image_input_p pic = inputFindCachePic(state,image_name);
	if(!pic)
		return NULL;
	if(STR_EQ(channel_id,ipl_key_red))
		return pic->rgba[0];
	if(STR_EQ(channel_id,ipl_key_green))
		return pic->rgba[1];
	if(STR_EQ(channel_id,ipl_key_blue))
		return pic->rgba[2];
	if(STR_EQ(channel_id,ipl_key_alpha))
		return pic->rgba[3];
	if(STR_EQ(channel_id,ipl_key_hue))
	{
		if(!pic->hsv[0])
			ctransHsvFromRgb(pic->hsv,pic->rgba);
		return pic->hsv[0];
	}
	if(STR_EQ(channel_id,ipl_key_saturation))
	{
		if(!pic->hsv[1])
			ctransHsvFromRgb(pic->hsv,pic->rgba);
		return pic->hsv[1];
	}
	if(STR_EQ(channel_id,ipl_key_value))
	{
		if(!pic->hsv[2])
			ctransHsvFromRgb(pic->hsv,pic->rgba);
		return pic->hsv[2];
	}
	if(STR_EQ(channel_id,ipl_key_cieX))
	{
		if(!pic->cie[0])
			ctransCieFromRgb(pic->cie,pic->rgba);
		return pic->cie[0];
	}
	if(STR_EQ(channel_id,ipl_key_cieY))
	{
		if(!pic->cie[1])
			ctransCieFromRgb(pic->cie,pic->rgba);
		return pic->cie[1];
	}
	if(STR_EQ(channel_id,ipl_key_cieZ))
	{
		if(!pic->cie[2])
			ctransCieFromRgb(pic->cie,pic->rgba);
		return pic->cie[2];
	}
	ERROR_GENERIC_2ARG(ipl_unknown_channel,channel_id)
	return NULL;
}

void
inputCollectGarbage(void)
{
	u32_t i;
	image_input_p ptr;
	while(vpsPop(&image_cache,(void_p*)&ptr))
	{
		for(i=0;i<3;i++)
		{
			if(ptr->rgba[i])
				memFree(ptr->rgba[i]);
			if(ptr->hsv[i])
				memFree(ptr->hsv[i]);
			if(ptr->cie[i])
				memFree(ptr->cie[i]);
		}
		if(ptr->rgba[3])
			memFree(ptr->rgba[3]);
		memFree(ptr->id);
		memFree(ptr);
	}
	vpsClear(&image_cache);
}
