/*
 * CINELERRA
 * Copyright (C) 2011 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 "asset.h"
#include "bcsignals.h"
#include "file.h"
#include "filescene.h"
#include "filesystem.h"
#include "libmjpeg.h"
#include "scenegraph.h"


#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>


extern "C"
{
#include <uuid.h>
}


extern void get_exe_path(char *result);

// Paths relative to the exe path
#define FESTIVAL_PATH "/festival"
#define FESTIVAL_LIB_PATH "/lib/"
#define ASSET_PATH "/models/"
#define FREAD_SIZE 0x10000
#define WAVHEADER 44
#define FESTIVAL_SAMPLERATE 16000
#define PAUSE_SAMPLES FESTIVAL_SAMPLERATE
// Amount to truncate if ...
#define ADVANCE_SAMPLES FESTIVAL_SAMPLERATE

// Maximum characters in a line of dialog is limited by festival
#define MAX_CHARS 65536

#define STRIP_LINE(string) \
/* Strip linefeeds */ \
	while(len > 0 && (string[len - 1] == '\n' || \
		string[len - 1] == ' ')) \
	{ \
		string[len - 1] = 0; \
		len--; \
	} \
 \
/* Strip comments */ \
	for(i = 0; i < len; i++) \
	{ \
		if(string[i] == '#') \
		{ \
			string[i] = 0; \
			i = len; \
		} \
	}




#define STRING_PARAMETER(title, need_char, dst) \
if(!strncmp(command, title, strlen(title))) \
{ \
/* advance to argument */ \
	i += strlen(title); \
	while(string[i] != 0 && string[i] == ' ') \
		i++; \
 \
	if(current_char || !need_char) \
	{ \
/* printf("STRING_PARAMETER %s %s %p\n", title, string + i, dst); */ \
		strcpy(dst, string + i); \
	} \
	else \
	{ \
		printf("FileScene::read_script %d Line %d: %s but no current character\n",  \
			__LINE__, \
			current_line, \
			title); \
	} \
 \
	i = len; \
}

static int read_parameter(char *string,
	int *i,
	const char *title, 
	char *dst_string, 
	float *dst_float0, 
	float *dst_float1, 
	float *dst_float2)
{
	char *command = string + *i;
	char *arg_text[3];

	if(!strncmp(command, title, strlen(title)))
	{
		*i += strlen(title);

		for(int j = 0; j < 4; j++)
		{
/* skip to start of argument */
			while(string[*i] != 0 && string[*i] == ' ')
				(*i)++;

			if(string[*i] != 0)
			{
				arg_text[j] = string + *i;
				while(string[*i] != 0 && string[*i] != ' ')
					(*i)++;
			}
			else
			{
				arg_text[j] = 0;
			}
 		}

// printf("read_parameter %d %s %s %s %s\n", 
// __LINE__, 
// title, 
// arg_text[0], 
// arg_text[1], 
// arg_text[2]);

		if(arg_text[0])
		{
			if(dst_string)
			{
				char *ptr1 = dst_string;
				char *ptr2 = arg_text[0];
				while(*ptr2 != 0 && *ptr2 != ' ')
					*ptr1++ = *ptr2++;
				*ptr1 = 0;
			}
			else
			if(dst_float0)
			{
				*dst_float0 = atof(arg_text[0]);
			}
		}

		if(arg_text[1])
		{
			if(dst_float1)
			{
				*dst_float1 = atof(arg_text[1]);
			}
		}

		if(arg_text[2])
		{
			if(dst_float2)
			{
				*dst_float2 = atof(arg_text[2]);
			}
		}

		return 1;
	}

	return 0;
}













FileScene::FileScene(Asset *asset, File *file)
 : FileBase(asset, file)
{
	reset_parameters();
	get_exe_path(exe_path);
}


FileScene::~FileScene()
{
	close_file();
}



int FileScene::open_file(int rd, int wr)
{
// Load the script to get character count
	read_script();
	


// Set asset format
	asset->format = FILE_SCENE;
	asset->video_data = 1;
// Should be set in the scene file
	asset->layers = 1;
	asset->width = 1280;
	asset->height = 720;
// Dictated by character animations
	asset->frame_rate = (double)30000 / 1001;
// Some arbitrary default length.
// Hard to calculate without rendering it.
	asset->video_length = -1;



	asset->audio_data = 1;
// The speech synthesizer outputs different samplerates depending on the speaker.
// The heroine voices are all 16khz
	asset->sample_rate = FESTIVAL_SAMPLERATE;
// Mono voice placement for now
// Maybe a different track for each voice in the future or 3D positioning
	asset->channels = script->total_characters();
	asset->audio_length = -1;


	return 0;
}

int FileScene::close_file()
{
	delete script;
	delete [] audio_temp;
//	delete overlayer;
//	delete affine;
	FileBase::close_file();
	return 0;
}


int FileScene::check_sig(Asset *asset, char *test)
{
	if(!strncmp(test, "TEXT2MOVIE", 10)) return 1;

	return 0;
}



int FileScene::set_video_position(int64_t x)
{
	return 0;
}


int FileScene::set_audio_position(int64_t x)
{
	return 0;
}


int FileScene::read_frame(VFrame *frame)
{
// Everything is timed based on speech, so render the audio for this frame.
	const int debug = 0;
	
	
	frame->clear_frame();
	int64_t audio_position1 = (int64_t)(file->current_frame * 
		asset->sample_rate /
		asset->frame_rate);
	int64_t audio_position2 = (int64_t)(audio_position1 + 
		asset->sample_rate /
		asset->frame_rate);

	if(debug) printf("FileScene::read_frame %d frame=%lld frame_rate=%f sample_rate=%d audio_position1=%lld audio_position2=%lld\n",
		__LINE__,
		(long long)file->current_frame,
		asset->frame_rate,
		asset->sample_rate,
		(long long)audio_position1,
		(long long)audio_position2);

	render_chunks(audio_position1, 
		audio_position2 - audio_position1,
		1);
	if(!script) return 1;
	if(debug) printf("FileScene::read_frame %d\n", __LINE__);

//	script->dump();


// Determine lip position from amplitude
	double accum = 0;
	for(int i = 0; i < audio_position2 - audio_position1; i++)
	{
		double sample_float = fabs((double)audio_temp[i] / 32768);
		if(sample_float > accum) accum = sample_float;
	}
	if(debug) printf("FileScene::read_frame %d accum=%f\n", __LINE__, accum);

// Reset cameras
	for(int i = 0; i < script->total_characters(); i++)
	{
		SceneChar *character = script->get_character(i);
		character->current_camera = SceneChar::CAMERA_WIDE;
		character->is_speeking = 0;
		character->max = 0;
	}

// Now determine most recent character which is speaking from the sample times.
	int64_t current_sample = 0;
	SceneChar *speeking_character = 0;
// Sample relative to start of chunk
	int64_t chunk_sample = 0;
	for(int i = 0; 
		i < script->total_chunks() && current_sample < audio_position2; 
		i++)
	{
		SceneChunk *chunk = script->get_chunk(i);
		int samples = chunk->audio_size / 2;


		if(audio_position1 >= current_sample && 
			audio_position1 < current_sample + samples)
		{
			speeking_character = chunk->character;
			speeking_character->max = chunk->max;
			chunk->character->is_speeking = 1;
			chunk_sample = audio_position1 - current_sample;
//			break;
		}
		else
		if(!chunk->character->is_speeking)
		{
			chunk->character->increment_camera();
		}

		current_sample += chunk->advance_samples;
	}



	if(debug) printf("FileScene::read_frame %d\n", __LINE__);
	
// Render the scene.
// Store component placement in a scene graph.
	SceneGraph scene;
	SceneNode *speeking_node = 0;

// Scale for the entire scene
	SceneCamera *camera = new SceneCamera;
	scene.append_camera(camera);
	camera->at_x = 0;
	camera->at_y = 0;

// Render background
	if(script->background[0])
	{
		script->render_background(&scene);
	}

	if(debug) printf("FileScene::read_frame %d\n", __LINE__);

// Render characters
	for(int i = 0; i < script->total_characters(); i++)
	{
		SceneChar *character = script->get_character(i);

		character->read_model();

// Nodes for the character
		SceneNode *character_node = new SceneNode(character->name);
		scene.append(character_node);
		character_node->sx = character_node->sy = character->scale;

		SceneNode *head_node = new SceneNode("head");

		SceneNode *body_node = new SceneNode("body");

// Render in the order listed in the file
		if(debug) printf("FileScene::read_frame %d\n", __LINE__);
		for(int j = 0; j < 2; j++)
		{
			if(debug) printf("FileScene::read_frame %d j=%d head_order=%d body_order=%d\n", 
				__LINE__, 
				j,
				character->head_order,
				character->body_order);
			if(j == character->head_order) character_node->append(head_node);
			else
			if(j == character->body_order) character_node->append(body_node);
		}

		SceneNode *eye_node = 0;
		if(character->eyes.size())
		{
			eye_node = new SceneNode("eyes");
			head_node->append(eye_node);
		}

		SceneNode *mouth_node = new SceneNode("mouth");
		head_node->append(mouth_node);

// Logical character placement
		switch(script->total_characters())
		{
			case 1:
				if(speeking_character == character &&
					speeking_character->current_camera == SceneChar::CAMERA_CU)
				{
					camera->at_x = asset->width / 4;
				}

				character_node->x = (int)(asset->width / 2 - 
					character->w * character->scale / 2);
				character_node->y = (int)(asset->height - 
					character->h * character->scale);
				break;

			case 2:
				if(i == 0)
				{
					character_node->x = 0;
					character_node->y = (int)(asset->height - 
						character->h * character->scale);

					if(speeking_character == character &&
						speeking_character->current_camera == SceneChar::CAMERA_CU)
					{
						camera->at_y = character_node->y;
						camera->at_x = character_node->x + 
							(character->head->x + character->head->image->get_w() / 2) * character->scale - 
							asset->width / 4;
						CLAMP(camera->at_x, 0, asset->width / 2);
						CLAMP(camera->at_y, 0, asset->height);
//printf("FileScene::read_frame %d camera->at_x=%f camera->at_y=%f\n", __LINE__, camera->at_x, camera->at_y);
					}

					if(character->faces_left)
					{
						character_node->flip = 1;
						character_node->x = asset->width - 
							character->w * character->scale;
					}
				}
				else
				{

					character_node->x = (int)(asset->width - 
						character->w * character->scale);
					character_node->y = (int)(asset->height - 
						character->h * character->scale);

					if(speeking_character == character &&
						speeking_character->current_camera == SceneChar::CAMERA_CU)
					{
						camera->at_x = character_node->x + 
							character->head->x * character->scale - 
							asset->width / 4;
						CLAMP(camera->at_x, 0, asset->width / 2);
					}

					if(!character->faces_left) 
					{
						character_node->flip = 1;
						character_node->x = 0;
					}
				}
				break;

			case 3:
				if(i == 0)
				{
					character_node->x = 0;
					character_node->y = (int)(asset->height - 
						character->h * character->scale);
					if(character->faces_left)
					{
						character_node->flip = 1;
					}
				}
				else
				if(i == 1)
				{
					if(speeking_character == character &&
						speeking_character->current_camera == SceneChar::CAMERA_CU)
					{
						camera->at_x = asset->width / 4;
					}
					character_node->x = (int)(asset->width / 2 - 
						character->w * character->scale / 2);
					character_node->y = (int)(asset->height - 
						character->h * character->scale);
					if(character->faces_left)
					{
						character_node->flip = 1;
					}
				}
				else
				{
					if(speeking_character == character &&
						speeking_character->current_camera == SceneChar::CAMERA_CU)
					{
						camera->at_x = asset->width / 2;
					}
					character_node->x = (int)(asset->width - 
						character->w * character->scale);
					character_node->y = (int)(asset->height - 
						character->h * character->scale);
					if(!character->faces_left)
					{
						character_node->flip = 1;
						character_node->x = 0;
					}
				}
				break;
		}

// Clamp the head
		if(character_node->y < 0) character_node->y = 0;

// Add remaining parts
		body_node->copy_ref(character->body);
		head_node->copy_ref(character->head);

// Speeker head rotation
		if(speeking_character == character)
		{
			speeking_node = character_node;
		
			int head_time = (chunk_sample / asset->sample_rate / 2) % 2;

			if(head_time > 0)
			{
				double temp;
				double anim_position = modf((double)chunk_sample / asset->sample_rate / 2, &temp);
				double anim_length = 0.1;
// printf("FileScene::read_frame %d %d %f\n", 
// __LINE__, 
// head_time,
// anim_position);

				if(anim_position < anim_length)
					head_node->ry = -5 * anim_position / anim_length;
				else
				if(anim_position > 1.0 - anim_length)
					head_node->ry = -5 * (1.0 - anim_position) / anim_length;
				else
					head_node->ry = -5;
			}
		}

//head_node->ry = -5;

// Eyes
		double intpart;
		if(character->eyes.size())
		{
			if(modf((file->current_frame / asset->frame_rate + script->get_char_number(character)) / 5, &intpart) <= 
				0.1 / 5 &&
				file->current_frame / asset->frame_rate > 1)
			{
				eye_node->copy_ref(character->eyes.get(0));
			}
			else
				eye_node->copy_ref(character->eyes.get(1));
		}

// Compute the mouth image
		int fraction = 0;
		if(character->is_speeking && character->max > 0)
		{
			fraction = (int)(accum * character->mouths.size() / character->max);
			if(fraction >= character->mouths.size())
				fraction = character->mouths.size() - 1;
		}

		mouth_node->copy_ref(character->mouths.get(fraction));


// camera->scale = 2;
// camera->at_x = asset->width / 2;
// camera->at_y = 0;

// Compute camera
		if(speeking_character == character &&
			speeking_character->current_camera == SceneChar::CAMERA_CU)
		{
// If closeup, increase scene scale
			camera->scale = 2;
		}
	}




	if(debug) printf("FileScene::read_frame %d\n", __LINE__);



// Render scene graph
	scene.render(frame, file->cpus);
		
		
		
	if(debug) printf("FileScene::read_frame %d\n", __LINE__);


	return 0;
}


int FileScene::read_samples(double *buffer, int64_t len)
{
// Speech rendering
// Everything is timed based on speech, so we have to do this for video, too.
	render_chunks(file->current_sample, len, 0);

//	script->dump();


// Convert temp to output
	for(int i = 0; i < len; i++)
	{
		buffer[i] = (double)audio_temp[i] / 32768;
	}

	return 0;
}


int64_t FileScene::get_memory_usage()
{
//PRINT_TRACE
	int total = 0x100000;
	if(script)
	{
		total += script->get_memory_usage();
		
	}
//PRINT_TRACE
	return total;
}



int FileScene::get_best_colormodel(Asset *asset, int driver)
{
	return 0;
}


int FileScene::colormodel_supported(int colormodel)
{
	return BC_RGBA8888;
}


int FileScene::can_copy_from(Asset *asset, int64_t position)
{
	return 0;
}


int FileScene::reset_parameters_derived()
{
	script = 0;
	audio_temp = 0;
	temp_allocated = 0;
//	overlayer = 0;
//	affine = 0;
	return 0;
}



void FileScene::render_chunks(int64_t start_position, 
	int64_t len, 
	int all_channels)
{
	int64_t end_position = start_position + len;
	const int debug = 0;


	if(debug) printf("FileScene::render_chunks %d start_position=%lld len=%lld\n",
		__LINE__,
		(long long)start_position,
		(long long)len);

// Update script
	read_script();

	if(!script) return;

	if(debug) PRINT_TRACE

// Reallocate temp buffer
	if(len > temp_allocated)
	{
		delete [] audio_temp;
		audio_temp = new int16_t[len];
		temp_allocated = len;
	}
	bzero(audio_temp, sizeof(int16_t) * len);

	if(debug) PRINT_TRACE




// Find start_position in script output.
// Must know length of every chunk before end of buffer
	int64_t current_sample = 0;
	for(int i = 0; i < script->total_chunks() &&
		current_sample < end_position; i++)
	{
		SceneChunk *chunk = script->get_chunk(i);
		chunk->used = 0;
		if(debug) printf("FileScene::render_chunks %d\n", __LINE__);

// If extent of audio output hasn't been calculated, render it
		if(!chunk->audio_size)
		{
			if(debug) printf("FileScene::render_chunks %d i=%d\n", __LINE__, i);
			chunk->render();
		}

// Dialog chunk is inside output buffer
		if(current_sample + chunk->audio_size / 2 > start_position &&
			current_sample < end_position)
		{
			if(debug) printf("FileScene::render_chunks %d\n", __LINE__);

// If no audio output exists, render it
			if(!chunk->audio)
			{
				if(debug) printf("FileScene::render_chunks %d rerendering audio\n", __LINE__);
				chunk->render();
			}

			if(debug) printf("FileScene::render_chunks %d: Using \"%s\" samples=%d\n",
				__LINE__,
				chunk->text,
				chunk->audio_size / 2);
			if(debug) printf("FileScene::render_chunks %d: start_position=%lld current_sample=%lld\n",
				__LINE__,
				(long long)start_position,
				(long long)current_sample);

// Memcpy it.
// TODO: allow characters to talk simultaneously
			int64_t src_offset = start_position - current_sample;
			int64_t dst_offset = 0;
			int64_t src_len = chunk->audio_size / 2 - src_offset;

			if(debug) printf("FileScene::render_chunks %d: src_offset=%lld dst_offset=%lld src_len=%lld\n",
				__LINE__,
				(long long)src_offset,
				(long long)dst_offset,
				(long long)src_len);

			if(src_offset < 0)
			{
				dst_offset -= src_offset;
				src_len += src_offset;
				src_offset = 0;
			}

			if(dst_offset + src_len > len)
			{
				src_len = len - dst_offset;
			}

			if(debug) printf("FileScene::render_chunks %d: src_offset=%lld dst_offset=%lld src_len=%lld\n",
				__LINE__,
				(long long)src_offset,
				(long long)dst_offset,
				(long long)src_len);

// Transfer if right channel
			if(all_channels || 
				file->current_channel == script->get_char_number(chunk->character))
			{
				for(int j = 0; j < src_len; j++)
				{
					audio_temp[dst_offset + j] = 
						chunk->audio[(src_offset + j) * 2] | 
						(chunk->audio[(src_offset + j) * 2 + 1] << 8);
				}
			}

			if(debug) printf("FileScene::render_chunks %d\n", __LINE__);
			chunk->used = 1;
		}

		current_sample += chunk->advance_samples;
	}

// Erase unused dialog chunks
	if(debug) printf("FileScene::render_chunks %d\n", __LINE__);
	for(int i = 0; i < script->total_chunks(); i++)
	{
		SceneChunk *chunk = script->get_chunk(i);
		if(!chunk->used && chunk->audio)
		{
			if(debug) printf("FileScene::render_chunks %d erasing unused audio\n", __LINE__);
			delete [] chunk->audio;
			chunk->audio = 0;
			chunk->audio_allocated = 0;
		}
	}
	if(debug) printf("FileScene::render_chunks %d\n", __LINE__);
	
	
}



int FileScene::read_script()
{
	const int debug = 0;
	struct stat ostat;
	if(stat(asset->path, &ostat))
	{
		printf("FileScene::read_script %d: %s\n", __LINE__, strerror(errno));
		return 1;
	}

// Test timestamp
	if(script)
	{
		if(ostat.st_mtime == script->timestamp)
		{
			if(debug) printf("FileScene::read_script %d: script unchanged\n", __LINE__);
			return 0;
		}
	}


// Read new script
	delete script;
	script = 0;
	if(!script) script = new SceneTokens(this, file->cpus);
	script->timestamp = ostat.st_mtime;

	script->read_script(asset->path);

	return 1;
}















SceneChar::SceneChar(SceneTokens *script)
{
	this->script = script;
	name[0] = 0;
	voice[0] = 0;
	body = 0;
	head = 0;
	body_order = 0;
	head_order = 1;
	sprintf(model, "heroine01");
	current_camera = CAMERA_WIDE;
	faces_left = 0;
	scale = 1;
	max = 0;
	is_speeking = 0;
}

SceneChar::~SceneChar()
{
	mouths.remove_all_objects();
	eyes.remove_all_objects();
	delete body;
	delete head;
}


void SceneChar::increment_camera()
{
	current_camera++;
	if(current_camera >= CAMERA_TOTAL) 
		current_camera = 0;
}


int SceneChar::read_model()
{
// Read descriptor file
	const int debug = 0;
	int current_line = 0;
	float x, y;
	int i;
	int current_order = 0;
	char path[BCTEXTLEN];

// Already read it
	if(body) return 0;

	script->convert_path(path, model);
	FILE *fd = fopen(path, "r");
	

// Read assets
	if(fd)
	{
// Read 1 line
		char string[BCTEXTLEN];
		char string2[BCTEXTLEN];

		while(!feof(fd))
		{
			char *result = fgets(string, BCTEXTLEN, fd);
			
			if(result)
			{
				int len = strlen(string);

				STRIP_LINE(string);

				if(debug) printf("SceneChar::read_model %d: %s\n", 
					__LINE__, 
					string);
			
				for(i = 0; i < len; i++)
				{
					if(isalnum(string[i]))
					{
						string2[0] = 0;
						
						if(read_parameter(string,
							&i,
							"width:", 
							0, 
							&w, 
							0, 
							0))
						{
						}
						else
						if(read_parameter(string,
							&i,
							"height:", 
							0, 
							&h, 
							0, 
							0))
						{
						}
						else
						if(read_parameter(string,
							&i,
							"body:", 
							string2, 
							0, 
							&x, 
							&y))
						{
// Load image
							body = new SceneNode(script->load_image(string2), 1, x, y);
							body_order = current_order++;
						}
						else
						if(read_parameter(string,
							&i,
							"head:", 
							string2, 
							0, 
							&x, 
							&y))
						{
// Load image
							head = new SceneNode(script->load_image(string2), 1, x, y);
							head_order = current_order++;
						}
						else
						if(read_parameter(string,
							&i,
							"mouth:", 
							string2, 
							0, 
							&x, 
							&y))
						{
// Load image
							SceneNode *mouth;
							mouths.append(mouth = new SceneNode(script->load_image(string2), 1, x, y));
// Make coordinates relative to head
							if(head)
							{
								mouth->x -= head->x;
								mouth->y -= head->y;
							}
						}
						else
						if(read_parameter(string,
							&i,
							"eyes:", 
							string2, 
							0, 
							&x, 
							&y))
						{
// Load image
							SceneNode *eye;
							eyes.append(eye = new SceneNode(script->load_image(string2), 1, x, y));
// Make coordinates relative to head
							if(head)
							{
								eye->x -= head->x;
								eye->y -= head->y;
							}
						}
						else
						if(read_parameter(string,
							&i,
							"faces_left", 
							0, 
							0, 
							0, 
							0))
						{
							faces_left = 1;
						}
						else
						if(read_parameter(string,
							&i,
							"scale:", 
							0, 
							&scale, 
							0, 
							0))
						{
						}
						
						i = len;
					}
				}
			}
			
			current_line++;
		}
		
		fclose(fd);
		if(debug) dump();
		return 0;
	}
	
	printf("SceneChar::read_model %d: %s %s\n", __LINE__, path, strerror(errno));
	return 1;
}

int SceneChar::get_memory_usage()
{
	int total = 0;
	if(body) total += body->get_memory_usage();
	if(head) total += head->get_memory_usage();
	for(int i = 0; i < mouths.size(); i++)
		total += mouths.get(i)->get_memory_usage();
	return total;
}

void SceneChar::dump()
{
	printf("SceneChar::dump %d: %p name=%s voice=%s model=%s body=%p eyes=%d mouths=%d\n", 
		__LINE__, 
		this,
		name, 
		voice, 
		model,
		body,
		eyes.size(),
		mouths.size());
	printf("SceneChar::dump %d: w=%f h=%f\n", __LINE__, w, h);


	for(int i = 0; i < mouths.size(); i++)
	{
		SceneNode *node = mouths.get(i);
		printf("    mouth=%p x=%f y=%f\n", node, node->x, node->y);
	}

	for(int i = 0; i < eyes.size(); i++)
	{
		SceneNode *node = eyes.get(i);
		printf("    eyes=%p x=%f y=%f\n", node, node->x, node->y);
	}
}









// Dialog from a single character
SceneChunk::SceneChunk(SceneTokens *script)
{
	text = 0;
	character = 0;
	audio = 0;
	audio_size = 0;
	audio_allocated = 0;
	advance_samples = 0;
	used = 0;
	max = 0;
	command = NO_COMMAND;
	this->script = script;
}

SceneChunk::~SceneChunk()
{
	delete [] text;
	delete [] audio;
}

void SceneChunk::dump()
{
	printf("SceneChunk::dump %d: character=%s command=%d text=%s\n", 
		__LINE__, 
		character->name,
		command,
		text);
	printf("SceneChunk::dump %d: audio=%p audio_size=%d advance_samples=%d\n",
		__LINE__,
		audio,
		audio_size,
		advance_samples);
}

int SceneChunk::get_memory_usage()
{
	return audio_allocated;
}


void SceneChunk::append_text(char *new_text)
{
	char string[BCTEXTLEN];

// Replace "
// Convert ' to \'
	char *ptr = string;
	for(int i = 0; i < strlen(new_text); i++)
	{
// 		if(new_text[i] == '"')
// 			*ptr++ = ' ';
// 		else
// 		if(new_text[i] == '\'')
// 		{
// 			*ptr++ = '\\';
// 			*ptr++ = '\'';
// 			*ptr++ = '\'';
// 		}
// 		else
			*ptr++ = new_text[i];
	}
	*ptr++ = 0;
	
	int len2 = strlen(string);
	if(text)
	{
		int len1 = strlen(text);
		int len3 = 1;
		int need_space = 0;

//		if(len1 > 0 && isalnum(text[len1 - 1]))
		if(len1 > 0 && text[len1 - 1] != ' ')
		{
			need_space = 1;
			len3++;
		}

		text = (char*)realloc(text, len1 + len2 + len3);

// Append space
		if(need_space)
		{
			text[len1] = ' ';
			text[len1 + 1] = 0;
		}
	}
	else
	{
		text = new char[len2 + 1];
		text[0] = 0;
	}

	strcat(text, string);
}




void SceneChunk::render()
{
	const int debug = 0;
	int len = 0;
	char command_line[BCTEXTLEN];
	char string2[MAX_CHARS];
	char script_path[BCTEXTLEN];
	
	int total_args = 0;
	if(text) len = strlen(text);
	char *text_end = text + len;
	char *text_ptr = text;

	audio_size = 0;

	if(!character)
	{
		printf("SceneChunk::render %d: no character defined.\n", __LINE__);
	}
	
	if(len > MAX_CHARS)
	{
		printf("SceneChunk::render %d: text '%s' exceeds festival's maximum line length of %d chars.\n",
			__LINE__,
			text,
			MAX_CHARS);
	}

// Process command
	switch(command)
	{
		case SceneChunk::PAUSE:
			audio_allocated = PAUSE_SAMPLES * 2;
			audio_size = PAUSE_SAMPLES * 2;
			advance_samples = PAUSE_SAMPLES;
			audio = (unsigned char*)realloc(audio, audio_allocated);
			bzero(audio, audio_size);
			max = 0;
			break;
	}

	while(text && text_ptr < text_end)
	{
// Copy at most MAX_CHARS of data into string2
		char *ptr = string2;
		for(int i = 0; i < MAX_CHARS && text_ptr < text_end; i++)
		{
			*ptr++ = *text_ptr++;
		}
		*ptr++ = 0;


// Rewind to white space if still more text
		if(text_ptr < text_end)
		{
			ptr--;
			text_ptr--;
			while(*text_ptr != ' ' && 
				*text_ptr != '\n' && 
				text_ptr > text)
			{
				text_ptr--;
				ptr--;
			}


// Truncate string2 at white space
			*ptr = 0;
// If no white space, abort.
			if(text_ptr <= text)
			{
				break;
			}

		}

		uuid_t temp_id;
		sprintf(script_path, "/tmp/cinelerra.");
		uuid_generate(temp_id);
		uuid_unparse(temp_id, script_path + strlen(script_path));
		FILE *script_fd = fopen(script_path, "w");

		sprintf(command_line, "%s%s --libdir %s%s -b %s", 
			script->file->exe_path,
			FESTIVAL_PATH,
			script->file->exe_path,
			FESTIVAL_LIB_PATH,
			script_path);

// Create script.
// The maximum text length is limited with the command line

		fprintf(script_fd, 
			"(voice_%s)\n"
			"(set! text (Utterance Text \"%s\"))\n"
			"(utt.synth text)"
			"(utt.save.wave text \"-\")",
			character->voice,
			string2);
		fclose(script_fd);


		if(debug)
		{
			printf("SceneChunk::render %d %s\n", 
				__LINE__,
				command_line);
			
			FILE *script_fd = fopen(script_path, "r");
			while(!feof(script_fd))
				fputc(fgetc(script_fd), stdout);
			printf("\n");
			fclose(script_fd);
		}

// popen only does half duplex
		FILE *fd = popen(command_line, "r");

// Capture output
		if(fd)
		{
			int audio_start = audio_size;

		
			if(debug) printf("SceneChunk::render %d\n", 
				__LINE__);
			while(!feof(fd))
			{
				if(debug) printf("SceneChunk::render %d\n", 
					__LINE__);
				if(audio_size + FREAD_SIZE > audio_allocated)
				{
					audio_allocated += FREAD_SIZE;
					audio = (unsigned char*)realloc(audio, audio_allocated);
				}


				if(debug) printf("SceneChunk::render %d audio=%p audio_size=%d\n", 
					__LINE__,
					audio,
					audio_size);

				int bytes_read = fread(audio + audio_size, 1, FREAD_SIZE, fd);
				if(debug) printf("SceneChunk::render %d bytes_read=%d\n", 
					__LINE__,
					bytes_read);
				audio_size += bytes_read;
 				if(bytes_read < FREAD_SIZE)
 				{
 					break;
 				}
			}


			pclose(fd);

			if(debug) printf("SceneChunk::render %d audio=%p audio_size=%d audio_allocated=%d\n", 
				__LINE__,
				audio,
				audio_size,
				audio_allocated);

// Strip WAV header

			if(audio_size - audio_start > WAVHEADER)
			{
// for(int i = 0; i < 128; i++)
// {
// printf("%c ", audio[audio_start + i]);
// if(!((i + 1) % 16)) printf("\n");
// }
// printf("\n");
// Find header after error messages
				int header_size = WAVHEADER;
				for(int i = audio_start; i < audio_start + audio_size - 4; i++)
				{
					if(audio[i] == 'R' &&
						audio[i + 1] == 'I' &&
						audio[i + 2] == 'F' &&
						audio[i + 3] == 'F')
					{
						header_size += i - audio_start;
						break;
					}
				}

				memcpy(audio + audio_start, 
					audio + audio_start + header_size, 
					audio_size - audio_start - header_size);
				audio_size -= header_size;
				if(debug) printf("SceneChunk::render %d: audio_size=%d\n",
					__LINE__,
					audio_size);
			}
			
			advance_samples = audio_size / 2;
		}
		else
		{
			printf("SceneChunk::render %d: Couldn't run %s: %s\n",
				__LINE__,
				command_line,
				strerror(errno));
		}

		remove(script_path);
		if(debug) printf("SceneChunk::render %d max=%f\n", 
			__LINE__,
			max);
	}


	if(text)
	{

// Truncate if ...
		text_ptr = text + len - 1;
		while(text_ptr > text && 
			(*text_ptr == ' ' ||
			*text_ptr == '\n'))
			text_ptr--;
		if(text_ptr > text + 3 &&
			*(text_ptr - 0) == '.' &&
			*(text_ptr - 1) == '.' &&
			*(text_ptr - 2) == '.')
		{
			advance_samples -= ADVANCE_SAMPLES;
			if(advance_samples < 0) advance_samples = 0;
		}

// Calculate loudest part
		max = 0;
		for(int i = 0; i < audio_size; i += 2)
		{
			int16_t sample = audio[i] |
				(audio[i + 1] << 8);
			double sample_float = fabs((double)sample / 32768);
			if(sample_float > max) max = sample_float;
		}
	}
}













SceneTokens::SceneTokens(FileScene *file, int cpus)
{
	background[0] = 0;
	background_image = 0;
	timestamp = 0;
	this->cpus = cpus;
	this->file = file;
//	overlayer = 0;
}

SceneTokens::~SceneTokens()
{
	chunks.remove_all_objects();
	characters.remove_all_objects();
	delete background_image;
//	delete overlayer;
}

SceneChar* SceneTokens::get_character(char *name)
{
	const int debug = 0;
	if(debug) printf("SceneTokens::get_character %d %d\n",
		__LINE__,
		characters.size());

	for(int i = 0; i < characters.size(); i++)
	{
		if(!strcmp(characters.get(i)->name, name)) return characters.get(i);
	}
	if(debug) printf("SceneTokens::get_character %d %d\n",
		__LINE__,
		characters.size());
	
	SceneChar *result = new SceneChar(this);
	if(debug) printf("SceneTokens::get_character %d %d this=%p\n",
		__LINE__,
		characters.size(),
		&characters);

	characters.append(result);
	if(debug) printf("SceneTokens::get_character %d %d\n",
		__LINE__,
		characters.size());

	strcpy(result->name, name);
	if(debug) printf("SceneTokens::get_character %d %d\n",
		__LINE__,
		characters.size());

	if(debug) printf("SceneTokens::get_character %d %d\n",
		__LINE__,
		characters.size());

	return result;
}

SceneChar* SceneTokens::get_character(int number)
{
	return characters.get(number);
}

int SceneTokens::get_char_number(SceneChar *ptr)
{
	for(int i = 0; i < characters.size(); i++)
		if(ptr == characters.get(i)) return i;
	return 0;
}

SceneChunk* SceneTokens::new_chunk()
{
	SceneChunk *result = new SceneChunk(this);
	chunks.append(result);
	return result;
}

int SceneTokens::total_chunks()
{
	return chunks.size();
}

int SceneTokens::total_characters()
{
	return characters.size();
}

SceneChunk* SceneTokens::get_chunk(int number)
{
	return chunks.get(number);
}

int SceneTokens::read_script(char *path)
{
	const int debug = 0;

	strcpy(this->path, path);

	FILE *fd = fopen(path, "r");
	if(fd)
	{
// Read 1 line
		char string[BCTEXTLEN];
// Current character name
		char char_name[BCTEXTLEN];
		SceneChar *current_char = 0;
		char_name[0] = 0;
		SceneChunk *current_chunk = 0;
		int current_line = 0;
		int i;

		while(!feof(fd))
		{
			char *result = fgets(string, BCTEXTLEN, fd);
			current_line++;

			if(result)
			{
				int len = strlen(string);
				STRIP_LINE(string)

				if(debug) printf("SceneTokens::read_script %d: %s\n", 
					__LINE__, 
					string);

// Skip the file ID & empty lines
				if(string[0] == 0 ||
					!strncmp(string, "TEXT2MOVIE", 10))
					continue;

				int got_it = 0;
				for(i = 0; i < len; i++)
				{
					if(isalnum(string[i]))
					{
						got_it = 1;
						i = len;
					}
				}

				if(!got_it) continue;

// A line all in caps is a character name
				got_it = 1;
				for(i = 0; i < len; i++)
				{
					if(islower(string[i]))
					{
						got_it = 0;
						i = len;
					}
				}

				if(got_it)
				{
					strcpy(char_name, string);

					if(debug) printf("SceneTokens::read_script %d: char_name=%s\n", 
						__LINE__, 
						char_name);

					current_char = get_character(char_name);

					if(debug) printf("SceneTokens::read_script %d current_char=%p\n", 
						__LINE__, 
						current_char);

// Reset the current chunk pointer
					current_chunk = 0;
					i = len;
				}
				else
					i = 0;

// Certain words are commands
				for(; i < len; i++)
				{
					if(string[i] == '[' || isalnum(string[i]))
					{
						char *command = string + i;

						STRING_PARAMETER("voice:", 1, current_char->voice)
						else
						STRING_PARAMETER("model:", 1, current_char->model)
						else
						STRING_PARAMETER("background:", 0, background)
						else
// Default is dialogue
						{
							if(!current_char)
							{
								printf("SceneTokens::read_script %d Line %d: dialogue text but no current character\n", 
									__LINE__,
									current_line);
							}
							else
							{
								if(!current_chunk)
								{
									current_chunk = new_chunk();
									current_chunk->character = current_char;
								}

// Append dialogue to current chunk
								current_chunk->append_text(string + i);
							}
							
							i = len;
						}
					}
				}
			}
		}
		
		
		
		fclose(fd);

		if(debug) printf("SceneTokens::read_script %d total_chunks=%d\n", 
			__LINE__,
			total_chunks());
// Parse commands in dialogue
		for(i = 0; i < total_chunks(); i++)
		{
			SceneChunk *chunk = get_chunk(i);
			if(chunk->text)
			{
				char *ptr = chunk->text;
				char *end = chunk->text + strlen(chunk->text);
				int got_text = 0;
				if(debug) printf("SceneTokens::read_script %d %s\n", __LINE__, chunk->text);
				while(ptr < end)
				{
// Start of command
					if(*ptr == '[')
					{
						if(debug) printf("SceneTokens::read_script %d [\n", __LINE__);
// Split chunk
						if(got_text)
						{
							SceneChunk *new_chunk = new SceneChunk(this);
							new_chunk->character = chunk->character;
							chunks.insert(new_chunk, i + 1);
							
// Move text from start of command to new chunk.
							new_chunk->append_text(ptr);
// Truncate current chunk
							*ptr = 0;
// Advance to next chunk
							ptr = new_chunk->text;
							end = new_chunk->text + strlen(new_chunk->text);
							chunk = new_chunk;
							i++;
						}
						if(debug) printf("SceneTokens::read_script %d\n", __LINE__);

// Read command
						while(ptr < end && 
							(*ptr == '[' || *ptr == ' ' || *ptr == '\n')) 
							ptr++;

						if(debug) printf("SceneTokens::read_script %d\n", __LINE__);

						char *ptr2 = string;
						char *string_end = string + BCTEXTLEN;
						while(*ptr != ']' && 
							*ptr != ' ' &&
							*ptr != '\n' &&
							ptr < end && 
							ptr2 < string_end - 1)
						{
							*ptr2++ = *ptr++;
						}
						*ptr2 = 0;
						if(debug) printf("SceneTokens::read_script %d command=%s\n", __LINE__, string);

// Convert command to code
						if(!strcasecmp(string, "pause"))
						{
							chunk->command = SceneChunk::PAUSE;
						}
						else
						{
// TODO: line numbers
							printf("SceneTokens::read_script %d: Unknown command '%s'\n", 
								__LINE__, 
								string);
						}
						if(debug) printf("SceneTokens::read_script %d\n", __LINE__);

// Search for more text
						while(ptr < end && 
							(*ptr == ']' || *ptr == ' ' || *ptr == '\n')) 
							ptr++;

// Create new chunk for rest of text
						if(ptr < end)
						{
							SceneChunk *new_chunk = new SceneChunk(this);
							new_chunk->character = chunk->character;
							chunks.insert(new_chunk, i + 1);
// Move text from end of command to new chunk.
							new_chunk->append_text(ptr);
// Parse next chunk in next iteration
							ptr = end;
//							i++;
						}
						if(debug) printf("SceneTokens::read_script %d\n", __LINE__);

// Truncate current chunk
						chunk->text[0] = 0;
					}
					else
// Got non whitespace
					if(*ptr != ' ' &&
						*ptr != '\n')
					{
						got_text = 1;
					}

					ptr++;
				}
			}
		}
		
		
		if(debug) dump();
		return 0;
	}

	printf("SceneTokens::read_script %d: %s %s\n", __LINE__, path, strerror(errno));
	return 1;
}

void SceneTokens::convert_path(char *dst, char *src)
{
	char string[BCTEXTLEN];

// Absolute path in src
	if(src[0] == '/')
	{
		strcpy(dst, src);
	}
	else
// Relative path
	{
// Try directory of script
		FileSystem fs;
		fs.extract_dir(dst, path);
		strcat(dst, src);
		
		struct stat ostat;
		if(stat(dst, &ostat))
		{
// Try cinelerra directory
			get_exe_path(dst);
			strcat(dst, ASSET_PATH);
			strcat(dst, src);
//printf("SceneTokens::convert_path %d %s\n", __LINE__, dst);
		}
	}
}

VFrame* SceneTokens::load_image(char *path)
{
	VFrame *result = 0;
	char complete_path[BCTEXTLEN];
	convert_path(complete_path, path);

	int64_t size = FileSystem::get_size(complete_path);
	if(!size)
	{
		printf("SceneTokens::load_image %d: Couldn't open %s\n",
			__LINE__,
			complete_path);
		return 0;
	}

	unsigned char *data = new unsigned char[size + 4];
	FILE *fd = fopen(complete_path, "r");
	int temp = fread(data + 4, 1, size, fd);
	data[0] = (size >> 24) & 0xff;
	data[1] = (size >> 16) & 0xff;
	data[2] = (size >> 8) & 0xff;
	data[3] = size & 0xff;
	result = new VFrame(data);
	delete [] data;


	if(!cmodel_has_alpha(result->get_color_model()) )
		printf("SceneTokens::load_image %d: image %s has no alpha channel\n", 
			__LINE__,
			path);
	return result;
}

void SceneTokens::render_background(SceneGraph *scene)
{
// Decompress background image
	if(!background_image)
	{
		background_image = load_image(background);
	}
	
	if(background_image)
	{
		SceneNode *node = new SceneNode("background");
		scene->append(node);
		node->image = background_image;
		node->x = 0;
		node->y = 0;
		node->sx = 1;
		node->sy = 1;
	}
}

int SceneTokens::get_memory_usage()
{
	int total = 0;
	if(background_image) total += background_image->get_memory_usage();
	for(int i = 0; i < total_chunks(); i++)
	{
		total += get_chunk(i)->get_memory_usage();
	}
	for(int i = 0; i < total_characters(); i++)
	{
		total += get_character(i)->get_memory_usage();
	}
	return total;
}


void SceneTokens::dump()
{
	printf("SceneTokens::dump %d background=%s\n", __LINE__, background);
	for(int i = 0; i < characters.size(); i++)
	{
		characters.get(i)->dump();
	}

	
	for(int i = 0; i < chunks.size(); i++)
	{
		chunks.get(i)->dump();
	}
}




