
/*
 * CINELERRA
 * Copyright (C) 2008 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 "clip.h"
#include "bccmodels.h"
#include "filexml.h"
#include "aging.h"
#include "agingwindow.h"
#include "effecttv.h"
#include "language.h"

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

REGISTER_PLUGIN(AgingMain)

int AgingMain::dx[] = { 1, 1, 0, -1, -1, -1,  0, 1 };
int AgingMain::dy[] = { 0, -1, -1, -1, 0, 1, 1, 1 };

AgingMain::AgingMain(PluginServer *server)
 : PluginVClient(server)
{
	aging_server = 0;
	pits_count = 0;
	dust_count = 0;
	memset(scratches, 0, sizeof(scratches));
}

AgingMain::~AgingMain()
{

	delete aging_server;
}

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

void AgingConfig::reset()
{
	area_scale = 10;
	aging_mode = 0;
	dust_interval = 0;
	pits_interval = 0;
	scratch_lines = 7;
	colorage = 1;
	scratch = 1;
	pits = 1;
	dust = 1;
}

AgingConfig::AgingConfig()
{
	reset();
}

AgingConfig::~AgingConfig()
{
}

int AgingConfig::equivalent(AgingConfig &that)
{
	return area_scale == that.area_scale &&
		aging_mode == that.aging_mode &&
		dust_interval == that.dust_interval &&
		pits_interval == that.pits_interval &&
		scratch_lines == that.scratch_lines &&
		colorage == that.colorage &&
		scratch == that.scratch &&
		pits == that.pits &&
		dust == that.dust;
}

void AgingConfig::copy_from(AgingConfig &that)
{
	area_scale = that.area_scale;
	aging_mode = that.aging_mode;
	dust_interval = that.dust_interval;
	pits_interval = that.pits_interval;
	scratch_lines = that.scratch_lines;
	colorage = that.colorage;
	scratch = that.scratch;
	pits = that.pits;
	dust = that.dust;
}

void AgingConfig::interpolate(AgingConfig &prev, AgingConfig &next,
		int64_t prev_frame, int64_t next_frame, int64_t current_frame)
{
	copy_from(prev);
}

LOAD_CONFIGURATION_MACRO(AgingMain, AgingConfig)

void AgingMain::save_data(KeyFrame *keyframe)
{
	FileXML output;
// cause data to be stored directly in text
	output.set_shared_output(keyframe->xbuf);
// Store data
	output.tag.set_title("AGING");
	output.tag.set_property("AREA_SCALE", config.area_scale);
	output.tag.set_property("AGING_MODE", config.aging_mode);
	output.tag.set_property("DUST_INTERVAL", config.dust_interval);
	output.tag.set_property("PITS_INTERVAL", config.pits_interval);
	output.tag.set_property("SCRATCH_LINES", config.scratch_lines);
	output.tag.set_property("COLORAGE", config.colorage);
	output.tag.set_property("SCRATCH", config.scratch);
	output.tag.set_property("PITS", config.pits);
	output.tag.set_property("DUST", config.dust);
	output.append_tag();
	output.tag.set_title("/AGING");
	output.append_tag();
	output.append_newline();
	output.terminate_string();
}

void AgingMain::read_data(KeyFrame *keyframe)
{
	FileXML input;
	input.set_shared_input(keyframe->xbuf);

	int result = 0;

	while( !(result = input.read_tag()) ) {
		if( input.tag.title_is("AGING") ) {
			config.area_scale    = input.tag.get_property("AREA_SCALE", config.area_scale);
			config.aging_mode    = input.tag.get_property("AGING_MODE", config.aging_mode);
			config.dust_interval = input.tag.get_property("DUST_INTERVAL", config.dust_interval);
			config.pits_interval = input.tag.get_property("PITS_INTERVAL", config.pits_interval);
			config.scratch_lines = input.tag.get_property("SCRATCH_LINES", config.scratch_lines);
			config.colorage      = input.tag.get_property("COLORAGE", config.colorage);
			config.scratch       = input.tag.get_property("SCRATCH", config.scratch);
			config.pits          = input.tag.get_property("PITS", config.pits);
			config.dust          = input.tag.get_property("DUST", config.dust);
		}
	}
}

NEW_WINDOW_MACRO(AgingMain, AgingWindow)


int AgingMain::process_realtime(VFrame *input_ptr, VFrame *output_ptr)
{
//printf("AgingMain::process_realtime 1\n");
	load_configuration();
//printf("AgingMain::process_realtime 1\n");
	this->input_ptr = input_ptr;
	this->output_ptr = output_ptr;
	int cpus = PluginClient::smp + 1;
	if( cpus > 16 ) cpus = 16;
	if( !aging_server )
		aging_server = new AgingServer(this, cpus, cpus);
	aging_server->process_packages();
//printf("AgingMain::process_realtime 2\n");

	return 0;
}


AgingServer::AgingServer(AgingMain *plugin, int total_clients, int total_packages)
 : LoadServer(1, 1 /* total_clients, total_packages */)
{
	this->plugin = plugin;
}


LoadClient* AgingServer::new_client()
{
	return new AgingClient(this);
}

LoadPackage* AgingServer::new_package()
{
	return new AgingPackage;
}

void AgingServer::init_packages()
{
	for( int i = 0; i < get_total_packages(); i++ ) {
		AgingPackage *package = (AgingPackage*)get_package(i);
		package->row1 = plugin->input_ptr->get_h() * i / get_total_packages();
		package->row2 = plugin->input_ptr->get_h() * (i + 1) / get_total_packages();
	}
}


AgingClient::AgingClient(AgingServer *server)
 : LoadClient(server)
{
	this->plugin = server->plugin;
}

#define COLORAGE(type, components) \
{ \
	int a, b; \
	int i, j, k; \
 \
	for( i = 0; i < h; i++ ) { \
		for( j = 0; j < w; j++ ) { \
			for( k = 0; k < 3; k++ ) { \
				if( sizeof(type) == 4 ) { \
					a = (int)(((type**)input_rows)[i][j * components + k] * 0xffff); \
					CLAMP(a, 0, 0xffff); \
				} \
				else \
					a = (int)((type**)input_rows)[i][j * components + k]; \
				if( sizeof(type) == 4 ) { \
					b = (a & 0xffff) >> 2; \
					((type**)output_rows)[i][j * components + k] = \
						(type)(a - b + 0x1800 + (EffectTV::fastrand() & 0x1000)) / 0xffff; \
				} \
				else if( sizeof(type) == 2 ) { \
					b = (a & 0xffff) >> 2; \
					((type**)output_rows)[i][j * components + k] = \
						(type)(a - b + 0x1800 + (EffectTV::fastrand() & 0x1000)); \
				} \
				else { \
					b = (a & 0xff) >> 2; \
					((type**)output_rows)[i][j * components + k] =  \
						(type)(a - b + 0x18 + ((EffectTV::fastrand() >> 8) & 0x10)); \
				} \
			} \
		} \
	} \
}

void AgingClient::coloraging(unsigned char **output_rows, unsigned char **input_rows,
		int color_model, int w, int h)
{
	switch( color_model ) {
	case BC_RGB888:
	case BC_YUV888:
		COLORAGE(uint8_t, 3);
		break;

	case BC_RGB_FLOAT:
		COLORAGE(float, 3);
		break;

	case BC_RGBA_FLOAT:
		COLORAGE(float, 4);
		break;

	case BC_RGBA8888:
	case BC_YUVA8888:
		COLORAGE(uint8_t, 4);
		break;

	case BC_RGB161616:
	case BC_YUV161616:
		COLORAGE(uint16_t, 3);
		break;

	case BC_RGBA16161616:
	case BC_YUVA16161616:
		COLORAGE(uint16_t, 4);
		break;
	}
}

#define SCRATCHES(type, components, chroma) \
{ \
	int i, j, y, y1, y2; \
	type *p; \
	int a, b; \
	int w_256 = w * 256; \
 \
	for( i = 0; i < plugin->config.scratch_lines; i++ ) { \
		if( plugin->scratches[i].life )  { \
			plugin->scratches[i].x = plugin->scratches[i].x + plugin->scratches[i].dx; \
			if( plugin->scratches[i].x < 0 || plugin->scratches[i].x >= w_256 ) { \
				plugin->scratches[i].life = 0; \
				break; \
			} \
			p = (type*)output_rows[0] + (plugin->scratches[i].x >> 8) * components; \
			if( plugin->scratches[i].init ) { \
				y1 = plugin->scratches[i].init; \
				plugin->scratches[i].init = 0; \
			} \
			else { \
				y1 = 0; \
			} \
\
			plugin->scratches[i].life--; \
			if( plugin->scratches[i].life ) { \
				y2 = h; \
			}  \
			else { \
				y2 = EffectTV::fastrand() % h; \
			} \
 \
			for( y = y1; y < y2; y++ ) { \
				for( j = 0; j < (chroma ? 1 : 3); j++ ) { \
					if( sizeof(type) == 4 ) { \
						int temp = (int)(p[j] * 0xffff); \
						CLAMP(temp, 0, 0xffff); \
						a = temp & 0xfeff; \
						a += 0x2000; \
						b = a & 0x10000; \
						p[j] = (type)(a | (b - (b >> 8))) / 0xffff; \
					} \
					else if( sizeof(type) == 2 ) { \
						int temp = (int)p[j]; \
						a = temp & 0xfeff; \
						a += 0x2000; \
						b = a & 0x10000; \
						p[j] = (type)(a | (b - (b >> 8))); \
					} \
					else { \
						int temp = (int)p[j]; \
						a = temp & 0xfe; \
						a += 0x20; \
						b = a & 0x100; \
						p[j] = (type)(a | (b - (b >> 8))); \
					} \
				} \
				if( chroma ) { \
					p[1] = chroma; \
					p[2] = chroma; \
				} \
				p += w * components; \
			} \
		}  \
		else  { \
			if( (EffectTV::fastrand() & 0xf0000000) == 0 ) { \
				plugin->scratches[i].life = 2 + (EffectTV::fastrand() >> 27); \
				plugin->scratches[i].x = EffectTV::fastrand() % (w_256); \
				plugin->scratches[i].dx = ((int)EffectTV::fastrand()) >> 23; \
				plugin->scratches[i].init = (EffectTV::fastrand() % (h - 1)) + 1; \
			} \
		} \
	} \
}


void AgingClient::scratching(unsigned char **output_rows,
		int color_model, int w, int h)
{
	switch( color_model ) {
	case BC_RGB888:
		SCRATCHES(uint8_t, 3, 0);
		break;

	case BC_RGB_FLOAT:
		SCRATCHES(float, 3, 0);
		break;

	case BC_YUV888:
		SCRATCHES(uint8_t, 3, 0x80);
		break;

	case BC_RGBA_FLOAT:
		SCRATCHES(float, 4, 0);
		break;

	case BC_RGBA8888:
		SCRATCHES(uint8_t, 4, 0);
		break;

	case BC_YUVA8888:
		SCRATCHES(uint8_t, 4, 0x80);
		break;

	case BC_RGB161616:
		SCRATCHES(uint16_t, 3, 0);
		break;

	case BC_YUV161616:
		SCRATCHES(uint16_t, 3, 0x8000);
		break;

	case BC_RGBA16161616:
		SCRATCHES(uint16_t, 4, 0);
		break;

	case BC_YUVA16161616:
		SCRATCHES(uint16_t, 4, 0x8000);
		break;
	}
}


#define PITS(type, components, luma, chroma) \
{ \
	int i, j, k; \
	int pnum, size, pnumscale; \
	int x, y; \
	pnumscale = plugin->config.area_scale * 2; \
	pnum = EffectTV::fastrand() % (plugin->config.pits_interval+1); \
	if( plugin->pits_count ) { \
		pnum += pnumscale; \
		--plugin->pits_count; \
	}  \
	else { \
		if( (EffectTV::fastrand() & 0xf8000000) == 0 ) { \
			plugin->pits_count = (EffectTV::fastrand() >> 28) + 20; \
		} \
	} \
	for( i = 0; i < pnum; i++ ) { \
		x = EffectTV::fastrand() % (w - 1); \
		y = EffectTV::fastrand() % (h - 1); \
		size = EffectTV::fastrand() >> 28; \
		for( j = 0; j < size; j++ ) { \
			x = x + EffectTV::fastrand() % 3 - 1; \
			y = y + EffectTV::fastrand() % 3 - 1; \
			CLAMP(x, 0, w - 1); \
			CLAMP(y, 0, h - 1); \
			for( k = 0; k < (chroma ? 1 : 3); k++ ) { \
				((type**)output_rows)[y][x * components + k] = luma; \
			} \
			if( chroma ) { \
				((type**)output_rows)[y][x * components + 1] = chroma; \
				((type**)output_rows)[y][x * components + 2] = chroma; \
			} \
		} \
	} \
}


void AgingClient::pits(unsigned char **output_rows,
		int color_model, int w, int h)
{
	switch( color_model ) {
		case BC_RGB888:
			PITS(uint8_t, 3, 0xc0, 0);
			break;
		case BC_RGB_FLOAT:
			PITS(float, 3, (float)0xc0 / 0xff, 0);
			break;
		case BC_YUV888:
			PITS(uint8_t, 3, 0xc0, 0x80);
			break;

		case BC_RGBA_FLOAT:
			PITS(float, 4, (float)0xc0 / 0xff, 0);
			break;
		case BC_RGBA8888:
			PITS(uint8_t, 4, 0xc0, 0);
			break;
		case BC_YUVA8888:
			PITS(uint8_t, 4, 0xc0, 0x80);
			break;

		case BC_RGB161616:
			PITS(uint16_t, 3, 0xc000, 0);
			break;
		case BC_YUV161616:
			PITS(uint16_t, 3, 0xc000, 0x8000);
			break;

		case BC_RGBA16161616:
			PITS(uint16_t, 4, 0xc000, 0);
			break;
		case BC_YUVA16161616:
			PITS(uint16_t, 4, 0xc000, 0x8000);
			break;
	}
}


#define DUSTS(type, components, luma, chroma) \
{ \
	int i, j, k; \
	int dnum; \
	int d, len; \
	int x, y; \
	if( plugin->dust_count == 0 ) { \
		if( (EffectTV::fastrand() & 0xf0000000) == 0 ) { \
			plugin->dust_count = EffectTV::fastrand() >> 29; \
		} \
		return; \
	} \
	dnum = plugin->config.area_scale * 4 + EffectTV::fastrand() % (plugin->config.dust_interval+1); \
	for( i = 0; i < dnum; i++ ) { \
		x = EffectTV::fastrand() % w; \
		y = EffectTV::fastrand() % h; \
		d = EffectTV::fastrand() >> 29; \
		len = EffectTV::fastrand() % plugin->config.area_scale + 5; \
		for( j = 0; j < len; j++ ) { \
			CLAMP(x, 0, w - 1); \
			CLAMP(y, 0, h - 1); \
			for( k = 0; k < (chroma ? 1 : 3); k++ ) { \
				((type**)output_rows)[y][x * components + k] = luma; \
			} \
			if( chroma ) { \
				((type**)output_rows)[y][x * components + 1] = chroma; \
				((type**)output_rows)[y][x * components + 2] = chroma; \
			} \
			y += AgingMain::dy[d]; \
			x += AgingMain::dx[d]; \
			if( x < 0 || x >= w ) break; \
			if( y < 0 || y >= h ) break; \
			d = (d + EffectTV::fastrand() % 3 - 1) & 7; \
		} \
	} \
	--plugin->dust_count; \
}


void AgingClient::dusts(unsigned char **output_rows,
		int color_model, int w, int h)
{
	switch( color_model ) {
	case BC_RGB888:
		DUSTS(uint8_t, 3, 0x10, 0);
		break;

	case BC_RGB_FLOAT:
		DUSTS(float, 3, (float)0x10 / 0xff, 0);
		break;

	case BC_YUV888:
		DUSTS(uint8_t, 3, 0x10, 0x80);
		break;

	case BC_RGBA_FLOAT:
		DUSTS(float, 4, (float)0x10 / 0xff, 0);
		break;

	case BC_RGBA8888:
		DUSTS(uint8_t, 4, 0x10, 0);
		break;

	case BC_YUVA8888:
		DUSTS(uint8_t, 4, 0x10, 0x80);
		break;

	case BC_RGB161616:
		DUSTS(uint16_t, 3, 0x1000, 0);
		break;

	case BC_YUV161616:
		DUSTS(uint16_t, 3, 0x1000, 0x8000);
		break;

	case BC_RGBA16161616:
		DUSTS(uint16_t, 4, 0x1000, 0);
		break;

	case BC_YUVA16161616:
		DUSTS(uint16_t, 4, 0x1000, 0x8000);
		break;
	}
}


void AgingClient::process_package(LoadPackage *package)
{
	AgingPackage *local_package = (AgingPackage*)package;
	unsigned char **input_rows = plugin->input_ptr->get_rows() + local_package->row1;
	unsigned char **output_rows = plugin->output_ptr->get_rows() + local_package->row1;

	if( plugin->config.colorage )
		coloraging(output_rows,
			input_rows,
			plugin->input_ptr->get_color_model(),
			plugin->input_ptr->get_w(),
			local_package->row2 - local_package->row1);
	if( plugin->config.scratch )
		scratching(output_rows,
			plugin->input_ptr->get_color_model(),
			plugin->input_ptr->get_w(),
			local_package->row2 - local_package->row1);
	if( plugin->config.pits && plugin->config.pits_interval > 0 )
		pits(output_rows,
			plugin->input_ptr->get_color_model(),
			plugin->input_ptr->get_w(),
			local_package->row2 - local_package->row1);
	if( plugin->config.dust && plugin->config.dust_interval > 0 )
		dusts(output_rows,
			plugin->input_ptr->get_color_model(),
			plugin->input_ptr->get_w(),
			local_package->row2 - local_package->row1);
}

AgingPackage::AgingPackage()
{
}

