
#include "livido.h"
#include <dlfcn.h>
#include <stdio.h>       
#include <string.h>
#include <stdlib.h>

#define TEST_WIDTH 8
#define TEST_HEIGHT 8
#define TEST_PALETTE 	LVD_PALETTE_RGB888

#include "livido-library.c"


// this host uses only complex parameter passing

int get_prev_keyframe( void *host_data,                          //input
		  livido_timecode_t position_timecode,      //input
		  livido_timecode_t *keyframe_timecode,     //output
		  void *parameter_set,
		  int param_number)                       //output
{
	const livido_instance_template_t *insttmpl = (const livido_instance_template_t *)host_data;

// Copy minimal parameters ... if type is bool or number, else copy default
	// We emulate two keyframes - at 0.0 (min) and 1.0 (max)
	if (position_timecode >= 1.0)
	{
		lvd_params_to_max_num(insttmpl, parameter_set, param_number);
		*keyframe_timecode = 1.0; 
	} else 
	{
		lvd_params_to_min_num(insttmpl, parameter_set, param_number);
		if (position_timecode >= 0.0) 
			*keyframe_timecode = 0.0;   
		else
			*keyframe_timecode = -1;  // there is no next, previous has been returned
	}
	*keyframe_timecode = 0.0;
	return 0;
}

int get_next_keyframe( void *host_data,                          //input
		  livido_timecode_t position_timecode,      //input
		  livido_timecode_t *keyframe_timecode,     //output
		  void *parameter_set,
		  int param_number)                       //output
{
	const livido_instance_template_t *insttmpl = (const livido_instance_template_t *)host_data;

// Copy minimal parameters ... if type is bool or number, else copy default
	// We emulate two keyframes - at 0.0 (min) and 1.0 (max)
	if (position_timecode < 0.0)
	{
		lvd_params_to_min_num(insttmpl, parameter_set, param_number);
		*keyframe_timecode = 0.0; 
	} else 
	{
		lvd_params_to_max_num(insttmpl, parameter_set, param_number);
		if (position_timecode < 1.0) 
			*keyframe_timecode = 1.0;   
		else
			*keyframe_timecode = -1;  // there is no next, previous has been returned
	}
	return 0;
}


// setup channels according to defines at the begining
void setup_channels(	livido_channel_t *in,
			livido_channel_t *out) 
{
	int tmp = 0;
	while (in[tmp].based_on_template != NULL)
	{
		livido_channel_t *chan = &in[tmp];
		livido_channel_template_t *base = chan->based_on_template;
		livido_channel_t *same_as;

		// setup channel size according to template
		same_as = lvd_get_same_channel(in, out, base->same_as);   
		if (same_as && (base->flags & LIVIDO_CHNLTMPL_SAME_AS_SIZE)) {
			chan->width = same_as->width;
			chan->height = same_as->height;	
		} else
		if (base->width !=0)
		{
			chan->width = base->width;    // channel only takes one size
			chan->height = base->height;
		} else
		{
			chan->width = TEST_WIDTH;   // arbitrary size
			chan->height = TEST_HEIGHT; // could be a bit random for testing
		}

		if (same_as && (base->flags & LIVIDO_CHNLTMPL_SAME_AS_PALETTE)) {
			chan->palette = same_as->palette;
		} else
		{
			chan->palette = TEST_PALETTE;
		}
		
		
		// only for interlaved modes right now, 
		// can be incremented to test the correct rowstride usage in plugin
		chan->rowstrides[0] = (LIVIDO_PALETTE_BITS(chan->palette) * chan->width + 7) / 8; 
		tmp ++;
	}
	
	// This seems the same but ...
	tmp = 0;
	while (out[tmp].based_on_template != NULL)
	{
		livido_channel_t *chan = &out[tmp];
		livido_channel_template_t *base = chan->based_on_template;
		livido_channel_t *same_as;

		// setup channel size according to template
		same_as = lvd_get_same_channel(in, out, base->same_as);   
		if (same_as && (base->flags & LIVIDO_CHNLTMPL_SAME_AS_SIZE)) {
			chan->width = same_as->width;
			chan->height = same_as->height;	
		} else
		if (base->width !=0)
		{
			chan->width = base->width;    // channel only takes one size
			chan->height = base->height;
		} else
		{
			chan->width = TEST_WIDTH;   // arbitrary size
			chan->height = TEST_HEIGHT; // could be a bit random for testing
		}

		if (same_as && (base->flags & LIVIDO_CHNLTMPL_SAME_AS_PALETTE)) {
			chan->palette = same_as->palette;
		} else
		{
			chan->palette = TEST_PALETTE;
		}

		
		// only for interlaved modes right now, 
		// can be incremented to test the correct rowstride usage in plugin
		chan->rowstrides[0] = (LIVIDO_PALETTE_BITS(chan->palette) * chan->width + 7) / 8; 
		tmp ++;
	}

}

#define FILL_FRAME_VALUES(data_type, max, components) \
{ \
	data_type *data;  \
	int i, j; \
	for (i = 0; i < height; i++) { \
		data = (data_type*)((char *)in_data + i * rowstride); \
		for (j = 0; j < width; j++) { \
			if (components > 0) \
				data[j * components + 0] = j * max / (width - 1); \
			if (components > 1) \
				data[j * components + 1] = i * max / (height - 1); \
			if (components > 2) \
				data[j * components + 2] = (width - j - 1) * max / (width - 1); \
			if (components > 3) \
				data[j * components + 3] = (height - i - 1) * max / (height - 1); \
		} \
	} \
}
// Fills the frame data with generic patteren for every colormodel...
// first channel goes up from left to right
// second channel goes up from top to bottom
// third channel goes up from right to left
// fourth channel goes up from bottom to top
void fill_frame_data (void *in_data,
			livido_palette_t pal,
			int width,
			int height,
			int rowstride)
{

	// done in a clever way
	switch (pal)
	{
		case LVD_PALETTE_RGB888:
			FILL_FRAME_VALUES(uint8_t, 0xff, 3);
			break;
		case LVD_PALETTE_RGBA8888:
			FILL_FRAME_VALUES(uint8_t, 0xff, 4);
			break;
		case LVD_PALETTE_YUV888:
			FILL_FRAME_VALUES(uint8_t, 0xff, 3);
			break;
		case LVD_PALETTE_YUVA8888:
			FILL_FRAME_VALUES(uint8_t, 0xff, 4);
			break;
		case LVD_PALETTE_RGB161616:
			FILL_FRAME_VALUES(uint16_t, 0xffff, 3);
			break;
		case LVD_PALETTE_RGBA16161616:
			FILL_FRAME_VALUES(uint16_t, 0xffff, 4);
			break;
		case LVD_PALETTE_YUV161616:
			FILL_FRAME_VALUES(uint16_t, 0xffff, 3);
			break;
		case LVD_PALETTE_YUVA16161616:
			FILL_FRAME_VALUES(uint16_t, 0xffff, 4);
			break;
		case LVD_PALETTE_A8:
			FILL_FRAME_VALUES(uint8_t, 0xff, 1);
			break;
		case LVD_PALETTE_A16:
			FILL_FRAME_VALUES(uint16_t, 0xffff, 1);
			break;
		default:
			break;
			// ADD ALL YOU WANT (for planar you will need another macro...)
	}
}


livido_frame_t *create_frames_from_channels(livido_channel_t *channels, int fill) 
{
	int size = lvd_num_channels(channels);
	livido_frame_t *frames = malloc(sizeof(livido_frame_t) * (size + 1));
	int i;
	for (i = 0; i < size; i++) 
	{
		livido_frame_t *frame = &(frames[i]);
		frame->channel = &(channels[i]);
		size_t frame_size = frame->channel->rowstrides[0] * frame->channel->height;
		frame->pixel_data[0] = (void *) malloc(frame_size);
		if (fill)
			fill_frame_data(frame->pixel_data[0], // to stay general...
				frame->channel->palette,
				frame->channel->width, 
				frame->channel->height, 
				frame->channel->rowstrides[0]);
		else 
			memset(frame->pixel_data[0], 0, frame_size);
	}
	// Fill in the senitel frame
	frames[size].channel = NULL;
	frames[size].pixel_data[0] = 0;
	frames[size].pixel_data[1] = 0;
	frames[size].pixel_data[2] = 0;
	frames[size].pixel_data[3] = 0;
	
	return frames;
} 

void free_frames(livido_frame_t *frames) {
	livido_frame_t *orig_frames = frames;
	while (frames->channel != NULL)
	{
		free(frames->pixel_data[0]);
		frames++;
	}
	free(orig_frames);
}

#define DUMP_FRAME_VALUES(data_type, places, components) \
{ \
	data_type *data;  \
	int i, j; \
	for (i = 0; i < height; i++) { \
		data = (data_type*)((char *)in_data + i * rowstride); \
		for (j = 0; j < width; j++) { \
			printf("("); \
			if (components > 0) \
				printf("%2x", data[j * components + 0]); \
			if (components > 1) \
				printf(",%2x", data[j * components + 1]); \
			if (components > 2) \
				printf(",%2x", data[j * components + 2]); \
			if (components > 3) \
				printf(",%2x", data[j * components + 3]); \
			printf(")"); \
		} \
		printf("\n"); \
	} \
}



void dump_frame(livido_frame_t *frame) 
{
	int height = frame->channel->height;
	int width = frame->channel->width;
	int rowstride = frame->channel->rowstrides[0];
	void *in_data = (void *)frame->pixel_data[0];
	
	
	switch (frame->channel->palette)
	{
		case LVD_PALETTE_RGB888:
			DUMP_FRAME_VALUES(uint8_t, 2, 3);
			break;
		case LVD_PALETTE_RGBA8888:
			DUMP_FRAME_VALUES(uint8_t, 2, 4);
			break;
		case LVD_PALETTE_YUV888:
			DUMP_FRAME_VALUES(uint8_t, 2, 3);
			break;
		case LVD_PALETTE_YUVA8888:
			DUMP_FRAME_VALUES(uint8_t, 2, 4);
			break;
		case LVD_PALETTE_RGB161616:
			DUMP_FRAME_VALUES(uint16_t, 4, 3);
			break;
		case LVD_PALETTE_RGBA16161616:
			DUMP_FRAME_VALUES(uint16_t, 4, 4);
			break;
		case LVD_PALETTE_YUV161616:
			DUMP_FRAME_VALUES(uint16_t, 4, 3);
			break;
		case LVD_PALETTE_YUVA16161616:
			DUMP_FRAME_VALUES(uint16_t, 4, 4);
			break;
		case LVD_PALETTE_A8:
			DUMP_FRAME_VALUES(uint8_t, 2, 1);
			break;
		case LVD_PALETTE_A16:
			DUMP_FRAME_VALUES(uint16_t, 4, 1);
			break;
		default:
			break;
			// ADD ALL YOU WANT (for planar you will need another macro...)
	}
		
}


void dump_frames(livido_frame_t *frames)
{
	int i = 0;
	while (frames->channel != NULL)
	{
		printf("Frame %i, Size: %ix%i, Palette: %s (data: %p)\n", i, frames->channel->width, frames->channel->height, lvd_get_palette_name(frames->channel->palette), frames->pixel_data[0]);
		dump_frame(frames);
		frames++;
		i++;
	}
}
	

void test_template(livido_instance_template_t *cl) {
	livido_instance_t *inst;

	// Template-level functionality has been tested till now
	// Now try if the instance functionality is working
	// We do not test optional channels!
	LIVIDO_CREATE_INSTANCE(cl, inst);
	// channels are malloced by the macro, now we have to fill them

	// setup channels
	setup_channels(inst->in_channels, inst->out_channels);
	if (cl->init(inst) != 0) 
	{
		printf("Error initializing instance: (message: %s)\n", inst->error_message);
		exit(1);
	}
	
	void *params_by_plugin = lvd_malloc_in_params(cl);
//	If you want to try out how typecasting error looks like.. uncomment this
//	lvd_type_coord2d *paa = lvd_param_coord2d(cl, params_by_plugin, 0);
	
	// Prepare a parameter block... 
	printf("Default parameters:\n");
	lvd_dump_parameter_set(cl, cl->params_in_def);

	// first test - get the value of parameters at three points in time	
	if (cl->property & LVD_PROPERTY_SELF_AUTOMATION) 
	{
		printf("Plugin supports parameter keyframing, testing it\n");
		printf("Next three sets should differ:\n");
		printf("We are using two keyframes - minimal values at 0.0 and maximum at 1.0\n");
		printf("Tests are done at 0.0, 0.5 and 1.0\n");
		
		cl->interpolate_parameters(get_prev_keyframe, get_next_keyframe, (void *)cl, 0, params_by_plugin);
		lvd_dump_parameter_set(cl, params_by_plugin);
		cl->interpolate_parameters(get_prev_keyframe, get_next_keyframe, (void *)cl, 0.5, params_by_plugin);
		lvd_dump_parameter_set(cl, params_by_plugin);
		cl->interpolate_parameters(get_prev_keyframe, get_next_keyframe, (void *)cl, 1.0, params_by_plugin);
		lvd_dump_parameter_set(cl, params_by_plugin);

		// load the middle value for later use
		cl->interpolate_parameters(get_prev_keyframe, get_next_keyframe, (void *)cl, 0.5, params_by_plugin);
		memcpy(inst->params_in, params_by_plugin, cl->params_in_size);	
		printf("Using parameter values at 0.5 for testing\n");

	} else
	{
		printf("Plugin does not support automation, therefore not testing it\n");
		printf("Using default parameters\n");
		lvd_params_to_def(cl, inst->params_in);
	}

	livido_frame_t *in_frames = create_frames_from_channels(inst->in_channels, 1); // create test channel
	livido_frame_t *out_frames = create_frames_from_channels(inst->out_channels, 0); // create empty channel

	printf("Dumps of input channels:\n");
	dump_frames(in_frames);
	printf("Dumps of (empty) output channels:\n");
	dump_frames(out_frames);
	printf("Testing for parameters:\n");
	lvd_dump_parameter_set(cl, inst->params_in);
	printf("host: %p\n", inst->params_in);
	if (cl->process(inst, -1, in_frames, out_frames))
	{
		printf("Error when calling process: (message: %s)\n", inst->error_message);
		exit(1);
	}
	dump_frames(out_frames);
	
	
	if (cl->property & LVD_PROPERTY_CAN_DO_INPLACE)
	{
		printf("Testing LVD_PROPERTY_CAN_DO_INPLACE - has to be the same as previous:\n");
		cl->process(inst, -1, in_frames, in_frames);	
		dump_frames(in_frames);
	} 
			
	
	lvd_free_params(params_by_plugin);
	free_frames(in_frames);
	free_frames(out_frames);
	
	if (cl->deinit(inst) != 0) 
	{
		printf("Error deinitializing instance: %s\n", inst->error_message);
		exit(1);
	}
	LIVIDO_FREE_INSTANCE(cl, inst);
	
	
}


int main(int argc, char **argv) 
{

	char *name;
	void *handle;
	livido_setup_f *livido_setup;
	livido_instance_template_t *plugin_template;

	if (argc != 2) {
		printf("Usage: testhost plugin_name.so\n");
		return 0;
	}
	name = argv[1];
	
	handle = dlopen(name, RTLD_LAZY);
	if (!handle) { printf("dlopen failed on %s because of %s\n", name, dlerror()); return 1; };
	livido_setup = (livido_setup_f *) dlsym(handle, "livido_setup");
	if (!livido_setup) { printf("function livido_setup not found in %s\n", name); return 1; };
	plugin_template = (*livido_setup)();
	if (!plugin_template) { printf("livido_setup did not return a template\n"); return 1; };
 	printf("Loading of plugin and running livido_setup successeful\n");
 	if (plugin_template[1].name != NULL)
 		printf("This plugin has more then one template, but testing only first\n");
 	lvd_dump_instance_template(plugin_template);
 	
 	test_template(plugin_template);
 	
	dlclose(handle);
	return 0;
}

