
/*
 * CINELERRA
 * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
 * Copyright (C) 2003-2016 Cinelerra CV contributors
 *
 * 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 "clip.h"
#include "filexml.h"
#include "maskauto.h"
#include "maskautos.h"

#include <stdlib.h>
#include <string.h>


MaskPoint::MaskPoint()
{
	x = 0;
	y = 0;
	control_x1 = 0;
	control_y1 = 0;
	control_x2 = 0;
	control_y2 = 0;
}

void MaskPoint::copy_from(MaskPoint &ptr)
{
	this->x = ptr.x;
	this->y = ptr.y;
	this->control_x1 = ptr.control_x1;
	this->control_y1 = ptr.control_y1;
	this->control_x2 = ptr.control_x2;
	this->control_y2 = ptr.control_y2;
}

MaskPoint& MaskPoint::operator=(MaskPoint& ptr)
{
	copy_from(ptr);
	return *this;
}

int MaskPoint::operator==(MaskPoint& ptr)
{
	return EQUIV(x, ptr.x) &&
		EQUIV(y, ptr.y) &&
		EQUIV(control_x1, ptr.control_x1) &&
		EQUIV(control_y1, ptr.control_y1) &&
		EQUIV(control_x2, ptr.control_x2) &&
		EQUIV(control_y2, ptr.control_y2);
}

SubMask::SubMask(MaskAuto *keyframe, int no)
{
	this->keyframe = keyframe;
	memset(name, 0, sizeof(name));
	sprintf(name, "%d", no);
	this->fader = 100;
	this->feather = 0;
}

SubMask::~SubMask()
{
	points.remove_all_objects();
}

int SubMask::equivalent(SubMask& ptr)
{
	if( fader != ptr.fader ) return 0;
	if( feather != ptr.feather ) return 0;
	int n = points.size();
	if( n != ptr.points.size() ) return 0;
	for( int i=0; i<n; ++i ) {
		if(!(*points.get(i) == *ptr.points.get(i)))
			return 0;
	}
	return 1;
}

int SubMask::operator==(SubMask& ptr)
{
	return equivalent(ptr);
}

void SubMask::copy_from(SubMask& ptr, int do_name)
{
	if( do_name ) {
		memset(name, 0, sizeof(name));
		strncpy(name, ptr.name, sizeof(name)-1);
	}
	fader = ptr.fader;
	feather = ptr.feather;
	points.remove_all_objects();
//printf("SubMask::copy_from 1 %p %d\n", this, ptr.points.total);
	for(int i = 0; i < ptr.points.total; i++)
	{
		MaskPoint *point = new MaskPoint;
		*point = *ptr.points.values[i];
		points.append(point);
	}
}

void SubMask::load(FileXML *file)
{
	points.remove_all_objects();

	int result = 0;
	while( !(result = file->read_tag()) ) {
		if( file->tag.title_is("/MASK") ) break;
		if( file->tag.title_is("POINT") ) {
			XMLBuffer data;
			file->read_text_until("/POINT", &data);
			MaskPoint *point = new MaskPoint;
			char *cp = data.cstr();
			if( cp ) point->x = strtof(cp, &cp);
			if( cp && *cp==',' ) point->y = strtof(cp+1, &cp);
			if( cp && *cp==',' ) point->control_x1 = strtof(cp+1, &cp);
			if( cp && *cp==',' ) point->control_y1 = strtof(cp+1, &cp);
			if( cp && *cp==',' ) point->control_x2 = strtof(cp+1, &cp);
			if( cp && *cp==',' ) point->control_y2 = strtof(cp+1, &cp);
			points.append(point);
		}
	}
}

void SubMask::copy(FileXML *file)
{
	if(points.total)
	{
		file->tag.set_title("MASK");
		file->tag.set_property("NUMBER",
			!keyframe ? -1 : keyframe->masks.number_of(this));
		file->tag.set_property("NAME", name);
		file->tag.set_property("FADER", fader);
		file->tag.set_property("FEATHER", feather);
		file->append_tag();
		file->append_newline();

		for(int i = 0; i < points.total; i++)
		{
			file->append_newline();
			file->tag.set_title("POINT");
			file->append_tag();
			char string[BCTEXTLEN];
//printf("SubMask::copy 1 %p %d %p\n", this, i, points.values[i]);
			sprintf(string, "%.7g, %.7g, %.7g, %.7g, %.7g, %.7g",
				points.values[i]->x,
				points.values[i]->y,
				points.values[i]->control_x1,
				points.values[i]->control_y1,
				points.values[i]->control_x2,
				points.values[i]->control_y2);
//printf("SubMask::copy 2\n");
			file->append_text(string);
			file->tag.set_title("/POINT");
			file->append_tag();
		}
		file->append_newline();

		file->tag.set_title("/MASK");
		file->append_tag();
		file->append_newline();
	}
}

void SubMask::dump(FILE *fp)
{
	for( int i=0; i<points.size(); ++i ) {
		fprintf(fp, "	mask: %d, name: %s, fader: %f, feather %f\n", i,
			name, fader, feather);
		fprintf(fp, "	  point=%d x=%.2f y=%.2f in_x=%.2f in_y=%.2f out_x=%.2f out_y=%.2f\n",
			i, points.values[i]->x, points.values[i]->y,
			points.values[i]->control_x1, points.values[i]->control_y1,
			points.values[i]->control_x2, points.values[i]->control_y2);
	}
}


MaskAuto::MaskAuto(EDL *edl, MaskAutos *autos)
 : Auto(edl, autos)
{
	apply_before_plugins = 0;
	disable_opengl_masking = 0;

// We define a fixed number of submasks so that interpolation for each
// submask matches.

	for(int i = 0; i < SUBMASKS; i++)
		masks.append(new SubMask(this, i));
}

MaskAuto::~MaskAuto()
{
	masks.remove_all_objects();
}

int MaskAuto::operator==(Auto &that)
{
	return identical((MaskAuto*)&that);
}



int MaskAuto::operator==(MaskAuto &that)
{
	return identical((MaskAuto*)&that);
}


int MaskAuto::identical(MaskAuto *src)
{
	if( masks.size() != src->masks.size() ||
	    apply_before_plugins != src->apply_before_plugins ||
	    disable_opengl_masking != src->disable_opengl_masking ) return 0;

	for( int i=0; i<masks.size(); ++i ) {
		if(!(*masks.values[i] == *src->masks.values[i])) return 0;
	}
	return 1;
}

void MaskAuto::update_parameter(MaskAuto *ref, MaskAuto *src)
{
	if( src->apply_before_plugins != ref->apply_before_plugins )
		this->apply_before_plugins = src->apply_before_plugins;
	if( src->disable_opengl_masking != ref->disable_opengl_masking )
		this->disable_opengl_masking = src->disable_opengl_masking;

	for( int i=0; i<masks.size(); ++i ) {
		if( !src->get_submask(i)->equivalent(*ref->get_submask(i)) )
			this->get_submask(i)->copy_from(*src->get_submask(i));
	}
}

void MaskAuto::copy_from(Auto *src)
{
	copy_from((MaskAuto*)src);
}

void MaskAuto::copy_from(MaskAuto *src)
{
	Auto::copy_from(src);
	copy_data(src);
}

void MaskAuto::copy_data(MaskAuto *src)
{
	apply_before_plugins = src->apply_before_plugins;
	disable_opengl_masking = src->disable_opengl_masking;

	masks.remove_all_objects();
	for(int i = 0; i < src->masks.size(); i++)
	{
		masks.append(new SubMask(this, i));
		masks.values[i]->copy_from(*src->masks.values[i]);
	}
}

int MaskAuto::interpolate_from(Auto *a1, Auto *a2, int64_t position, Auto *templ) {
	if(!a1) a1 = previous;
	if(!a2) a2 = next;
	MaskAuto  *mask_auto1 = (MaskAuto *)a1;
	MaskAuto  *mask_auto2 = (MaskAuto *)a2;

	if (!mask_auto2 || !mask_auto1 || mask_auto2->masks.total == 0)
	// can't interpolate, fall back to copying (using template if possible)
	{
		return Auto::interpolate_from(a1, a2, position, templ);
	}
	this->apply_before_plugins = mask_auto1->apply_before_plugins;
	this->disable_opengl_masking = mask_auto1->disable_opengl_masking;
	this->position = position;
	masks.remove_all_objects();

	for( int i=0; i<mask_auto1->masks.total; ++i ) {
		SubMask *new_submask = new SubMask(this, i);
		masks.append(new_submask);
		SubMask *mask1 = mask_auto1->masks.values[i];
		SubMask *mask2 = mask_auto2->masks.values[i];
		double len = mask_auto2->position - mask_auto1->position;
		double weight = !len ? 0 : (position - mask_auto1->position) / len;
		new_submask->fader = mask1->fader*(1-weight) + mask2->fader*weight + 0.5;
		new_submask->feather = mask1->feather*(1-weight) + mask2->feather*weight + 0.5;

		// just in case, should never happen
		int total_points = MIN(mask1->points.total, mask2->points.total);
		for( int j=0; j<total_points; ++j ) {
			MaskPoint *point = new MaskPoint;
			MaskAutos::avg_points(point,
				mask1->points.values[j], mask2->points.values[j],
				position, mask_auto1->position, mask_auto2->position);
			new_submask->points.append(point);
		}
	}
	return 1;


}


SubMask* MaskAuto::get_submask(int number)
{
	CLAMP(number, 0, masks.size() - 1);
	return masks.values[number];
}

void MaskAuto::get_points(MaskPoints *points,
	int submask)
{
	points->remove_all_objects();
	SubMask *submask_ptr = get_submask(submask);
	for(int i = 0; i < submask_ptr->points.size(); i++)
	{
		MaskPoint *point = new MaskPoint;
		point->copy_from(*submask_ptr->points.get(i));
		points->append(point);
	}
}

void MaskAuto::set_points(MaskPoints *points,
	int submask)
{
	SubMask *submask_ptr = get_submask(submask);
	submask_ptr->points.remove_all_objects();
	for(int i = 0; i < points->size(); i++)
	{
		MaskPoint *point = new MaskPoint;
		point->copy_from(*points->get(i));
		submask_ptr->points.append(point);
	}
}


void MaskAuto::load(FileXML *file)
{
// legacy, moved to SubMask
	int old_mode = file->tag.get_property("MODE", -1);
	int old_value = file->tag.get_property("VALUE", 100);
	float old_feather = file->tag.get_property("FEATHER", 0);
	apply_before_plugins = file->tag.get_property("APPLY_BEFORE_PLUGINS", apply_before_plugins);
	disable_opengl_masking = file->tag.get_property("DISABLE_OPENGL_MASKING", disable_opengl_masking);
	for( int i=0; i<masks.size(); ++i ) {
		delete masks.values[i];
		masks.values[i] = new SubMask(this, i);
	}

	int result = 0;
	while( !(result = file->read_tag()) ) {
		if( file->tag.title_is("/AUTO") ) break;
		if( file->tag.title_is("MASK") ) {
			int no = file->tag.get_property("NUMBER", 0);
			char name[BCTEXTLEN];  name[0] = 0;
			file->tag.get_property("NAME", name);
			if( !name[0] ) sprintf(name, "%d", no);
			SubMask *mask = masks.values[no];
			memset(mask->name, 0, sizeof(mask->name));
			strncpy(mask->name, name, sizeof(mask->name));
			mask->feather = file->tag.get_property("FEATHER", old_feather);
			mask->fader = file->tag.get_property("FADER", old_value);
			if( old_mode == MASK_MULTIPLY_ALPHA )
				mask->fader = -mask->fader;
			mask->load(file);
		}
	}
}

void MaskAuto::copy(int64_t start, int64_t end, FileXML *file, int default_auto)
{
	file->tag.set_title("AUTO");
	file->tag.set_property("APPLY_BEFORE_PLUGINS", apply_before_plugins);
	file->tag.set_property("DISABLE_OPENGL_MASKING", disable_opengl_masking);
	file->tag.set_property("POSITION", default_auto ? 0 : position - start);
	file->append_tag();
	file->append_newline();

	for( int i=0; i<masks.size(); ++i )
		masks.values[i]->copy(file);

	file->tag.set_title("/AUTO");
	file->append_tag();
	file->append_newline();
}

void MaskAuto::dump(FILE *fp)
{
	fprintf(fp,"mask_auto:  apply_before_plugins %d, disable_opengl_masking %d\n",
		apply_before_plugins, disable_opengl_masking);
	for( int i=0; i<masks.size(); ++i ) {
		fprintf(fp,"    submask %d:\n", i);
		masks.values[i]->dump(fp);
	}
}

void MaskAuto::translate_submasks(float translate_x, float translate_y)
{
	for( int i=0; i<masks.size(); ++i ) {
		SubMask *mask = get_submask(i);
		for( int j=0; j<mask->points.total; ++j ) {
			mask->points.values[j]->x += translate_x;
			mask->points.values[j]->y += translate_y;
		}
	}
}

void MaskAuto::scale_submasks(int orig_scale, int new_scale)
{
	for( int i=0; i<masks.size(); ++i ) {
		SubMask *mask = get_submask(i);
		for( int j=0; j<mask->points.total; ++j ) {
			float orig_x = mask->points.values[j]->x * orig_scale;
			float orig_y = mask->points.values[j]->y * orig_scale;
			mask->points.values[j]->x = orig_x / new_scale;
			mask->points.values[j]->y = orig_y / new_scale;

			orig_x = mask->points.values[j]->control_x1 * orig_scale;
			orig_y = mask->points.values[j]->control_y1 * orig_scale;
			mask->points.values[j]->control_x1 = orig_x / new_scale;
			mask->points.values[j]->control_y1 = orig_y / new_scale;

			orig_x = mask->points.values[j]->control_x2 * orig_scale;
			orig_y = mask->points.values[j]->control_y2 * orig_scale;
			mask->points.values[j]->control_x2 = orig_x / new_scale;
			mask->points.values[j]->control_y2 = orig_y / new_scale;
		}
	}
}

int MaskAuto::has_active_mask()
{
	int total_points = 0;
	float min_fader = 100;
	for( int i=0; i<masks.size(); ++i ) {
		SubMask *mask = get_submask(i);
		int submask_points = mask->points.size();
		if( submask_points > 1 ) total_points += submask_points;
		int fader = mask->fader;
		if( fader < min_fader ) min_fader = fader;
	}
	return min_fader >= 0 && total_points < 2 ? 0 : 1;
}

static inline double line_dist(float cx,float cy, float tx,float ty)
{
	double dx = tx-cx, dy = ty-cy;
	return sqrt(dx*dx + dy*dy);
}

void MaskEdge::load(MaskPoints &points, float ofs)
{
	remove_all();
	int first_point = 1;
// Need to tabulate every vertex in persistent memory because
// gluTessVertex doesn't copy them.
	for( int i=0; i<points.total; ++i ) {
		MaskPoint *point1 = points.values[i];
		MaskPoint *point2 = (i >= points.total-1) ?
			points.values[0] : points.values[i+1];

		int segments = 0;
		if( !point1->control_x2 && !point1->control_y2 &&
		    !point2->control_x1 && !point2->control_y1 )
			segments = 1;

		float x0 = point1->x, y0 = point1->y;
		float x1 = point1->x + point1->control_x2;
		float y1 = point1->y + point1->control_y2;
		float x2 = point2->x + point2->control_x1;
		float y2 = point2->y + point2->control_y1;
		float x3 = point2->x, y3 = point2->y;

		// forward differencing bezier curves implementation taken from GPL code at
		// http://cvs.sourceforge.net/viewcvs.py/guliverkli/guliverkli/src/subtitles/Rasterizer.cpp?rev=1.3

		float cx3, cx2, cx1, cx0;
		float cy3, cy2, cy1, cy0;

		// [-1 +3 -3 +1]
		// [+3 -6 +3  0]
		// [-3 +3  0  0]
		// [+1  0  0  0]

		cx3 = -  x0 + 3*x1 - 3*x2 + x3;
		cx2 =  3*x0 - 6*x1 + 3*x2;
		cx1 = -3*x0 + 3*x1;
		cx0 =    x0;

		cy3 = -  y0 + 3*y1 - 3*y2 + y3;
		cy2 =  3*y0 - 6*y1 + 3*y2;
		cy1 = -3*y0 + 3*y1;
		cy0 =    y0;

		// This equation is from Graphics Gems I.
		//
		// The idea is that since we're approximating a cubic curve with lines,
		// any error we incur is due to the curvature of the line, which we can
		// estimate by calculating the maximum acceleration of the curve.  For
		// a cubic, the acceleration (second derivative) is a line, meaning that
		// the absolute maximum acceleration must occur at either the beginning
		// (|c2|) or the end (|c2+c3|).  Our bounds here are a little more
		// conservative than that, but that's okay.
		if( !segments ) {
			float maxaccel1 = fabs(2*cy2) + fabs(6*cy3);
			float maxaccel2 = fabs(2*cx2) + fabs(6*cx3);
			float maxaccel = maxaccel1 > maxaccel2 ? maxaccel1 : maxaccel2;
			segments =  maxaccel > 1.0 ? sqrt(maxaccel) :
				1 + line_dist(point1->x,point1->y, point2->x,point2->y);
		}

		for( int j=0; j<=segments; ++j ) {
			float t = (float)j / segments;
			float x = cx0 + t*(cx1 + t*(cx2 + t*cx3));
			float y = cy0 + t*(cy1 + t*(cy2 + t*cy3));

			if( j > 0 || first_point ) {
				append(x, y-ofs);
				first_point = 0;
			}
		}
	}
}

