
/*
 * CINELERRA
 * Copyright (C) 2017 Adam Williams <broadcast at earthling dot net>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 */

#include "affine.h"
#include "bcdisplayinfo.h"
#include "bchash.h"
#include "bcsignals.h"
#include "clip.h"
#include "filexml.h"
#include "language.h"
#include "360fuser.h"


#include <string.h>



// largely based on equations from http://paulbourke.net/dome/fish2/



REGISTER_PLUGIN(Fuse360Main)




Fuse360Config::Fuse360Config()
{
	fov = 1.0;
	radius_x = 0.5;
	radius_y = 0.5;
	center_x = 50.0;
	center_y = 50.0;
	distance_x = 50;
	distance_y = 0;
	feather = 0;
	translate_x = 0;
	rotation = 0;
	draw_guides = 1;
	mode = Fuse360Config::DO_NOTHING;
}

int Fuse360Config::equivalent(Fuse360Config &that)
{
	if(EQUIV(fov, that.fov) &&
		EQUIV(radius_x, that.radius_x) &&
		EQUIV(radius_y, that.radius_y) &&
		EQUIV(feather, that.feather) && 
		EQUIV(center_x, that.center_x) &&
		EQUIV(center_y, that.center_y) &&
		EQUIV(distance_x, that.distance_x) &&
		EQUIV(distance_y, that.distance_y) &&
		EQUIV(translate_x, that.translate_x) &&
		EQUIV(rotation, that.rotation) &&
		mode == that.mode &&
		draw_guides == that.draw_guides)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

void Fuse360Config::copy_from(Fuse360Config &that)
{
	*this = that;
}

void Fuse360Config::interpolate(Fuse360Config &prev, 
	Fuse360Config &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);

	fov = prev.fov * prev_scale + next.fov * next_scale;
	radius_x = prev.radius_x * prev_scale + next.radius_x * next_scale;
	radius_y = prev.radius_y * prev_scale + next.radius_y * next_scale;
	feather = prev.feather * prev_scale + next.feather * next_scale;
	center_x = prev.center_x * prev_scale + next.center_x * next_scale;
	center_y = prev.center_y * prev_scale + next.center_y * next_scale;
	distance_x = prev.distance_x * prev_scale + next.distance_x * next_scale;
	distance_y = prev.distance_y * prev_scale + next.distance_y * next_scale;
	rotation = prev.rotation * prev_scale + next.rotation * next_scale;
	translate_x = prev.translate_x * prev_scale + next.translate_x * next_scale;
	draw_guides = prev.draw_guides;
	mode = prev.mode;

	boundaries();
}

void Fuse360Config::boundaries()
{
	CLAMP(fov, 0.0, 1.0);
	CLAMP(radius_x, 0.3, 1.0);
	CLAMP(radius_y, 0.3, 1.0);
	CLAMP(feather, 0, 50);
	CLAMP(center_x, 0.0, 99.0);
	CLAMP(center_y, 0.0, 99.0);
	CLAMP(distance_x, 0.0, 100.0);
	CLAMP(distance_y, -50.0, 50.0);
	CLAMP(translate_x, -100, 100);
	CLAMP(rotation, -180, 180);
}




Fuse360Slider::Fuse360Slider(Fuse360Main *client, 
	Fuse360GUI *gui,
	Fuse360Text *text,
	float *output, 
	int x, 
	int y, 
	float min,
	float max)
 : BC_FSlider(x, y, 0, 200, 200, min, max, *output)
{
	this->gui = gui;
	this->client = client;
	this->output = output;
	this->text = text;
	set_precision(0.01);
}

int Fuse360Slider::handle_event()
{
	float prev_output = *output;
	*output = get_value();
	text->update(*output);


	client->send_configure_change();
	return 1;
}



Fuse360Text::Fuse360Text(Fuse360Main *client, 
	Fuse360GUI *gui,
	Fuse360Slider *slider,
	float *output, 
	int x, 
	int y)
 : BC_TextBox(x, y, 100, 1, *output)
{
	this->gui = gui;
	this->client = client;
	this->output = output;
	this->slider = slider;
}

int Fuse360Text::handle_event()
{
	float prev_output = *output;
	*output = atof(get_text());
	slider->update(*output);


	client->send_configure_change();
	return 1;
}



Fuse360Toggle::Fuse360Toggle(Fuse360Main *client, 
	int *output, 
	int x, 
	int y,
	const char *text)
 : BC_CheckBox(x, y, *output, text)
{
	this->output = output;
	this->client = client;
}

int Fuse360Toggle::handle_event()
{
	*output = get_value();
	client->send_configure_change();
	return 1;
}










Fuse360Mode::Fuse360Mode(Fuse360Main *plugin,  
	Fuse360GUI *gui,
	int x,
	int y)
 : BC_PopupMenu(x,
 	y,
	calculate_w(gui),
	"",
	1)
{
	this->plugin = plugin;
	this->gui = gui;
}

int Fuse360Mode::handle_event()
{
	plugin->config.mode = from_text(get_text());
	plugin->send_configure_change();
	return 1;

}

void Fuse360Mode::create_objects()
{
	add_item(new BC_MenuItem(to_text(Fuse360Config::DO_NOTHING)));
	add_item(new BC_MenuItem(to_text(Fuse360Config::STRETCHXY)));
	add_item(new BC_MenuItem(to_text(Fuse360Config::STANDARD)));
	add_item(new BC_MenuItem(to_text(Fuse360Config::BLEND)));
	update(plugin->config.mode);
}

void Fuse360Mode::update(int mode)
{
	char string[BCTEXTLEN];
	sprintf(string, "%s", to_text(mode));
	set_text(string);
}

int Fuse360Mode::calculate_w(Fuse360GUI *gui)
{
	int result = 0;
	result = MAX(result, gui->get_text_width(MEDIUMFONT, to_text(Fuse360Config::STRETCHXY)));
	result = MAX(result, gui->get_text_width(MEDIUMFONT, to_text(Fuse360Config::STANDARD)));
	result = MAX(result, gui->get_text_width(MEDIUMFONT, to_text(Fuse360Config::BLEND)));
	result = MAX(result, gui->get_text_width(MEDIUMFONT, to_text(Fuse360Config::DO_NOTHING)));
	return result + 50;
}

int Fuse360Mode::from_text(char *text)
{
	for(int i = 0; i < 4; i++)
	{
		if(!strcmp(text, to_text(i)))
		{
			return i;
		}
	}

	return Fuse360Config::STRETCHXY;
}

const char* Fuse360Mode::to_text(int mode)
{
	switch(mode)
	{
		case Fuse360Config::DO_NOTHING:
			return "Do nothing";
			break;
		case Fuse360Config::STRETCHXY:
			return "Stretch";
			break;
		case Fuse360Config::STANDARD:
			return "Equirectangular";
			break;
	}
	return "Blend";
}
















Fuse360GUI::Fuse360GUI(Fuse360Main *client)
 : PluginClientWindow(client,
	350, 
	650, 
	350, 
	650, 
	0)
{
	this->client = client;
}

Fuse360GUI::~Fuse360GUI()
{
}


void Fuse360GUI::create_objects()
{
	int x = 10;
	int y = 10;
	int x1;
	int margin = 10;
	BC_Title *title;
	Fuse360Toggle *toggle;

// 	add_tool(title = new BC_Title(x, y, _("Field of View:")));
// 	y += title->get_h() + 5;
// 	add_tool(fov_slider = new Fuse360Slider(client, 
// 		this,
// 		0,
// 		&client->config.fov, 
// 		x, 
// 		y, 
// 		0.0001,
// 		1.0));
// 
// 
// 	x1 = x + fov_slider->get_w() + margin;
// 	add_tool(fov_text = new Fuse360Text(client, 
// 		this,
// 		fov_slider,
// 		&client->config.fov, 
// 		x1, 
// 		y));
// 	fov_slider->text = fov_text;
// 	y += fov_text->get_h() + margin;




	add_tool(title = new BC_Title(x, y, _("Eye Radius X:")));
	y += title->get_h() + 5;
	add_tool(radiusx_slider = new Fuse360Slider(client, 
		this,
		0,
		&client->config.radius_x, 
		x, 
		y, 
		0.3,
		1.0));
	x1 = x + radiusx_slider->get_w() + margin;
	add_tool(radiusx_text = new Fuse360Text(client, 
		this,
		radiusx_slider,
		&client->config.radius_x, 
		x1, 
		y));
	radiusx_slider->text = radiusx_text;
	y += radiusx_text->get_h() + 5;


	add_tool(title = new BC_Title(x, y, _("Eye Radius Y:")));
	y += title->get_h() + margin;
	add_tool(radiusy_slider = new Fuse360Slider(client, 
		this,
		0,
		&client->config.radius_y, 
		x, 
		y, 
		0.3,
		1.0));
	x1 = x + radiusy_slider->get_w() + margin;
	add_tool(radiusy_text = new Fuse360Text(client, 
		this,
		radiusy_slider,
		&client->config.radius_y, 
		x1, 
		y));
	radiusy_slider->text = radiusy_text;
	y += radiusy_text->get_h() + margin;


// 	add_tool(title = new BC_Title(x, y, _("Center X:")));
// 	y += title->get_h() + 5;
// 	add_tool(centerx_slider = new Fuse360Slider(client, 
// 		this,
// 		0,
// 		&client->config.center_x, 
// 		x, 
// 		y, 
// 		0.0,
// 		99.0));
// 	x1 = x + centerx_slider->get_w() + margin;
// 	add_tool(centerx_text = new Fuse360Text(client, 
// 		this,
// 		centerx_slider,
// 		&client->config.center_x, 
// 		x1, 
// 		y));
// 	centerx_slider->text = centerx_text;
// 	centerx_slider->set_precision(0.1);
// 	y += centerx_text->get_h() + margin;
// 
// 
// 	add_tool(title = new BC_Title(x, y, _("Center Y:")));
// 	y += title->get_h() + 5;
// 	add_tool(centery_slider = new Fuse360Slider(client, 
// 		this,
// 		0,
// 		&client->config.center_y, 
// 		x, 
// 		y, 
// 		0.0,
// 		99.0));
// 	x1 = x + centery_slider->get_w() + margin;
// 	add_tool(centery_text = new Fuse360Text(client, 
// 		this,
// 		centery_slider,
// 		&client->config.center_y, 
// 		x1, 
// 		y));
// 	centery_slider->text = centery_text;
// 	centery_slider->set_precision(0.1);
// 	y += centery_text->get_h() + margin;




	add_tool(title = new BC_Title(x, y, _("X Eye Spacing:")));
	y += title->get_h() + 5;
	add_tool(distancex_slider = new Fuse360Slider(client, 
		this,
		0,
		&client->config.distance_x, 
		x, 
		y, 
		0.0,
		100.0));
	x1 = x + distancex_slider->get_w() + margin;
	add_tool(distancex_text = new Fuse360Text(client, 
		this,
		distancex_slider,
		&client->config.distance_x, 
		x1, 
		y));
	distancex_slider->text = distancex_text;
	distancex_slider->set_precision(0.1);
	y += distancex_text->get_h() + margin;







	add_tool(title = new BC_Title(x, y, _("Y Eye Spacing:")));
	y += title->get_h() + 5;
	add_tool(distancey_slider = new Fuse360Slider(client, 
		this,
		0,
		&client->config.distance_y, 
		x, 
		y, 
		-50,
		50));
	x1 = x + distancey_slider->get_w() + margin;
	add_tool(distancey_text = new Fuse360Text(client, 
		this,
		distancey_slider,
		&client->config.distance_y, 
		x1, 
		y));
	distancey_slider->text = distancey_text;
	distancey_slider->set_precision(0.1);
	y += distancey_text->get_h() + margin;






	add_tool(title = new BC_Title(x, y, _("X Translation:")));
	y += title->get_h() + 5;
	add_tool(translatex_slider = new Fuse360Slider(client, 
		this,
		0,
		&client->config.translate_x, 
		x, 
		y, 
		-50.0,
		50.0));
	x1 = x + translatex_slider->get_w() + margin;
	add_tool(translatex_text = new Fuse360Text(client, 
		this,
		translatex_slider,
		&client->config.translate_x, 
		x1, 
		y));
	translatex_slider->text = translatex_text;
	translatex_slider->set_precision(0.1);
	y += translatex_text->get_h() + margin;






	add_tool(title = new BC_Title(x, y, _("Feather:")));
	y += title->get_h() + 5;
	add_tool(feather_slider = new Fuse360Slider(client, 
		this,
		0,
		&client->config.feather, 
		x, 
		y, 
		0,
		50));
	x1 = x + feather_slider->get_w() + margin;
	add_tool(feather_text = new Fuse360Text(client, 
		this,
		feather_slider,
		&client->config.feather, 
		x1, 
		y));
	feather_slider->text = feather_text;
	feather_slider->set_precision(1);
	y += feather_text->get_h() + margin;






	add_tool(title = new BC_Title(x, y, _("Rotation:")));
	y += title->get_h() + 5;
	add_tool(rotation_slider = new Fuse360Slider(client, 
		this,
		0,
		&client->config.rotation, 
		x, 
		y, 
		-180,
		180));
	x1 = x + rotation_slider->get_w() + margin;
	add_tool(rotation_text = new Fuse360Text(client, 
		this,
		rotation_slider,
		&client->config.rotation, 
		x1, 
		y));
	rotation_slider->text = rotation_text;
	rotation_slider->set_precision(0.1);
	y += rotation_text->get_h() + margin;







//printf("Fuse360GUI::create_objects %d %f\n", __LINE__, client->config.distance);


	add_tool(draw_guides = new Fuse360Toggle(client, 
		&client->config.draw_guides, 
		x, 
		y,
		_("Draw guides")));
	y += draw_guides->get_h() + margin;

	
	add_tool(title = new BC_Title(x, y, _("Mode:")));
	add_tool(mode = new Fuse360Mode(client, 
		this, 
		x + title->get_w() + margin, 
		y));
	mode->create_objects();
	y += mode->get_h() + margin;



	show_window();
	flush();
}







Fuse360Main::Fuse360Main(PluginServer *server)
 : PluginVClient(server)
{
	engine = 0;
	affine = 0;
}

Fuse360Main::~Fuse360Main()
{
	delete engine;
	delete affine;
}

NEW_WINDOW_MACRO(Fuse360Main, Fuse360GUI)
LOAD_CONFIGURATION_MACRO(Fuse360Main, Fuse360Config)
int Fuse360Main::is_realtime() { return 1; }
const char* Fuse360Main::plugin_title() { return "360 Fuser"; }

void Fuse360Main::update_gui()
{
	if(thread)
	{
		if(load_configuration())
		{
			((Fuse360GUI*)thread->window)->lock_window("Fuse360Main::update_gui");
//			((Fuse360GUI*)thread->window)->fov_slider->update(config.fov);
//			((Fuse360GUI*)thread->window)->fov_text->update(config.fov);
			((Fuse360GUI*)thread->window)->radiusx_slider->update(config.radius_x);
			((Fuse360GUI*)thread->window)->radiusx_text->update(config.radius_x);
			((Fuse360GUI*)thread->window)->radiusy_slider->update(config.radius_y);
			((Fuse360GUI*)thread->window)->radiusy_text->update(config.radius_y);
//			((Fuse360GUI*)thread->window)->centerx_slider->update(config.center_x);
//			((Fuse360GUI*)thread->window)->centerx_text->update(config.center_x);
//			((Fuse360GUI*)thread->window)->centery_slider->update(config.center_y);
//			((Fuse360GUI*)thread->window)->centery_text->update(config.center_y);
			((Fuse360GUI*)thread->window)->distancex_slider->update(config.distance_x);
			((Fuse360GUI*)thread->window)->distancex_text->update(config.distance_x);
			((Fuse360GUI*)thread->window)->distancey_slider->update(config.distance_y);
			((Fuse360GUI*)thread->window)->distancey_text->update(config.distance_y);
			((Fuse360GUI*)thread->window)->translatex_slider->update(config.translate_x);
			((Fuse360GUI*)thread->window)->translatex_text->update(config.translate_x);
			((Fuse360GUI*)thread->window)->feather_slider->update(config.feather);
			((Fuse360GUI*)thread->window)->feather_text->update(config.feather);
			((Fuse360GUI*)thread->window)->rotation_slider->update(config.rotation);
			((Fuse360GUI*)thread->window)->rotation_text->update(config.rotation);
			((Fuse360GUI*)thread->window)->mode->update(config.mode);
			((Fuse360GUI*)thread->window)->draw_guides->update(config.draw_guides);
			((Fuse360GUI*)thread->window)->unlock_window();
		}
	}
}


void Fuse360Main::save_data(KeyFrame *keyframe)
{
	FileXML output;
	char string[BCTEXTLEN];



// cause data to be stored directly in text
	output.set_shared_string(keyframe->get_data(), MESSAGESIZE);
	output.tag.set_title("360FUSER");
	output.tag.set_property("FOCAL_LENGTH", config.fov);
	output.tag.set_property("RADIUS_X", config.radius_x);
	output.tag.set_property("RADIUS_Y", config.radius_y);
	output.tag.set_property("FEATHER", config.feather);
	output.tag.set_property("CENTER_X", config.center_x);
	output.tag.set_property("CENTER_Y", config.center_y);
	output.tag.set_property("DISTANCE_X", config.distance_x);
	output.tag.set_property("DISTANCE_Y", config.distance_y);
	output.tag.set_property("TRANSLATE_X", config.translate_x);
	output.tag.set_property("ROTATION", config.rotation);
	output.tag.set_property("DRAW_GUIDES", config.draw_guides);
	output.tag.set_property("MODE", config.mode);
	output.append_tag();
	output.terminate_string();

}


void Fuse360Main::read_data(KeyFrame *keyframe)
{
	FileXML input;
	char string[BCTEXTLEN];


	input.set_shared_string(keyframe->get_data(), strlen(keyframe->get_data()));

	int result = 0;

	while(!result)
	{
		result = input.read_tag();

		if(!result)
		{
			if(input.tag.title_is("360FUSER"))
			{
				config.fov = input.tag.get_property("FOCAL_LENGTH", config.fov);
				config.radius_x = input.tag.get_property("RADIUS_X", config.radius_x);
				config.radius_y = input.tag.get_property("RADIUS_Y", config.radius_y);
				config.feather = input.tag.get_property("FEATHER", config.feather);
				config.center_x = input.tag.get_property("CENTER_X", config.center_x);
				config.center_y = input.tag.get_property("CENTER_Y", config.center_y);
				config.distance_x = input.tag.get_property("DISTANCE_X", config.distance_x);
				config.distance_y = input.tag.get_property("DISTANCE_Y", config.distance_y);
				config.translate_x = input.tag.get_property("TRANSLATE_X", config.translate_x);
				config.rotation = input.tag.get_property("ROTATION", config.rotation);
				config.draw_guides = input.tag.get_property("DRAW_GUIDES", config.draw_guides);
				config.mode = input.tag.get_property("MODE", config.mode);
			}
		}
	}
}



int Fuse360Main::process_buffer(VFrame *frame,
	int64_t start_position,
	double frame_rate)
{
	load_configuration();
	
	VFrame *input = new_temp(frame->get_w(), frame->get_h(), frame->get_color_model());
	
	read_frame(input, 
		0, 
		start_position, 
		frame_rate,
		0); // use opengl

	calculate_extents();

// always rotate it
	if(!EQUIV(config.rotation, 0))
	{
		int center_x = w / 2;
		int center_y = h / 2;
		int center_x1 = w / 4;
		int center_y1 = h / 2;
		int center_x2 = w * 3 / 4;
		int center_y2 = h / 2;
		int radius_x = 0;
		int radius_y = 0;

		radius_x = config.radius_x * w / 2;
		radius_y = config.radius_y * h;

		if(!affine) affine = new AffineEngine(PluginClient::smp + 1, 
			PluginClient::smp + 1);
		affine->set_in_pivot(center_x1, center_y1);
		affine->set_in_viewport(0, 0, center_x, h);
		affine->set_out_viewport(0, 0, center_x, h);
		affine->rotate(get_output(),
			input, 
			config.rotation);

		affine->set_in_pivot(center_x2, center_y2);
		affine->set_in_viewport(center_x, 0, w - center_x, h);
		affine->set_out_viewport(center_x, 0, w - center_x, h);
		affine->rotate(get_output(),
			input, 
			-config.rotation);

		input->copy_from(get_output());
		get_output()->clear_frame();

	}

	if(config.mode == Fuse360Config::DO_NOTHING)
	{
		get_output()->copy_from(input);
	}
	else
	{

		if(!engine) engine = new Fuse360Engine(this);
		engine->process_packages();
	}



	if(config.draw_guides)
	{

		get_output()->draw_line(center_x, 0, center_x, h);

// draw lenses
		get_output()->draw_oval(center_x1 - radius_x + distance_x, 
			center_y1 - radius_y + distance_y, 
			center_x1 + radius_x + distance_x, 
			center_y1 + radius_y + distance_y);
		get_output()->draw_oval(center_x2 - radius_x - distance_x, 
			center_y2 - radius_y - distance_y, 
			center_x2 + radius_x - distance_x, 
			center_y2 + radius_y - distance_y);

// draw feather
		get_output()->draw_line(feather_x1, 0, feather_x1, h);
		get_output()->draw_line(feather_x2, 0, feather_x2, h);
		

	}

	return 0;
}


void Fuse360Main::calculate_extents()
{
	w = get_output()->get_w();
	h = get_output()->get_h();
	center_x = w / 2;
	center_y = h / 2;
	center_x1 = w * 1 / 4;
	center_y1 = h / 2;
	center_x2 = w * 3 / 4;
	center_y2 = h / 2;
	radius_x = 0;
	radius_y = 0;
	distance_x = (int)((50 - config.distance_x) * w / 100 / 2);
	distance_y = (int)(config.distance_y * h / 100 / 2);
	feather = (int)(config.feather * w / 100);
 	feather_x1 = center_x - (int)(config.feather * w / 100 / 2);
	feather_x2 = center_x + (int)(config.feather * w / 100 / 2);
	radius_x = config.radius_x * w / 2;
	radius_y = config.radius_y * h;
//printf("Fuse360Main::calculate_extents %d radius_y=%d\n", __LINE__, radius_y);
}





Fuse360Package::Fuse360Package()
 : LoadPackage() {}





Fuse360Unit::Fuse360Unit(Fuse360Engine *engine, Fuse360Main *plugin)
 : LoadClient(engine)
{
	this->plugin = plugin;
}


Fuse360Unit::~Fuse360Unit()
{
}



void Fuse360Unit::process_blend(Fuse360Package *pkg)
{
	VFrame *input = plugin->get_temp();
	VFrame *output = plugin->get_output();

	int row1 = pkg->row1;
 	int row2 = pkg->row2;
	int width = input->get_w();
	int height = input->get_h();

#define PROCESS_BLEND(type, components, chroma) \
{ \
	type **in_rows = (type**)input->get_rows(); \
	type **out_rows = (type**)output->get_rows(); \
	type black[4] = { 0, chroma, chroma, 0 }; \
 \
	for(int y = row1; y < row2; y++) \
	{ \
		type *out_row = out_rows[y]; \
		type *in_row = in_rows[y]; \
 \
		for(int x = 0; x < width; x++) \
		{ \
		} \
	} \
}


	switch(plugin->get_input()->get_color_model())
	{
		case BC_RGB888:
			PROCESS_BLEND(unsigned char, 3, 0x0);
			break;
		case BC_RGBA8888:
			PROCESS_BLEND(unsigned char, 4, 0x0);
			break;
		case BC_RGB_FLOAT:
			PROCESS_BLEND(float, 3, 0.0);
			break;
		case BC_RGBA_FLOAT:
			PROCESS_BLEND(float, 4, 0.0);
			break;
		case BC_YUV888:
			PROCESS_BLEND(unsigned char, 3, 0x80);
			break;
		case BC_YUVA8888:
			PROCESS_BLEND(unsigned char, 4, 0x80);
			break;
	}
	
}

// interpolate 1 eye
#define BLEND_PIXEL(type, components) \
 	if(x_in < 0.0 || x_in >= w - 1 || \
		y_in < 0.0 || y_in >= h - 1) \
	{ \
		*out_row++ = black[0]; \
		*out_row++ = black[1]; \
		*out_row++ = black[2]; \
		if(components == 4) *out_row++ = black[3]; \
	} \
	else \
	{ \
		float y1_fraction = y_in - floor(y_in); \
		float y2_fraction = 1.0 - y1_fraction; \
		float x1_fraction = x_in - floor(x_in); \
		float x2_fraction = 1.0 - x1_fraction; \
		type *in_pixel1 = in_rows[(int)y_in] + (int)x_in * components; \
		type *in_pixel2 = in_rows[(int)y_in + 1] + (int)x_in * components; \
		for(int i = 0; i < components; i++) \
		{ \
			*out_row++ = (type)(in_pixel1[i] * x2_fraction * y2_fraction + \
				in_pixel2[i] * x2_fraction * y1_fraction + \
				in_pixel1[i + components] * x1_fraction * y2_fraction + \
				in_pixel2[i + components] * x1_fraction * y1_fraction); \
		} \
	}


// interpolate 2 eyes
#define BLEND_PIXEL2(type, components) \
	type pixel1[components], pixel2[components]; \
 \
/* calculate the left eye */ \
 	if(x_in1 < 0.0 || x_in1 >= w - 1 || \
		y_in1 < 0.0 || y_in1 >= h - 1) \
	{ \
		pixel1[0] = black[0]; \
		pixel1[1] = black[1]; \
		pixel1[2] = black[2]; \
		if(components == 4) pixel1[3] = black[3]; \
	} \
	else \
	{ \
		float y1_fraction = y_in1 - floor(y_in1); \
		float y2_fraction = 1.0 - y1_fraction; \
		float x1_fraction = x_in1 - floor(x_in1); \
		float x2_fraction = 1.0 - x1_fraction; \
		type *in_pixel1 = in_rows[(int)y_in1] + (int)x_in1 * components; \
		type *in_pixel2 = in_rows[(int)y_in1 + 1] + (int)x_in1 * components; \
		for(int i = 0; i < components; i++) \
		{ \
			pixel1[i] = (type)(in_pixel1[i] * x2_fraction * y2_fraction + \
				in_pixel2[i] * x2_fraction * y1_fraction + \
				in_pixel1[i + components] * x1_fraction * y2_fraction + \
				in_pixel2[i + components] * x1_fraction * y1_fraction); \
		} \
	} \
 \
 \
/* calculate the right eye */ \
 	if(x_in2 < 0.0 || x_in2 >= w - 1 || \
		y_in2 < 0.0 || y_in2 >= h - 1) \
	{ \
		pixel2[0] = black[0]; \
		pixel2[1] = black[1]; \
		pixel2[2] = black[2]; \
		if(components == 4) pixel2[3] = black[3]; \
	} \
	else \
	{ \
		float y1_fraction = y_in2 - floor(y_in2); \
		float y2_fraction = 1.0 - y1_fraction; \
		float x1_fraction = x_in2 - floor(x_in2); \
		float x2_fraction = 1.0 - x1_fraction; \
		type *in_pixel1 = in_rows[(int)y_in2] + (int)x_in2 * components; \
		type *in_pixel2 = in_rows[(int)y_in2 + 1] + (int)x_in2 * components; \
		for(int i = 0; i < components; i++) \
		{ \
			pixel2[i] = (type)(in_pixel1[i] * x2_fraction * y2_fraction + \
				in_pixel2[i] * x2_fraction * y1_fraction + \
				in_pixel1[i + components] * x1_fraction * y2_fraction + \
				in_pixel2[i + components] * x1_fraction * y1_fraction); \
		} \
	} \
 \
/* blend the 2 eyes */ \
	for(int i = 0; i < components; i++) \
	{ \
		*out_row++ = (type)(pixel1[i] * (1.0f - fraction) + \
			pixel2[i] * fraction); \
	} \




#define COLORSPACE_SWITCH(function) \
	switch(plugin->get_input()->get_color_model()) \
	{ \
		case BC_RGB888: \
			function(unsigned char, 3, 0x0); \
			break; \
		case BC_RGBA8888: \
			function(unsigned char, 4, 0x0); \
			break; \
		case BC_RGB_FLOAT: \
			function(float, 3, 0.0); \
			break; \
		case BC_RGBA_FLOAT: \
			function(float, 4, 0.0); \
			break; \
		case BC_YUV888: \
			function(unsigned char, 3, 0x80); \
			break; \
		case BC_YUVA8888: \
			function(unsigned char, 4, 0x80); \
			break; \
	}


double Fuse360Unit::calculate_max_z(double a, double r)
{
	if(a < 0) a += 2 * M_PI;

	if(a < M_PI / 4)
	{
		return r / cos(a); // bottom right edge
	}
	else
	if(a < 3 * M_PI / 4)
	{
		return r / cos(M_PI / 2 - a); // bottom edge
	}
	else
	if(a < 5 * M_PI / 4)
	{
		return r / cos(M_PI - a); // left edge
	}
	else
	if(a < 7 * M_PI / 4)
	{
		return r / cos(3 * M_PI / 2 - a); // top edge
	}
	else
	{
		return r / cos(a); // top right edge
	}
}

void Fuse360Unit::process_stretch(Fuse360Package *pkg)
{
	VFrame *input = plugin->get_temp();
	VFrame *output = plugin->get_output();

	float fov = plugin->config.fov;
	int row1 = pkg->row1;
	int row2 = pkg->row2;
	int center_x1 = plugin->center_x1;
	int center_x2 = plugin->center_x2;
	int center_y1 = plugin->center_y1;
	int center_y2 = plugin->center_y2;
	int center_x = plugin->center_x;
	int center_y = plugin->center_y;
	int w = plugin->w;
	int h = plugin->h;
	double radius = plugin->radius_x;


#define PROCESS_STRETCH(type, components, chroma) \
{ \
	type **in_rows = (type**)input->get_rows(); \
	type **out_rows = (type**)output->get_rows(); \
	type black[4] = { 0, chroma, chroma, 0 }; \
 \
	for(int y = row1; y < row2; y++) \
	{ \
		type *out_row = out_rows[y]; \
		type *in_row = in_rows[y]; \
		double y_diff = y - center_y1; \
 \
/* left eye */ \
		for(int x = 0; x < center_x; x++) \
		{ \
			double x_diff = x - center_x1; \
/* polar output coordinate */ \
			double z = hypot(x_diff, y_diff); \
			double a = atan2(y_diff, x_diff); \
 \
/* scale the magnitude to the radius */ \
			double scaled_z = z * radius / calculate_max_z(a, radius); \
/* xy input coordinate */ \
			double x_in = scaled_z * cos(a) + center_x1; \
			double y_in = scaled_z * sin(a) + center_y1; \
 \
 			if(x_in < center_x) \
 			{ \
				BLEND_PIXEL(type, components) \
			} \
		} \
	} \
 \
	for(int y = row1; y < row2; y++) \
	{ \
		type *out_row = out_rows[y] + center_x * components; \
		type *in_row = in_rows[y]; \
		double y_diff = y - center_y2; \
 \
/* right eye */ \
		for(int x = center_x; x < w; x++) \
		{ \
			double x_diff = x - center_x2; \
/* polar output coordinate */ \
			double z = hypot(x_diff, y_diff); \
			double a = atan2(y_diff, x_diff); \
 \
/* scale the magnitude to the radius */ \
			double scaled_z = z * radius / calculate_max_z(a, radius); \
/* xy input coordinate */ \
			double x_in = scaled_z * cos(a) + center_x2; \
			double y_in = scaled_z * sin(a) + center_y2; \
 \
 			if(x_in >= center_x) \
 			{ \
				BLEND_PIXEL(type, components) \
			} \
		} \
	} \
}



	COLORSPACE_SWITCH(PROCESS_STRETCH)
}





void Fuse360Unit::process_standard(Fuse360Package *pkg)
{
	VFrame *input = plugin->get_temp();
	VFrame *output = plugin->get_output();

	float fov = plugin->config.fov;
	int row1 = pkg->row1;
	int row2 = pkg->row2;
	int center_x1 = plugin->center_x1;
	int center_x2 = plugin->center_x2;
	int center_y1 = plugin->center_y1;
	int center_y2 = plugin->center_y2;
	int center_x = plugin->center_x;
	int center_y = plugin->center_y;
	int feather = plugin->feather;
	int feather_x1 = plugin->feather_x1;
	int feather_x2 = plugin->feather_x2;
	int w = plugin->w;
	int h = plugin->h;
	float radius_x = plugin->radius_x;
	float radius_y = plugin->radius_y;
	int distance_x = plugin->distance_x;
	int distance_y = plugin->distance_y;
// field of view of the fisheye
	float FOV = M_PI;

#define PROCESS_STANDARD(type, components, chroma) \
{ \
	type **in_rows = (type**)input->get_rows(); \
	type **out_rows = (type**)output->get_rows(); \
	type black[4] = { 0, chroma, chroma, 0 }; \
 \
/* left eye */ \
	for(int y = row1; y < row2; y++) \
	{ \
		type *out_row = out_rows[y]; \
 \
/* -M_PI/2 to M_PI/2 */ \
		float y_diff = y - center_y1; \
/* polar angles */ \
		float phi = M_PI / 2 * (y_diff / radius_y); \
		for(int x = 0; x < feather_x1; x++) \
		{ \
			float x_diff = x - center_x1; \
/* polar angles */ \
/* -M_PI/2 to M_PI/2 */ \
			float theta = M_PI / 2 * (x_diff / radius_x); \
/* vector in 3D space */ \
			float vect_x = cos(phi) * sin(theta); \
			float vect_y = cos(phi) * cos(theta); \
			float vect_z = sin(phi); \
/* fisheye angle & radius */ \
			float theta2 = atan2(vect_z, vect_x); \
			float phi2 = atan2(hypot(vect_x, vect_z), vect_y); \
			float r = radius_x * 2 * phi2 / FOV; \
/* pixel in fisheye space */ \
			float x_in = center_x1 + r * cos(theta2); \
			float y_in = center_y1 + r * sin(theta2); \
 \
 			BLEND_PIXEL(type, components) \
		} \
	} \
 \
 \
 \
/* right eye */ \
	for(int y = row1; y < row2; y++) \
	{ \
		type *out_row = out_rows[y] + components * feather_x2; \
 \
/* -M_PI/2 to M_PI/2 */ \
		float y_diff = y - center_y2; \
/* polar angles */ \
		float phi = M_PI / 2 * (y_diff / radius_y); \
		for(int x = feather_x2; x < w; x++) \
		{ \
			float x_diff = x - center_x2; \
/* polar angles */ \
/* -M_PI/2 to M_PI/2 */ \
			float theta = M_PI / 2 * (x_diff / radius_x); \
/* vector in 3D space */ \
			float vect_x = cos(phi) * sin(theta); \
			float vect_y = cos(phi) * cos(theta); \
			float vect_z = sin(phi); \
/* fisheye angle & radius */ \
			float theta2 = atan2(vect_z, vect_x); \
			float phi2 = atan2(hypot(vect_x, vect_z), vect_y); \
			float r = radius_x * 2 * phi2 / FOV; \
/* pixel in fisheye space */ \
			float x_in = center_x2 + r * cos(theta2); \
			float y_in = center_y2 + r * sin(theta2); \
 \
 			BLEND_PIXEL(type, components) \
		} \
	} \
 \
 \
}

	COLORSPACE_SWITCH(PROCESS_STANDARD)
}



void Fuse360Unit::process_package(LoadPackage *package)
{
	Fuse360Package *pkg = (Fuse360Package*)package;

	switch(plugin->config.mode)
	{
		case Fuse360Config::STRETCHXY:
			process_stretch(pkg);
			break;
		case Fuse360Config::STANDARD:
			process_standard(pkg);
			break;
		case Fuse360Config::BLEND:
			process_blend(pkg);
			break;
	}
}





Fuse360Engine::Fuse360Engine(Fuse360Main *plugin)
// : LoadServer(plugin->PluginClient::smp + 1, plugin->PluginClient::smp + 1)
 : LoadServer(1, 1)
{
	this->plugin = plugin;
}

Fuse360Engine::~Fuse360Engine()
{
}

void Fuse360Engine::init_packages()
{
	for(int i = 0; i < LoadServer::get_total_packages(); i++)
	{
		Fuse360Package *package = (Fuse360Package*)LoadServer::get_package(i);
		package->row1 = plugin->get_input()->get_h() * i / LoadServer::get_total_packages();
		package->row2 = plugin->get_input()->get_h() * (i + 1) / LoadServer::get_total_packages();
	}
}

LoadClient* Fuse360Engine::new_client()
{
	return new Fuse360Unit(this, plugin);
}

LoadPackage* Fuse360Engine::new_package()
{
	return new Fuse360Package;
}


