#include "clip.h"
#include "bchash.h"
#include "filexml.h"
#include "histogramengine.h"
#include "language.h"
#include "plugincolors.h"
#include "threshold.h"
#include "thresholdwindow.h"
#include "vframe.h"

#include <string.h>


ThresholdConfig::ThresholdConfig()
{
	reset();
}

int ThresholdConfig::equivalent(ThresholdConfig &that)
{
	return EQUIV(min, that.min) &&
		EQUIV(max, that.max) &&
		plot == that.plot;
}

void ThresholdConfig::copy_from(ThresholdConfig &that)
{
	min = that.min;
	max = that.max;
	plot = that.plot;
}

void ThresholdConfig::interpolate(ThresholdConfig &prev,
	ThresholdConfig &next,
	int64_t prev_frame, 
	int64_t next_frame, 
	int64_t current_frame)
{
	double next_scale = (double)(current_frame - prev_frame) / 
		(next_frame - prev_frame);
	double prev_scale = (double)(next_frame - current_frame) / 
		(next_frame - prev_frame);

	min = prev.min * prev_scale + next.min * next_scale;
	max = prev.max * prev_scale + next.max * next_scale;
	plot = prev.plot;
}

void ThresholdConfig::reset()
{
	min = 0.0;
	max = 1.0;
	plot = 1;
}

void ThresholdConfig::boundaries()
{
	CLAMP(min, HISTOGRAM_MIN, max);
	CLAMP(max, min, HISTOGRAM_MAX);
}








REGISTER_PLUGIN(ThresholdMain)

ThresholdMain::ThresholdMain(PluginServer *server)
 : PluginVClient(server)
{
	PLUGIN_CONSTRUCTOR_MACRO
	engine = 0;
	threshold_engine = 0;
}

ThresholdMain::~ThresholdMain()
{
	PLUGIN_DESTRUCTOR_MACRO
	delete engine;
	delete threshold_engine;
}

int ThresholdMain::is_realtime()
{
	return 1;
}

char* ThresholdMain::plugin_title() 
{ 
	return N_("Threshold"); 
}


#include "picon_png.h"
NEW_PICON_MACRO(ThresholdMain)

SHOW_GUI_MACRO(ThresholdMain, ThresholdThread)

SET_STRING_MACRO(ThresholdMain)

RAISE_WINDOW_MACRO(ThresholdMain)

LOAD_CONFIGURATION_MACRO(ThresholdMain, ThresholdConfig)







int ThresholdMain::process_buffer(VFrame *frame,
	int64_t start_position,
	double frame_rate)
{
	load_configuration();

	int use_opengl = get_use_opengl() &&
		(!config.plot || !gui_open());

	read_frame(frame,
		0,
		get_source_position(),
		get_framerate(),
		use_opengl);

	if(use_opengl) return run_opengl();

	send_render_gui(frame);

	if(!threshold_engine)
		threshold_engine = new ThresholdEngine(this);
	threshold_engine->process_packages(frame);
	
	return 0;
}

int ThresholdMain::load_defaults()
{
	char directory[BCTEXTLEN], string[BCTEXTLEN];
	sprintf(directory, "%sthreshold.rc", BCASTDIR);
	defaults = new BC_Hash(directory);
	defaults->load();
	config.min = defaults->get("MIN", config.min);
	config.max = defaults->get("MAX", config.max);
	config.plot = defaults->get("PLOT", config.plot);
	config.boundaries();
	return 0;
}

int ThresholdMain::save_defaults()
{
	defaults->update("MIN", config.min);
	defaults->update("MAX", config.max);
	defaults->update("PLOT", config.plot);
	defaults->save();
}

void ThresholdMain::save_data(KeyFrame *keyframe)
{
	FileXML file;
	file.set_shared_string(keyframe->data, MESSAGESIZE);
	file.tag.set_title("THRESHOLD");
	file.tag.set_property("MIN", config.min);
	file.tag.set_property("MAX", config.max);
	file.tag.set_property("PLOT", config.plot);
	file.append_tag();
	file.terminate_string();
}

void ThresholdMain::read_data(KeyFrame *keyframe)
{
	FileXML file;
	file.set_shared_string(keyframe->data, strlen(keyframe->data));
	int result = 0;
	while(!result)
	{
		result = file.read_tag();
		if(!result)
		{
			config.min = file.tag.get_property("MIN", config.min);
			config.max = file.tag.get_property("MAX", config.max);
			config.plot = file.tag.get_property("PLOT", config.plot);
		}
	}
	config.boundaries();
}

void ThresholdMain::update_gui()
{
	if(thread)
	{
		thread->window->lock_window("ThresholdMain::update_gui");
		if(load_configuration())
		{
			thread->window->min->update(config.min);
			thread->window->max->update(config.max);
			thread->window->plot->update(config.plot);
		}
		thread->window->unlock_window();
	}
}

void ThresholdMain::render_gui(void *data)
{
	if(thread)
	{
		calculate_histogram((VFrame*)data);
		thread->window->lock_window("ThresholdMain::render_gui");
		thread->window->canvas->draw();
		thread->window->unlock_window();
	}
}

void ThresholdMain::calculate_histogram(VFrame *frame)
{
	if(!engine) engine = new HistogramEngine(get_project_smp() + 1,
		get_project_smp() + 1);
	engine->process_packages(frame);
}

int ThresholdMain::handle_opengl()
{
#ifdef HAVE_GL
	static char *rgb_shader = 
		"uniform sampler2D tex;\n"
		"uniform float min;\n"
		"uniform float max;\n"
		"void main()\n"
		"{\n"
		"	vec4 pixel = texture2D(tex, gl_TexCoord[0].st);\n"
		"	float v = dot(pixel.rgb, vec3(0.299, 0.587, 0.114));\n"
		"	if(v >= min && v < max)\n"
		"		pixel.rgb = vec3(1.0, 1.0, 1.0);\n"
		"	else\n"
		"		pixel.rgb = vec3(0.0, 0.0, 0.0);\n"
		"	gl_FragColor = pixel;\n"
		"}\n";

	static char *yuv_shader = 
		"uniform sampler2D tex;\n"
		"uniform float min;\n"
		"uniform float max;\n"
		"void main()\n"
		"{\n"
		"	vec4 pixel = texture2D(tex, gl_TexCoord[0].st);\n"
		"	if(pixel.r >= min && pixel.r < max)\n"
		"		pixel.rgb = vec3(1.0, 0.5, 0.5);\n"
		"	else\n"
		"		pixel.rgb = vec3(0.0, 0.5, 0.5);\n"
		"	gl_FragColor = pixel;\n"
		"}\n";

	get_output()->to_texture();
	get_output()->enable_opengl();

	unsigned int shader = 0;
	if(cmodel_is_yuv(get_output()->get_color_model()))
		shader = VFrame::make_shader(0, yuv_shader, 0);
	else
		shader = VFrame::make_shader(0, rgb_shader, 0);

	if(shader > 0)
	{
		glUseProgram(shader);
		glUniform1i(glGetUniformLocation(shader, "tex"), 0);
		glUniform1f(glGetUniformLocation(shader, "min"), config.min);
		glUniform1f(glGetUniformLocation(shader, "max"), config.max);
	}

	get_output()->init_screen();
	get_output()->bind_texture(0);
	get_output()->draw_texture();
	glUseProgram(0);
	get_output()->set_opengl_state(VFrame::SCREEN);
#endif
}























ThresholdPackage::ThresholdPackage()
 : LoadPackage()
{
	start = end = 0;
}











ThresholdUnit::ThresholdUnit(ThresholdEngine *server)
 : LoadClient(server)
{
	this->server = server;
}

void ThresholdUnit::process_package(LoadPackage *package)
{
	ThresholdPackage *pkg = (ThresholdPackage*)package;
	VFrame *data = server->data;
	int min = (int)(server->plugin->config.min * 0xffff);
	int max = (int)(server->plugin->config.max * 0xffff);
	int r, g, b, a, y, u, v;
	int w = server->data->get_w();
	int h = server->data->get_h();

#define THRESHOLD_HEAD(type) \
{ \
	for(int i = pkg->start; i < pkg->end; i++) \
	{ \
		type *in_row = (type*)data->get_rows()[i]; \
		type *out_row = in_row; \
		for(int j = 0; j < w; j++) \
		{

#define THRESHOLD_TAIL(components, r_on, g_on, b_on, a_on, r_off, g_off, b_off, a_off) \
			v = (r * 76 + g * 150 + b * 29) >> 8; \
			if(v >= min && v < max) \
			{ \
				*out_row++ = r_on; \
				*out_row++ = g_on; \
				*out_row++ = b_on; \
				if(components == 4) *out_row++ = a_on; \
			} \
			else \
			{ \
				*out_row++ = r_off; \
				*out_row++ = g_off; \
				*out_row++ = b_off; \
				if(components == 4) *out_row++ = a_off; \
			} \
			in_row += components; \
		} \
	} \
}


	switch(data->get_color_model())
	{
		case BC_RGB888:
			THRESHOLD_HEAD(unsigned char)
			r = (in_row[0] << 8) | in_row[0];
			g = (in_row[1] << 8) | in_row[1];
			b = (in_row[2] << 8) | in_row[2];
			THRESHOLD_TAIL(3, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0);
			break;
		case BC_RGB_FLOAT:
			THRESHOLD_HEAD(float)
			r = (int)(in_row[0] * 0xffff);
			g = (int)(in_row[1] * 0xffff);
			b = (int)(in_row[2] * 0xffff);
			THRESHOLD_TAIL(3, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0);
			break;
		case BC_RGBA8888:
			THRESHOLD_HEAD(unsigned char)
			r = (in_row[0] << 8) | in_row[0];
			g = (in_row[1] << 8) | in_row[1];
			b = (in_row[2] << 8) | in_row[2];
			THRESHOLD_TAIL(4, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0);
			break;
		case BC_RGBA_FLOAT:
			THRESHOLD_HEAD(float)
			r = (int)(in_row[0] * 0xffff);
			g = (int)(in_row[1] * 0xffff);
			b = (int)(in_row[2] * 0xffff);
			THRESHOLD_TAIL(4, 1.0, 1.0, 1.0, 1.0, 0, 0, 0, 0);
			break;
		case BC_YUV888:
			THRESHOLD_HEAD(unsigned char)
			y = (in_row[0] << 8) | in_row[0];
			u = (in_row[1] << 8) | in_row[1];
			v = (in_row[2] << 8) | in_row[2];
			server->yuv->yuv_to_rgb_16(r, g, b, y, u, v);
			THRESHOLD_TAIL(3, 0xff, 0x80, 0x80, 0xff, 0x0, 0x80, 0x80, 0x0)
			break;
		case BC_YUVA8888:
			THRESHOLD_HEAD(unsigned char)
			y = (in_row[0] << 8) | in_row[0];
			u = (in_row[1] << 8) | in_row[1];
			v = (in_row[2] << 8) | in_row[2];
			server->yuv->yuv_to_rgb_16(r, g, b, y, u, v);
			THRESHOLD_TAIL(4, 0xff, 0x80, 0x80, 0xff, 0x0, 0x80, 0x80, 0x0)
			break;
		case BC_YUV161616:
			THRESHOLD_HEAD(uint16_t)
			y = in_row[0];
			u = in_row[1];
			v = in_row[2];
			server->yuv->yuv_to_rgb_16(r, g, b, y, u, v);
			THRESHOLD_TAIL(3, 0xffff, 0x8000, 0x8000, 0xffff, 0x0, 0x8000, 0x8000, 0x0)
			break;
		case BC_YUVA16161616:
			THRESHOLD_HEAD(uint16_t)
			y = in_row[0];
			u = in_row[1];
			v = in_row[2];
			server->yuv->yuv_to_rgb_16(r, g, b, y, u, v);
			THRESHOLD_TAIL(4, 0xffff, 0x8000, 0x8000, 0xffff, 0x0, 0x8000, 0x8000, 0x0)
			break;
	}
}











ThresholdEngine::ThresholdEngine(ThresholdMain *plugin)
 : LoadServer(plugin->get_project_smp() + 1,
 	plugin->get_project_smp() + 1)
{
	this->plugin = plugin;
	yuv = new YUV;
}

ThresholdEngine::~ThresholdEngine()
{
	delete yuv;
}

void ThresholdEngine::process_packages(VFrame *data)
{
	this->data = data;
	LoadServer::process_packages();
}

void ThresholdEngine::init_packages()
{
	for(int i = 0; i < get_total_packages(); i++)
	{
		ThresholdPackage *package = (ThresholdPackage*)get_package(i);
		package->start = data->get_h() * i / get_total_packages();
		package->end = data->get_h() * (i + 1) / get_total_packages();
	}
}

LoadClient* ThresholdEngine::new_client()
{
	return (LoadClient*)new ThresholdUnit(this);
}

LoadPackage* ThresholdEngine::new_package()
{
	return (LoadPackage*)new HistogramPackage;
}






