
/*
 * CINELERRA
 * Copyright (C) 2008-2016 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 "bcdisplayinfo.h"
#include "clip.h"
#include "bchash.h"
#include "filexml.h"
#include "reframert.h"
#include "guicast.h"
#include "language.h"
#include "pluginvclient.h"
#include "theme.h"
#include "transportque.h"

#include <string.h>




REGISTER_PLUGIN(ReframeRT);



ReframeRTConfig::ReframeRTConfig()
{
	reset();
}

void ReframeRTConfig::reset()
{
	num = 1.0;
	denom = 1.0;
	stretch = 0;
	interp = 0;
	optic_flow = 1;
}

int ReframeRTConfig::equivalent(ReframeRTConfig &src)
{
	return fabs(num - src.num) < 0.0001 &&
		fabs(denom - src.denom) < 0.0001 &&
		stretch == src.stretch &&
		interp == src.interp;
}

void ReframeRTConfig::copy_from(ReframeRTConfig &src)
{
	this->num = src.num;
	this->denom = src.denom;
	this->stretch = src.stretch;
	this->interp = src.interp;
}

void ReframeRTConfig::interpolate(ReframeRTConfig &prev,
	ReframeRTConfig &next,
	int64_t prev_frame,
	int64_t next_frame,
	int64_t current_frame)
{
	this->interp = prev.interp;
	this->stretch = prev.stretch;
	this->denom = prev.denom;

	if (this->interp && prev_frame != next_frame)
	{
		double next_weight = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
		double prev_weight = (double)(next_frame - current_frame) / (next_frame - prev_frame);
		double prev_slope = prev.num / prev.denom, next_slope = next.num / next.denom;
		// for interpolation, this is (for now) a simple linear slope to the next keyframe.
		double scale = prev_slope * prev_weight + next_slope * next_weight;
		this->num = this->denom * scale;
	}
	else
	{
		this->num = prev.num;
	}
}

void ReframeRTConfig::boundaries()
{
	if(num < 0.0001) num = 0.0001;
	if(denom < 0.0001) denom = 0.0001;
}








ReframeRTWindow::ReframeRTWindow(ReframeRT *plugin)
 : PluginClientWindow(plugin, xS(230), yS(235), xS(230), yS(235), 0)
{
	this->plugin = plugin;
}

ReframeRTWindow::~ReframeRTWindow()
{
}

void ReframeRTWindow::create_objects()
{
	int x = plugin->get_theme()->window_border;
	int y = plugin->get_theme()->window_border;
	BC_Title *title;
	add_subwindow(title = new BC_Title(x, y, _("Input frames:")));
	y += title->get_h() + plugin->get_theme()->widget_border;
	num = new ReframeRTNum(plugin,
		this,
		x,
		y);
	num->create_objects();
	num->set_increment(1.0);

	y += num->get_h() + plugin->get_theme()->widget_border;
	add_subwindow(title = new BC_Title(x, y, _("Output frames:")));
	y += title->get_h() + plugin->get_theme()->widget_border;
	denom = new ReframeRTDenom(plugin,
		this,
		x,
		y);
	denom->create_objects();
	denom->set_increment(1.0);


	y += denom->get_h() + plugin->get_theme()->widget_border;
	add_subwindow(stretch = new ReframeRTStretch(plugin, this, x, y));
	y += yS(30);
	add_subwindow(downsample = new ReframeRTDownsample(plugin, this, x, y));
	y += yS(30);
	add_subwindow(interpolate = new ReframeRTInterpolate(plugin, this, x, y));
	y += yS(40);
	add_subwindow(reset = new ReframeRTReset(plugin, this, x, y));
	show_window();
}

void ReframeRTWindow::update()
{
	num->update((float)plugin->config.num);
	denom->update((float)plugin->config.denom);
	stretch->update(plugin->config.stretch);
	downsample->update(!plugin->config.stretch);
	interpolate->update(plugin->config.interp);
}






ReframeRTNum::ReframeRTNum(ReframeRT *plugin,
	ReframeRTWindow *gui,
	int x,
	int y)
 : BC_TumbleTextBox(gui,
 	(float)plugin->config.num,
	(float)0.0001,
	(float)1000,
 	x,
	y,
	gui->get_w() - plugin->get_theme()->window_border * 3)
{
	this->plugin = plugin;
}

int ReframeRTNum::handle_event()
{
	plugin->config.num = atof(get_text());
	plugin->config.boundaries();
	plugin->send_configure_change();
	return 1;
}


ReframeRTDenom::ReframeRTDenom(ReframeRT *plugin,
	ReframeRTWindow *gui,
	int x,
	int y)
 : BC_TumbleTextBox(gui,
 	(float)plugin->config.denom,
	(float)-1000,
	(float)1000,
 	x,
	y,
	gui->get_w() - plugin->get_theme()->window_border * 3)
{
	this->plugin = plugin;
}

int ReframeRTDenom::handle_event()
{
	plugin->config.denom = atof(get_text());
	plugin->config.boundaries();
	plugin->send_configure_change();
	return 1;
}


ReframeRTStretch::ReframeRTStretch(ReframeRT *plugin,
	ReframeRTWindow *gui,
	int x,
	int y)
 : BC_Radial(x, y, plugin->config.stretch, _("Stretch"))
{
	this->plugin = plugin;
	this->gui = gui;
}

int ReframeRTStretch::handle_event()
{
	plugin->config.stretch = get_value();
	gui->downsample->update(!get_value());
	plugin->send_configure_change();
	return 1;
}


ReframeRTDownsample::ReframeRTDownsample(ReframeRT *plugin,
	ReframeRTWindow *gui,
	int x,
	int y)
 : BC_Radial(x, y, !plugin->config.stretch, _("Downsample"))
{
	this->plugin = plugin;
	this->gui = gui;
}

int ReframeRTDownsample::handle_event()
{
	plugin->config.stretch = !get_value();
	gui->stretch->update(!get_value());
	plugin->send_configure_change();
	return 1;
}

ReframeRTInterpolate::ReframeRTInterpolate(ReframeRT *plugin,
	ReframeRTWindow *gui,
	int x,
	int y)
 : BC_CheckBox(x, y, 0, _("Interpolate"))
{
	this->plugin = plugin;
	this->gui = gui;
}

int ReframeRTInterpolate::handle_event()
{
	plugin->config.interp = get_value();
	gui->interpolate->update(get_value());
	plugin->send_configure_change();
	return 1;
}


ReframeRTReset::ReframeRTReset(ReframeRT *plugin, ReframeRTWindow *gui, int x, int y)
 : BC_GenericButton(x, y, _("Reset"))
{
	this->plugin = plugin;
	this->gui = gui;
}
ReframeRTReset::~ReframeRTReset()
{
}
int ReframeRTReset::handle_event()
{
	plugin->config.reset();
	gui->update();
	plugin->send_configure_change();
	return 1;
}



ReframeRT::ReframeRT(PluginServer *server)
 : PluginVClient(server)
{
}

ReframeRT::~ReframeRT()
{

}

const char* ReframeRT::plugin_title() { return N_("ReframeRT"); }
int ReframeRT::is_realtime() { return 1; }
int ReframeRT::is_synthesis() { return 1; }


NEW_WINDOW_MACRO(ReframeRT, ReframeRTWindow)
LOAD_CONFIGURATION_MACRO(ReframeRT, ReframeRTConfig)

int ReframeRT::process_buffer(VFrame *frame,
		int64_t start_position,
		double frame_rate)
{
	int64_t input_frame = get_source_start();
	ReframeRTConfig prev_config, next_config;
	KeyFrame *tmp_keyframe, *next_keyframe = get_prev_keyframe(get_source_start());
	int64_t tmp_position, next_position;
	int64_t segment_len;
	double input_rate = frame_rate;
	int is_current_keyframe;

// if there are no keyframes, the default keyframe is used, and its position is always 0;
// if there are keyframes, the first keyframe can be after the effect start (and it controls settings before it)
// so let's calculate using a fake keyframe with the same settings but position == effect start
	KeyFrame *fake_keyframe = new KeyFrame();
	fake_keyframe->copy_from(next_keyframe);
	fake_keyframe->position = local_to_edl(get_source_start());
	next_keyframe = fake_keyframe;

	// calculate input_frame accounting for all previous keyframes
	do
	{
		tmp_keyframe = next_keyframe;
		next_keyframe = get_next_keyframe(tmp_keyframe->position+1, 0);

		tmp_position = edl_to_local(tmp_keyframe->position);
		next_position = edl_to_local(next_keyframe->position);

		is_current_keyframe =
			next_position > start_position // the next keyframe is after the current position
			|| next_keyframe->position == tmp_keyframe->position // there are no more keyframes
			|| !next_keyframe->position; // there are no keyframes at all

		if (is_current_keyframe)
			segment_len = start_position - tmp_position;
		else
			segment_len = next_position - tmp_position;

		read_data(next_keyframe);
		next_config.copy_from(config);
		read_data(tmp_keyframe);
		prev_config.copy_from(config);
		config.interpolate(prev_config, next_config, tmp_position, next_position, tmp_position + segment_len);

		// the area under the curve is the number of frames to advance
		// as long as interpolate() uses a linear slope we can use geometry to determine this
		// if interpolate() changes to use a curve then this needs use (possibly) the definite integral
		double prev_scale = prev_config.num / prev_config.denom;
		double config_scale = config.num / config.denom;
		input_frame += (int64_t)(segment_len * ((prev_scale + config_scale) / 2));
	} while (!is_current_keyframe);

// Change rate
	if (!config.stretch)
	{
		input_rate *= config.num / config.denom;

	}

// printf("ReframeRT::process_buffer %d %lld %f %lld %f\n",
// __LINE__,
// start_position,
// frame_rate,
// input_frame,
// input_rate);

	read_frame(frame,
		0,
		input_frame,
		input_rate,
		0);

	delete fake_keyframe;

	return 0;
}



void ReframeRT::save_data(KeyFrame *keyframe)
{
	FileXML output;

// cause data to be stored directly in text
	output.set_shared_output(keyframe->xbuf);
	output.tag.set_title("REFRAMERT");
// for backwards compatability, we call num scale
	output.tag.set_property("SCALE", config.num);
	output.tag.set_property("DENOM", config.denom);
	output.tag.set_property("STRETCH", config.stretch);
	output.tag.set_property("INTERPOLATE", config.interp);
	output.append_tag();
	output.tag.set_title("/REFRAMERT");
	output.append_tag();
	output.append_newline();
	output.terminate_string();
}

void ReframeRT::read_data(KeyFrame *keyframe)
{
	FileXML input;

	input.set_shared_input(keyframe->xbuf);

	while(!input.read_tag())
	{
		if(input.tag.title_is("REFRAMERT"))
		{
// for backwards compatability, we call num scale
			config.num = input.tag.get_property("SCALE", config.num);
			config.denom = input.tag.get_property("DENOM", config.denom);
			config.stretch = input.tag.get_property("STRETCH", config.stretch);
			config.interp = input.tag.get_property("INTERPOLATE", config.interp);
		}
	}
}

void ReframeRT::update_gui()
{
	if(thread)
	{
		int changed = load_configuration();

		if(changed)
		{
			ReframeRTWindow* window = (ReframeRTWindow*)thread->window;
			window->lock_window("ReframeRT::update_gui");
			window->num->update((float)config.num);
			window->denom->update((float)config.denom);
			window->stretch->update(config.stretch);
			window->downsample->update(!config.stretch);
			window->interpolate->update(config.interp);
			window->unlock_window();
		}
	}
}





