/*
 * CINELERRA
 * Copyright (C) 1997-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 "bcsignals.h"
#include "clip.h"
#include "filexml.h"
#include "interpolatevideo.h"
#include "interpolatewindow.h"
#include "language.h"
#include "motionscan.h"
#include "opticflow.h"
#include "transportque.inc"
#include <unistd.h>








InterpolateVideoConfig::InterpolateVideoConfig()
{
	input_rate = (double)30000 / 1001;
	use_keyframes = 0;
	optic_flow = 1;
	draw_vectors = 1;
	search_radius = 16;
	macroblock_size = 16;
}

void InterpolateVideoConfig::copy_from(InterpolateVideoConfig *config)
{
	this->input_rate = config->input_rate;
	this->use_keyframes = config->use_keyframes;
	this->optic_flow = config->optic_flow;
	this->draw_vectors = config->draw_vectors;
	this->search_radius = config->search_radius;
	this->macroblock_size = config->macroblock_size;
}

int InterpolateVideoConfig::equivalent(InterpolateVideoConfig *config)
{
	return EQUIV(this->input_rate, config->input_rate) &&
		(this->use_keyframes == config->use_keyframes) &&
		this->optic_flow == config->optic_flow &&
		this->draw_vectors == config->draw_vectors &&
		this->search_radius == config->search_radius &&
		this->macroblock_size == config->macroblock_size;
}








REGISTER_PLUGIN(InterpolateVideo)









InterpolateVideo::InterpolateVideo(PluginServer *server)
 : PluginVClient(server)
{
	optic_flow_engine = 0;
	warp_engine = 0;
	blend_engine = 0;
	bzero(frames, sizeof(VFrame*) * 2);
	for(int i = 0; i < 2; i++)
		frame_number[i] = -1;
	last_position = -1;
	last_rate = -1;
	last_macroblock_size = 0;
	last_search_radius = 0;
	total_macroblocks = 0;
}


InterpolateVideo::~InterpolateVideo()
{
	delete optic_flow_engine;
	delete warp_engine;
	delete blend_engine;
	if(frames[0]) delete frames[0];
	if(frames[1]) delete frames[1];
	macroblocks.remove_all_objects();
}


void InterpolateVideo::fill_border(double frame_rate, int64_t start_position)
{
// A border frame changed or the start position is identical to the last 
// start position.
//printf("InterpolateVideo::fill_border %d %lld %lld\n", __LINE__, range_start, range_end);



	if(range_start != frame_number[0] || 
//		last_position != start_position ||
		!EQUIV(last_rate, frame_rate))
	{
// Keep reads sequential by being smart about what frame to read
		if(range_start == frame_number[1])
		{
			VFrame *temp = frames[0];
			frames[0] = frames[1];
			frames[1] = temp;
			
			
			frame_number[0] = frame_number[1];
			frame_number[1] = -1;
		}
		else
		{
//printf("InterpolateVideo::fill_border 1 %lld\n", range_start);
			read_frame(frames[0], 
				0, 
				range_start + (get_direction() == PLAY_REVERSE ? 1 : 0), 
				active_input_rate,
				0);
		}
	}

	if(range_end != frame_number[1] || 
//		last_position != start_position ||
		!EQUIV(last_rate, frame_rate))
	{
//printf("InterpolateVideo::fill_border 2 %lld\n", range_end);
		read_frame(frames[1], 
			0, 
			range_end + (get_direction() == PLAY_REVERSE ? 1 : 0), 
			active_input_rate,
			0);
	}

//printf("InterpolateVideo::fill_border %d\n", __LINE__);
	last_position = start_position;
	last_rate = frame_rate;
	frame_number[0] = range_start;
	frame_number[1] = range_end;
}





void InterpolateVideo::draw_pixel(VFrame *frame, int x, int y)
{
	if(!(x >= 0 && y >= 0 && x < frame->get_w() && y < frame->get_h())) return;

#define DRAW_PIXEL(x, y, components, do_yuv, max, type) \
{ \
	type **rows = (type**)frame->get_rows(); \
	rows[y][x * components] = max - rows[y][x * components]; \
	if(!do_yuv) \
	{ \
		rows[y][x * components + 1] = max - rows[y][x * components + 1]; \
		rows[y][x * components + 2] = max - rows[y][x * components + 2]; \
	} \
	else \
	{ \
		rows[y][x * components + 1] = (max / 2 + 1) - rows[y][x * components + 1]; \
		rows[y][x * components + 2] = (max / 2 + 1) - rows[y][x * components + 2]; \
	} \
	if(components == 4) \
		rows[y][x * components + 3] = max; \
}


	switch(frame->get_color_model())
	{
		case BC_RGB888:
			DRAW_PIXEL(x, y, 3, 0, 0xff, unsigned char);
			break;
		case BC_RGBA8888:
			DRAW_PIXEL(x, y, 4, 0, 0xff, unsigned char);
			break;
		case BC_RGB_FLOAT:
			DRAW_PIXEL(x, y, 3, 0, 1.0, float);
			break;
		case BC_RGBA_FLOAT:
			DRAW_PIXEL(x, y, 4, 0, 1.0, float);
			break;
		case BC_YUV888:
			DRAW_PIXEL(x, y, 3, 1, 0xff, unsigned char);
			break;
		case BC_YUVA8888:
			DRAW_PIXEL(x, y, 4, 1, 0xff, unsigned char);
			break;
		case BC_RGB161616:
			DRAW_PIXEL(x, y, 3, 0, 0xffff, uint16_t);
			break;
		case BC_YUV161616:
			DRAW_PIXEL(x, y, 3, 1, 0xffff, uint16_t);
			break;
		case BC_RGBA16161616:
			DRAW_PIXEL(x, y, 4, 0, 0xffff, uint16_t);
			break;
		case BC_YUVA16161616:
			DRAW_PIXEL(x, y, 4, 1, 0xffff, uint16_t);
			break;
	}
}


void InterpolateVideo::draw_line(VFrame *frame, int x1, int y1, int x2, int y2)
{
	int w = labs(x2 - x1);
	int h = labs(y2 - y1);
//printf("InterpolateVideo::draw_line 1 %d %d %d %d\n", x1, y1, x2, y2);

	if(!w && !h)
	{
		draw_pixel(frame, x1, y1);
	}
	else
	if(w > h)
	{
// Flip coordinates so x1 < x2
		if(x2 < x1)
		{
			y2 ^= y1;
			y1 ^= y2;
			y2 ^= y1;
			x1 ^= x2;
			x2 ^= x1;
			x1 ^= x2;
		}
		int numerator = y2 - y1;
		int denominator = x2 - x1;
		for(int i = x1; i < x2; i++)
		{
			int y = y1 + (int64_t)(i - x1) * (int64_t)numerator / (int64_t)denominator;
			draw_pixel(frame, i, y);
		}
	}
	else
	{
// Flip coordinates so y1 < y2
		if(y2 < y1)
		{
			y2 ^= y1;
			y1 ^= y2;
			y2 ^= y1;
			x1 ^= x2;
			x2 ^= x1;
			x1 ^= x2;
		}
		int numerator = x2 - x1;
		int denominator = y2 - y1;
		for(int i = y1; i < y2; i++)
		{
			int x = x1 + (int64_t)(i - y1) * (int64_t)numerator / (int64_t)denominator;
			draw_pixel(frame, x, i);
		}
	}
//printf("InterpolateVideo::draw_line 2\n");
}

#define ARROW_SIZE 10
void InterpolateVideo::draw_arrow(VFrame *frame, 
	int x1, 
	int y1, 
	int x2, 
	int y2)
{
	double angle = atan((float)(y2 - y1) / (float)(x2 - x1));
	double angle1 = angle + (float)145 / 360 * 2 * 3.14159265;
	double angle2 = angle - (float)145 / 360 * 2 * 3.14159265;
	int x3;
	int y3;
	int x4;
	int y4;
	if(x2 < x1)
	{
		x3 = x2 - (int)(ARROW_SIZE * cos(angle1));
		y3 = y2 - (int)(ARROW_SIZE * sin(angle1));
		x4 = x2 - (int)(ARROW_SIZE * cos(angle2));
		y4 = y2 - (int)(ARROW_SIZE * sin(angle2));
	}
	else
	{
		x3 = x2 + (int)(ARROW_SIZE * cos(angle1));
		y3 = y2 + (int)(ARROW_SIZE * sin(angle1));
		x4 = x2 + (int)(ARROW_SIZE * cos(angle2));
		y4 = y2 + (int)(ARROW_SIZE * sin(angle2));
	}

// Main vector
	draw_line(frame, x1, y1, x2, y2);
//	draw_line(frame, x1, y1 + 1, x2, y2 + 1);

// Arrow line
	if(abs(y2 - y1) || abs(x2 - x1)) draw_line(frame, x2, y2, x3, y3);
//	draw_line(frame, x2, y2 + 1, x3, y3 + 1);
// Arrow line
	if(abs(y2 - y1) || abs(x2 - x1)) draw_line(frame, x2, y2, x4, y4);
//	draw_line(frame, x2, y2 + 1, x4, y4 + 1);
}

void InterpolateVideo::draw_rect(VFrame *frame, 
	int x1, 
	int y1, 
	int x2, 
	int y2)
{
	draw_line(frame, x1, y1, x2, y1);
	draw_line(frame, x2, y1, x2, y2);
	draw_line(frame, x2, y2, x1, y2);
	draw_line(frame, x1, y2, x1, y1);
}


void InterpolateVideo::create_macroblocks()
{
// Get macroblock size
	x_macroblocks = frames[0]->get_w() / config.macroblock_size;
	y_macroblocks = frames[0]->get_h() / config.macroblock_size;
// printf("InterpolateVideo::create_macroblocks %d %d %d %d\n", 
// __LINE__, 
// config.macroblock_size,
// x_macroblocks,
// y_macroblocks);

	if(config.macroblock_size * x_macroblocks < frames[0]->get_w())
	{
		x_macroblocks++;
	}
	
	if(config.macroblock_size * y_macroblocks < frames[0]->get_h())
	{
		y_macroblocks++;
	}
	
	total_macroblocks = x_macroblocks * y_macroblocks;

	if(total_macroblocks != macroblocks.size())
	{
		macroblocks.remove_all_objects();
	}

	for(int i = 0; i < total_macroblocks; i++)
	{
		OpticFlowMacroblock *mb = 0;
		if(macroblocks.size() > i) 
		{
			mb = macroblocks.get(i);
		}
		else
		{
			mb = new OpticFlowMacroblock;
			macroblocks.append(mb);
		}

		mb->x = (i % x_macroblocks) * config.macroblock_size + config.macroblock_size / 2;
		mb->y = (i / x_macroblocks) * config.macroblock_size + config.macroblock_size / 2;
	}
}


void InterpolateVideo::draw_vectors(int processed)
{
// Draw arrows
	if(config.draw_vectors)
	{
		create_macroblocks();

		for(int i = 0; i < total_macroblocks; i++)
		{
			OpticFlowMacroblock *mb = macroblocks.get(i);
// 		    printf("InterpolateVideo::optic_flow %d x=%d y=%d dx=%d dy=%d\n",
// 		  	    __LINE__,
// 			    mb->x,
// 			    mb->y,
// 			    mb->dx / OVERSAMPLE,
// 			    mb->dy / OVERSAMPLE);

			if(processed)
			{
				draw_arrow(get_output(),
					mb->x,
					mb->y,
					mb->x - mb->dx / OVERSAMPLE,
					mb->y - mb->dy / OVERSAMPLE);

// debug
// 				if(mb->is_valid && mb->visible)
// 				{
// 					draw_arrow(get_output(),
// 						mb->x + 1,
// 						mb->y + 1,
// 						mb->x - mb->dx / OVERSAMPLE + 1,
// 						mb->y - mb->dy / OVERSAMPLE + 1);
// 				}
			}
			else
			{
				draw_pixel(get_output(),
					mb->x,
					mb->y);
			}
		}
		
// Draw center macroblock
		OpticFlowMacroblock *mb = macroblocks.get(
			x_macroblocks / 2 + y_macroblocks / 2 * x_macroblocks);
		draw_rect(get_output(),
			mb->x - config.macroblock_size / 2,
			mb->y - config.macroblock_size / 2,
			mb->x + config.macroblock_size / 2,
			mb->y + config.macroblock_size / 2);
		draw_rect(get_output(),
			mb->x - config.macroblock_size / 2 - config.search_radius,
			mb->y - config.macroblock_size / 2 - config.search_radius,
			mb->x + config.macroblock_size / 2 + config.search_radius,
			mb->y + config.macroblock_size / 2 + config.search_radius);
	}
}


int InterpolateVideo::angles_overlap(float dst2_angle1, 
	float dst2_angle2,
	float dst1_angle1,
	float dst1_angle2)
{
	if(dst2_angle1 < 0 || dst2_angle2 < 0)
	{
		dst2_angle1 += 2 * M_PI;
		dst2_angle2 += 2 * M_PI;
	}

	if(dst1_angle1 < 0 || dst1_angle2 < 0)
	{
		dst1_angle1 += 2 * M_PI;
		dst1_angle2 += 2 * M_PI;
	}

	if(dst1_angle1 < dst2_angle2 && 
		dst1_angle2 > dst2_angle1) return 1;

	return 0;
}



void InterpolateVideo::blend_macroblock(int number)
{
	OpticFlowMacroblock *src = macroblocks.get(number);
	struct timeval start_time;
	gettimeofday(&start_time, 0);
// 	printf("InterpolateVideo::blend_macroblock %d %d\n",
// 	__LINE__,
// 	src->is_valid);


// Copy macroblock table to local thread
	ArrayList<OpticFlowMacroblock*> local_macroblocks;
	for(int i = 0; i < macroblocks.size(); i++)
	{
		OpticFlowMacroblock *mb = new OpticFlowMacroblock;
		mb->copy_from(macroblocks.get(i));
		local_macroblocks.append(mb);
	}

// Get nearest macroblocks
	for(int i = 0; i < local_macroblocks.size(); i++)
	{
		OpticFlowMacroblock *dst = local_macroblocks.get(i);
		if(i != number && dst->is_valid)
		{

// rough estimation of angle coverage
			float angle = atan2(dst->y - src->y, dst->x - src->x);
			float dist = sqrt(SQR(dst->y - src->y) + SQR(dst->x - src->x));
			float span = sin((float)config.macroblock_size / dist);
			dst->angle1 = angle - span / 2;
			dst->angle2 = angle + span / 2;
			dst->dist = dist;
// All macroblocks start as visible
			dst->visible = 1;

// printf("InterpolateVideo::blend_macroblock %d %d x=%d y=%d span=%f angle1=%f angle2=%f dist=%f\n", 
// __LINE__, 
// i,
// dst->x, 
// dst->y, 
// span * 360 / 2 / M_PI,
// dst->angle1 * 360 / 2 / M_PI, 
// dst->angle2 * 360 / 2 / M_PI,
// dst->dist);
		}
	}
	
	for(int i = 0; i < local_macroblocks.size(); i++)
	{
// Conceil macroblocks which are hidden
		OpticFlowMacroblock *dst1 = local_macroblocks.get(i);
		if(i != number && dst1->is_valid && dst1->visible)
		{
// Find macroblock which is obstructing
			for(int j = 0; j < local_macroblocks.size(); j++)
			{
				OpticFlowMacroblock *dst2 = local_macroblocks.get(j);
				if(j != number && 
					dst2->is_valid && 
					dst2->dist < dst1->dist &&
					angles_overlap(dst2->angle1, 
						dst2->angle2,
						dst1->angle1,
						dst1->angle2))
				{
					dst1->visible = 0;
					j = local_macroblocks.size();
				}
			}
		}
	}

// Blend all visible macroblocks
// Get distance metrics
	float total = 0;
	float min = 0;
	float max = 0;
	int first = 1;
	for(int i = 0; i < local_macroblocks.size(); i++)
	{
		OpticFlowMacroblock *dst = local_macroblocks.get(i);
		if(i != number && dst->is_valid && dst->visible)
		{
			total += dst->dist;
			if(first)
			{
				min = max = dst->dist;
				first = 0;
			}
			else
			{
				min = MIN(dst->dist, min);
				max = MAX(dst->dist, max);
			}
// printf("InterpolateVideo::blend_macroblock %d %d x=%d y=%d dist=%f\n", 
// __LINE__, 
// i,
// dst->x, 
// dst->y, 
// dst->dist);

		}
	}

// Invert distances to convert to weights
	total = 0;
	for(int i = 0; i < local_macroblocks.size(); i++)
	{
		OpticFlowMacroblock *dst = local_macroblocks.get(i);
		if(i != number && dst->is_valid && dst->visible)
		{
			dst->dist = max - dst->dist + min;
			total += dst->dist;
// printf("InterpolateVideo::blend_macroblock %d %d x=%d y=%d dist=%f\n", 
// __LINE__, 
// i,
// dst->x, 
// dst->y, 
// max - dst->dist + min);

		}
	}

// Add weighted vectors
	float dx = 0;
	float dy = 0;
	if(total > 0)
	{
		for(int i = 0; i < local_macroblocks.size(); i++)
		{
			OpticFlowMacroblock *dst = local_macroblocks.get(i);
			if(i != number && dst->is_valid && dst->visible)
			{
				dx += dst->dist * dst->dx / total;
				dy += dst->dist * dst->dy / total;
				src->dx = dx;
				src->dy = dy;
// printf("InterpolateVideo::blend_macroblock %d %d x=%d y=%d dist=%f\n", 
// __LINE__, 
// i,
// dst->x, 
// dst->y, 
// max - dst->dist + min);

			}
		}
	}

	local_macroblocks.remove_all_objects();
	
// printf("InterpolateVideo::blend_macroblock %d total=%f\n", 
// __LINE__, 
// total);
	struct timeval end_time;
	gettimeofday(&end_time, 0);
// 	printf("InterpolateVideo::blend_macroblock %d %d\n",
// 		__LINE__,
// 		end_time.tv_sec * 1000 + end_time.tv_usec / 1000 -
// 		start_time.tv_sec * 1000 - start_time.tv_usec / 1000);
}



void InterpolateVideo::optic_flow()
{

	create_macroblocks();
	int need_motion = 0;

// New engine
	if(!optic_flow_engine)
	{
		optic_flow_engine = new OpticFlow(this,
			PluginClient::get_project_smp() + 1, 
			PluginClient::get_project_smp() + 1);
		need_motion = 1;
	}
	else
// Reuse old vectors
	if(motion_number[0] == frame_number[0] &&
		motion_number[1] == frame_number[1] &&
		last_macroblock_size == config.macroblock_size &&
		last_search_radius == config.search_radius)
	{
		;
	}
	else
// Calculate new vectors
	{
		need_motion = 1;
	}

	if(need_motion)
	{
		optic_flow_engine->set_package_count(MIN(MAX_PACKAGES, total_macroblocks));
		optic_flow_engine->process_packages();

// Fill in failed macroblocks
		invalid_blocks.remove_all();
		for(int i = 0; i < macroblocks.size(); i++)
		{
			if(!macroblocks.get(i)->is_valid 
// debug
//				 && i >= 30 * x_macroblocks)
				)
			{
				invalid_blocks.append(i);
			}
		}

//printf("InterpolateVideo::optic_flow %d %d\n", __LINE__, invalid_blocks.size());
		if(invalid_blocks.size())
		{
			if(!blend_engine)
			{
				blend_engine = new BlendMacroblock(this,
					PluginClient::get_project_smp() + 1, 
					PluginClient::get_project_smp() + 1);
			}

			blend_engine->set_package_count(MIN(PluginClient::get_project_smp() + 1, 
				invalid_blocks.size()));
			blend_engine->process_packages();
		}
	}



// for(int i = 0; i < total_macroblocks; i++)
// {
// 	OpticFlowPackage *pkg = (OpticFlowPackage*)optic_flow_engine->get_package(
// 		i);
// 	if((i / x_macroblocks) % 2)
// 	{
// 		pkg->dx = 0;
// 		pkg->dy = 0;
// 	}
// 	else
// 	{
// 		pkg->dx = -32;
// 		pkg->dy = 0;
// 	}
// }


	if(!warp_engine)
	{
		warp_engine = new Warp(this,
			PluginClient::get_project_smp() + 1, 
			PluginClient::get_project_smp() + 1);
	}
	
	warp_engine->process_packages();

	motion_number[0] = frame_number[0];
	motion_number[1] = frame_number[1];
	last_macroblock_size = config.macroblock_size;
	last_search_radius = config.search_radius;


// Debug
//	get_output()->copy_from(frames[1]);


	draw_vectors(1);
}


#define AVERAGE(type, temp_type,components, max) \
{ \
	temp_type fraction0 = (temp_type)(lowest_fraction * max); \
	temp_type fraction1 = (temp_type)(max - fraction0); \
 \
	for(int i = 0; i < h; i++) \
	{ \
		type *prev_row0 = (type*)frames[0]->get_rows()[i]; \
		type *next_row0 = (type*)frames[1]->get_rows()[i]; \
		type *out_row = (type*)frame->get_rows()[i]; \
		for(int j = 0; j < w * components; j++) \
		{ \
			*out_row++ = (*prev_row0++ * fraction0 + *next_row0++ * fraction1) / max; \
		} \
	} \
}


void InterpolateVideo::average()
{
	VFrame *frame = get_output();
	int w = frame->get_w();
	int h = frame->get_h();

	switch(frame->get_color_model())
	{
		case BC_RGB_FLOAT:
			AVERAGE(float, float, 3, 1);
			break;
		case BC_RGB888:
		case BC_YUV888:
			AVERAGE(unsigned char, int, 3, 0xff);
			break;
		case BC_RGBA_FLOAT:
			AVERAGE(float, float, 4, 1);
			break;
		case BC_RGBA8888:
		case BC_YUVA8888:
			AVERAGE(unsigned char, int, 4, 0xff);
			break;
	}
}


int InterpolateVideo::process_buffer(VFrame *frame,
	int64_t start_position,
	double frame_rate)
{
	if(get_direction() == PLAY_REVERSE) start_position--;
	load_configuration();

	if(!frames[0])
	{
		for(int i = 0; i < 2; i++)
		{
			frames[i] = new VFrame(0,
				-1,
				frame->get_w(),
				frame->get_h(),
				frame->get_color_model(),
				-1);
		}
	}
//printf("InterpolateVideo::process_buffer 1 %lld %lld\n", range_start, range_end);

	if(range_start == range_end)
	{
		read_frame(frame, 
			0, 
			range_start, 
			active_input_rate,
			0);
		return 0;
	}
	else
	{

// Fill border frames
		fill_border(frame_rate, start_position);

// Fraction of lowest frame in output
		int64_t requested_range_start = (int64_t)((double)range_start * 
			frame_rate / 
			active_input_rate);
		int64_t requested_range_end = (int64_t)((double)range_end * 
			frame_rate / 
			active_input_rate);
		float highest_fraction = (float)(start_position - requested_range_start) /
			(requested_range_end - requested_range_start);

// Fraction of highest frame in output
		lowest_fraction = 1.0 - highest_fraction;

		CLAMP(highest_fraction, 0, 1);
		CLAMP(lowest_fraction, 0, 1);

// printf("InterpolateVideo::process_buffer %lld %lld %lld %f %f %lld %lld %f %f\n",
// range_start,
// range_end,
// requested_range_start,
// requested_range_end,
// start_position,
// config.input_rate,
// frame_rate,
// lowest_fraction,
// highest_fraction);

		if(start_position == (int64_t)(range_start * frame_rate / active_input_rate))
		{
//printf("InterpolateVideo::process_buffer %d\n", __LINE__);
			frame->copy_from(frames[0]);
			
			if(config.optic_flow)
			{
				draw_vectors(0);
			}
		}
		else
		if(config.optic_flow)
		{
//printf("InterpolateVideo::process_buffer %d\n", __LINE__);
			optic_flow();
		}
		else
		{
			average();
		}
	}
	return 0;
}



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

NEW_WINDOW_MACRO(InterpolateVideo, InterpolateVideoWindow)
const char* InterpolateVideo::plugin_title() { return N_("Interpolate Video"); }

int InterpolateVideo::load_configuration()
{
	KeyFrame *prev_keyframe, *next_keyframe;
	InterpolateVideoConfig old_config;
	old_config.copy_from(&config);

	next_keyframe = get_next_keyframe(get_source_position());
	prev_keyframe = get_prev_keyframe(get_source_position());
// Previous keyframe stays in config object.
	read_data(prev_keyframe);


	int64_t prev_position = edl_to_local(prev_keyframe->position);
	int64_t next_position = edl_to_local(next_keyframe->position);
	if(prev_position == 0 && next_position == 0)
	{
		next_position = prev_position = get_source_start();
	}
// printf("InterpolateVideo::load_configuration 1 %lld %lld %lld %lld\n",
// prev_keyframe->position,
// next_keyframe->position,
// prev_position,
// next_position);

// Get range to average in requested rate
	range_start = prev_position;
	range_end = next_position;


// Use keyframes to determine range
	if(config.use_keyframes)
	{
		active_input_rate = get_framerate();
// Between keyframe and edge of range or no keyframes
		if(range_start == range_end)
		{
// Between first keyframe and start of effect
			if(get_source_position() >= get_source_start() &&
				get_source_position() < range_start)
			{
				range_start = get_source_start();
			}
			else
// Between last keyframe and end of effect
			if(get_source_position() >= range_start &&
				get_source_position() < get_source_start() + get_total_len())
			{
// Last frame should be inclusive of current effect
				range_end = get_source_start() + get_total_len() - 1;
			}
			else
			{
// Should never get here
				;
			}
		}


// Make requested rate equal to input rate for this mode.

// Convert requested rate to input rate
// printf("InterpolateVideo::load_configuration 2 %lld %lld %f %f\n", 
// range_start, 
// range_end,
// get_framerate(),
// config.input_rate);
//		range_start = (int64_t)((double)range_start / get_framerate() * active_input_rate + 0.5);
//		range_end = (int64_t)((double)range_end / get_framerate() * active_input_rate + 0.5);
	}
	else
// Use frame rate
	{
		active_input_rate = config.input_rate;
// Convert to input frame rate
		range_start = (int64_t)(get_source_position() / 
			get_framerate() *
			active_input_rate);
		range_end = (int64_t)(get_source_position() / 
			get_framerate() *
			active_input_rate) + 1;
	}

// printf("InterpolateVideo::load_configuration 1 %lld %lld %lld %lld %lld %lld\n",
// prev_keyframe->position,
// next_keyframe->position,
// prev_position,
// next_position,
// range_start,
// range_end);


	return !config.equivalent(&old_config);
}


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

// cause data to be stored directly in text
	output.set_shared_string(keyframe->get_data(), MESSAGESIZE);
	output.tag.set_title("INTERPOLATEVIDEO");
	output.tag.set_property("INPUT_RATE", config.input_rate);
	output.tag.set_property("USE_KEYFRAMES", config.use_keyframes);
	output.tag.set_property("OPTIC_FLOW", config.optic_flow);
	output.tag.set_property("DRAW_VECTORS", config.draw_vectors);
	output.tag.set_property("SEARCH_RADIUS", config.search_radius);
	output.tag.set_property("MACROBLOCK_SIZE", config.macroblock_size);
	output.append_tag();
	output.terminate_string();
}

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

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

	int result = 0;

	while(!input.read_tag())
	{
		if(input.tag.title_is("INTERPOLATEVIDEO"))
		{
			config.input_rate = input.tag.get_property("INPUT_RATE", config.input_rate);
			config.input_rate = Units::fix_framerate(config.input_rate);
			config.use_keyframes = input.tag.get_property("USE_KEYFRAMES", config.use_keyframes);
			config.optic_flow = input.tag.get_property("OPTIC_FLOW", config.optic_flow);
			config.draw_vectors = input.tag.get_property("DRAW_VECTORS", config.draw_vectors);
			config.search_radius = input.tag.get_property("SEARCH_RADIUS", config.search_radius);
			config.macroblock_size = input.tag.get_property("MACROBLOCK_SIZE", config.macroblock_size);
		}
	}
}

void InterpolateVideo::update_gui()
{
	if(thread)
	{
		if(load_configuration())
		{
			thread->window->lock_window("InterpolateVideo::update_gui");
			((InterpolateVideoWindow*)thread->window)->rate->update((float)config.input_rate);
			((InterpolateVideoWindow*)thread->window)->keyframes->update(config.use_keyframes);
			((InterpolateVideoWindow*)thread->window)->flow->update(config.optic_flow);
			((InterpolateVideoWindow*)thread->window)->vectors->update(config.draw_vectors);
			((InterpolateVideoWindow*)thread->window)->radius->update(config.search_radius);
			((InterpolateVideoWindow*)thread->window)->size->update(config.macroblock_size);
			((InterpolateVideoWindow*)thread->window)->update_enabled();
			thread->window->unlock_window();
		}
	}
}


