
/*
 * 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 "asset.h"
#include "bcsignals.h"
#include "edit.h"
#include "filetga.h"
#include "language.h"
#include "mwindow.inc"
#include "vframe.h"
#include "mainerror.h"

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

/* Known image types. */
#define TGA_TYPE_MAPPED      1
#define TGA_TYPE_COLOR       2
#define TGA_TYPE_GRAY        3

/* Only known compression is RLE */
#define TGA_COMP_NONE        0
#define TGA_COMP_RLE         1


FileTGA::FileTGA(Asset *asset, File *file)
 : FileList(asset, file, "TGALIST", ".tga", FILE_TGA, FILE_TGA_LIST)
{
	temp = 0;
}

FileTGA::~FileTGA()
{
	if(temp) delete temp;
}

int FileTGA::check_sig(Asset *asset)
{


// Test file extension
	int result = 0;
	char *ext = strrchr(asset->path, '.');

	if(ext)
	{

		if(!strncasecmp(ext, ".tga", 4)) result = 1;

	}



// Test for list
	if(!result)
	{
		FILE *stream;
		if(!(stream = fopen(asset->path, "rb")))
		{
// file not found
			result = 0;
		}
		else
		{
			char test[16];
			(void)fread(test, 16, 1, stream);
			fclose(stream);
			if(test[0] == 'T' && test[1] == 'G' && test[2] == 'A' &&
				test[3] == 'L' && test[4] == 'I' && test[5] == 'S' &&
				test[6] == 'T')
			{
				result = 1;
			}

		}
	}


	return result;
}

void FileTGA::get_parameters(BC_WindowBase *parent_window,
	Asset *asset, BC_WindowBase* &format_window,
	int audio_options, int video_options, EDL *edl)
{
	if(video_options)
	{
		TGAConfigVideo *window = new TGAConfigVideo(parent_window, asset);
		format_window = window;
		window->create_objects();
		window->run_window();
		delete window;
	}
}

#if 0
N_("RGB compressed")
N_("RGBA compressed")
N_("RGB uncompressed")
N_("RGBA uncompressed")
#endif

#define TGA_RGB_RLE "rle "
#define TGA_RGBA_RLE "rlea"
#define TGA_RGB "raw "
#define TGA_RGBA "rawa"

#define TGA_RGB_RLE_NAME "RGB compressed"
#define TGA_RGBA_RLE_NAME "RGBA compressed"
#define TGA_RGB_NAME "RGB uncompressed"
#define TGA_RGBA_NAME "RGBA uncompressed"

const char* FileTGA::compression_to_str(const char *compression)
{
	if(!strcasecmp(compression, TGA_RGB_RLE)) return _(TGA_RGB_RLE_NAME);
	if(!strcasecmp(compression, TGA_RGBA_RLE)) return _(TGA_RGBA_RLE_NAME);
	if(!strcasecmp(compression, TGA_RGB)) return _(TGA_RGB_NAME);
	if(!strcasecmp(compression, TGA_RGBA)) return _(TGA_RGBA_NAME);
	return TGA_RGB_NAME;
}

const char* FileTGA::str_to_compression(const char *string)
{
	if(!strcasecmp(compression_to_str(TGA_RGB_RLE), string)) return TGA_RGB_RLE;
	if(!strcasecmp(compression_to_str(TGA_RGBA_RLE), string)) return TGA_RGBA_RLE;
	if(!strcasecmp(compression_to_str(TGA_RGB), string)) return TGA_RGB;
	if(!strcasecmp(compression_to_str(TGA_RGBA), string)) return TGA_RGBA;
	return TGA_RGB;
}

int FileTGA::can_copy_from(Asset *asset, int64_t position)
{
	if(asset->format == FILE_TGA_LIST ||
		asset->format == FILE_TGA)
		return 1;

	return 0;
}


int  FileTGA::colormodel_supported(int colormodel)
{
	return colormodel;
}

int FileTGA::get_best_colormodel(Asset *asset, int driver)
{
	if(!strcasecmp(asset->vcodec, TGA_RGB_RLE) ||
		!strcasecmp(asset->vcodec, TGA_RGB)) return BC_RGB888;
	if(!strcasecmp(asset->vcodec, TGA_RGBA_RLE) ||
		!strcasecmp(asset->vcodec, TGA_RGBA)) return BC_RGBA8888;
	return BC_RGB888;
}

int FileTGA::read_frame(VFrame *frame, VFrame *data)
{
	read_tga(asset, frame, data, temp);
	return 0;
}

int FileTGA::write_frame(VFrame *frame, VFrame *data, FrameWriterUnit *unit)
{
	TGAUnit *tga_unit = (TGAUnit*)unit;

	write_tga(asset, frame, data, tga_unit->temp);
	return 0;
}

FrameWriterUnit* FileTGA::new_writer_unit(FrameWriter *writer)
{
	return new TGAUnit(this, writer);
}

int64_t FileTGA::get_memory_usage()
{
	int64_t result = FileList::get_memory_usage();
	if(temp) result += temp->get_data_size();
	return result;
}








#define FOOTERSIZE 26
#define HEADERSIZE 18
int FileTGA::read_frame_header(char *path)
{
	int result = 0;

//printf("FileTGA::read_frame_header 1\n");
	FILE *stream;

	if(!(stream = fopen(path, "rb")))
	{
		eprintf(_("Error while opening \"%s\" for reading. \n%m\n"), asset->path);
		return 1;
	}

	unsigned char header[HEADERSIZE];
	(void)fread(header, HEADERSIZE, 1, stream);
	fclose(stream);

	asset->width = header[12] | (header[13] << 8);
	asset->height = header[14] | (header[15] << 8);
	int bpp = header[16];
	int rle = header[2] & 0x8;
	switch(bpp)
	{
		case 32:
			if(rle)
				strcpy(asset->vcodec, TGA_RGBA_RLE);
			else
				strcpy(asset->vcodec, TGA_RGBA);
			break;
		case 24:
			if(rle)
				strcpy(asset->vcodec, TGA_RGB_RLE);
			else
				strcpy(asset->vcodec, TGA_RGB);
			break;
	}
//printf("FileTGA::read_frame_header 2 %d %d\n", asset->width, asset->height);

	return result;
}

void FileTGA::read_tga(Asset *asset, VFrame *frame, VFrame *data, VFrame* &temp)
{
// Read header
	int64_t file_offset = 0;

//	unsigned char *footer = data->get_data() +
//		data->get_compressed_size() - FOOTERSIZE;
	unsigned char *header = data->get_data();
	file_offset += HEADERSIZE;

	int image_type = 0;
	int image_compression = 0;
	switch(header[2])
	{
		case 1:
			image_type = TGA_TYPE_MAPPED;
			image_compression = TGA_COMP_NONE;
			break;
		case 2:
			image_type = TGA_TYPE_COLOR;
			image_compression = TGA_COMP_NONE;
			break;
		case 3:
			image_type = TGA_TYPE_GRAY;
			image_compression = TGA_COMP_NONE;
			break;
		case 9:
			image_type = TGA_TYPE_MAPPED;
			image_compression = TGA_COMP_RLE;
			break;
		case 10:
			image_type = TGA_TYPE_COLOR;
			image_compression = TGA_COMP_RLE;
			break;
		case 11:
			image_type = TGA_TYPE_GRAY;
			image_compression = TGA_COMP_RLE;
			break;
	}

	int idlength = header[0];
	int colormaptype = header[1];
	//int colormapindex = header[3] + header[4] * 256;
	int colormaplength = header[5] + header[6] * 256;
	int colormapsize = header[7];
	//int xorigin = header[8] + header[9] * 256;
	//int yorigin = header[10] + header[11] * 256;
	int width = header[12] + header[13] * 256;
	int height = header[14] + header[15] * 256;
	int bpp = header[16];
	int bytes = (bpp + 7) / 8;
	int alphabits = header[17] & 0x0f;
	int fliphoriz = (header[17] & 0x10) ? 1 : 0;
	int flipvert = (header[17] & 0x20) ? 0 : 1;
	int data_size = data->get_compressed_size();

	if(idlength) file_offset += idlength;

// Get colormap
	unsigned char *tga_cmap;
	unsigned char colormap[4 * 256];

	if(colormaptype == 1)
	{
		int cmap_bytes = (colormapsize + 7) / 8;
		tga_cmap = data->get_data() + file_offset;
		file_offset += colormaplength * cmap_bytes;

		switch(colormapsize)
		{
			case 32:
				bgr2rgb(colormap, tga_cmap, colormaplength, cmap_bytes, 1);
				break;
			case 24:
				bgr2rgb(colormap, tga_cmap, colormaplength, cmap_bytes, 0);
				break;
			case 16:
				upsample(colormap, tga_cmap, colormaplength, cmap_bytes);
				break;
		}
	}

	int source_cmodel = BC_RGB888;
	switch(bpp)
	{
		case 32:
			source_cmodel = BC_RGBA8888;
			break;
		case 24:
			source_cmodel = BC_RGB888;
			break;
		default:
			return;
	}

// Read image
	VFrame *output_frame;
	if(frame->get_color_model() == source_cmodel)
	{
		output_frame = frame;
	}
	else
	{
		if(temp && temp->get_color_model() != source_cmodel)
		{
			delete temp;
			temp = 0;
		}

		if(!temp)
		{
			temp = new VFrame(width, height, source_cmodel, 0);
		}
		output_frame = temp;
	}

	if(flipvert)
	{
		for(int i = height - 1; i >= 0; i--)
		{
			read_line(output_frame->get_rows()[i],
				data->get_data(),
				file_offset,
				image_type,
				bpp,
				image_compression,
				bytes,
				width,
				fliphoriz,
				alphabits,
				data_size);
		}
	}
	else
	{
		for(int i = 0; i < height; i++)
		{
			read_line(output_frame->get_rows()[i],
				data->get_data(),
				file_offset,
				image_type,
				bpp,
				image_compression,
				bytes,
				width,
				fliphoriz,
				alphabits,
				data_size);
		}
	}

	if(output_frame != frame)
	{
		BC_CModels::transfer(frame->get_rows(), output_frame->get_rows(),
			frame->get_y(), frame->get_u(), frame->get_v(),
			output_frame->get_y(), output_frame->get_u(), output_frame->get_v(),
			0, 0, width, height,
			0, 0, frame->get_w(), frame->get_h(),
			output_frame->get_color_model(), frame->get_color_model(),
			0, width, frame->get_w());
	}
}

void FileTGA::write_tga(Asset *asset, VFrame *frame, VFrame *data, VFrame* &temp)
{
	unsigned char header[18];
	//unsigned char footer[26];
	int64_t file_offset = 0;
	int out_bpp = 0;
	int rle = 0;
	int dest_cmodel = BC_RGB888;

//printf("FileTGA::write_tga 1\n");

	header[0] = 0;
	header[1] = 0;
	if(!strcasecmp(asset->vcodec, TGA_RGBA_RLE))
	{
		header[2] = 10;
		out_bpp = 4;
		rle = 1;
		header[16] = 32; /* bpp */
		header[17] = 0x28; /* alpha + orientation */
		dest_cmodel = BC_RGBA8888;
	}
	else
	if(!strcasecmp(asset->vcodec, TGA_RGBA))
	{
		header[2] = 2;
		out_bpp = 4;
		rle = 0;
		header[16] = 32; /* bpp */
		header[17] = 0x28; /* alpha + orientation */
		dest_cmodel = BC_RGBA8888;
	}
	else
	if(!strcasecmp(asset->vcodec, TGA_RGB_RLE))
	{
		header[2] = 10;
		out_bpp = 3;
		rle = 1;
		header[16] = 24; /* bpp */
		header[17] = 0x20; /* alpha + orientation */
		dest_cmodel = BC_RGB888;
	}
	else
	{
		header[2] = 2;
		out_bpp = 3;
		rle = 0;
		header[16] = 24; /* bpp */
		header[17] = 0x20; /* alpha + orientation */
		dest_cmodel = BC_RGB888;
	}
	header[3] = header[4] = header[5] = header[6] = header[7] = 0;
//printf("FileTGA::write_tga 1\n");

	VFrame *input_frame;
	if(frame->get_color_model() == dest_cmodel)
	{
		input_frame = frame;
	}
	else
	{
		if(temp && temp->get_color_model() != dest_cmodel)
		{
			delete temp;
			temp = 0;
		}

		if(!temp)
		{
			temp = new VFrame(0, -1, frame->get_w(), frame->get_h(), dest_cmodel, -1);
		}
		input_frame = temp;

		BC_CModels::transfer(input_frame->get_rows(), frame->get_rows(),
			input_frame->get_y(), input_frame->get_u(), input_frame->get_v(),
			frame->get_y(), frame->get_u(), frame->get_v(),
			0, 0, frame->get_w(), frame->get_h(),
			0, 0, frame->get_w(), frame->get_h(),
			frame->get_color_model(), input_frame->get_color_model(),
			0, frame->get_w(), frame->get_w());
	}
//printf("FileTGA::write_tga 1\n");

// xorigin
// yorigin
	header[8]  = header[9]  = 0;
	header[10] = header[11] = 0;

	header[12] = input_frame->get_w() % 256;
	header[13] = input_frame->get_w() / 256;

	header[14] = input_frame->get_h() % 256;
	header[15] = input_frame->get_h() / 256;
//printf("FileTGA::write_tga 1\n");

	write_data(header, data, file_offset, sizeof(header));
//printf("FileTGA::write_tga 1\n");

	unsigned char *output = new unsigned char[out_bpp * input_frame->get_w()];
//printf("FileTGA::write_tga 1\n");
	for(int i = 0; i < input_frame->get_h(); i++)
	{
//printf("FileTGA::write_tga 2\n");
		bgr2rgb(output, input_frame->get_rows()[i], input_frame->get_w(), out_bpp, (out_bpp == 4));
//printf("FileTGA::write_tga 3\n");

		if(rle)
		{
//printf("FileTGA::write_tga 4\n");
			rle_write(output,
				input_frame->get_w(),
				out_bpp,
				data,
				file_offset);
//printf("FileTGA::write_tga 5\n");
		}
		else
		{
//printf("FileTGA::write_tga 6\n");
			write_data(output,
				data,
				file_offset,
				input_frame->get_w() * out_bpp);
//printf("FileTGA::write_tga 7\n");
		}
	}
//printf("FileTGA::write_tga 8\n");
	delete [] output;
//printf("FileTGA::write_tga 9\n");
}

void FileTGA::write_data(unsigned char *buffer,
	VFrame *data,
	int64_t &file_offset,
	int64_t len)
{
//printf("FileTGA::write_data 1 %d\n", len);
	if(data->get_compressed_allocated() <= data->get_compressed_size() + len)
	{
		data->allocate_compressed_data((data->get_compressed_size() + len) * 2);
	}
//printf("FileTGA::write_data 1 %d\n", len);

	bcopy(buffer, data->get_data() + file_offset, len);
//printf("FileTGA::write_data 1 %d\n", len);
	file_offset += len;
//printf("FileTGA::write_data 1 %d\n", len);
	data->set_compressed_size(file_offset);
//printf("FileTGA::write_data 2 %d\n", len);
}

void FileTGA::read_line(unsigned char *row, unsigned char *data,
	int64_t &file_offset, int image_type, int bpp, int image_compression,
	int bytes, int width, int fliphoriz, int alphabits, int data_size)
{
	if(file_offset >= data_size) return;
	if(image_compression == TGA_COMP_RLE)
	{
		rle_read(row, data, file_offset, bytes, width);
	}
	else
	{
		if(file_offset + bytes * width <= data_size)
			bcopy(data + file_offset, row, bytes * width);
		file_offset += bytes * width;
	}

	if(fliphoriz)
	{
		flip_line(row, bytes, width);
	}

	if(image_type == TGA_TYPE_COLOR)
	{
		if(bpp == 16)
		{
			upsample(row, row, width, bytes);
		}
		else
		{
			bgr2rgb(row, row, width, bytes, alphabits);
		}
	}
	else
	{
		;
	}
}

void FileTGA::flip_line(unsigned char *row, int bytes, int width)
{
	unsigned char temp;
	unsigned char *alt;
	int x, s;
	alt = row + (bytes * (width - 1));

	for (x = 0; x * 2 <= width; x++) {
		for(s = 0; s < bytes; ++s) {
			temp = row[s];
			row[s] = alt[s];
			alt[s] = temp;
		}

		row += bytes;
		alt -= bytes;
	}
}

void FileTGA::rle_read(unsigned char *row,
	unsigned char *data,
	int64_t &file_offset,
	int bytes,
	int width)
{
	int repeat = 0;
	int direct = 0;
	unsigned char sample[4];
	int head;

	for(int x = 0; x < width; x++)
	{
		if(repeat == 0 && direct == 0)
		{
			head = data[file_offset++];
			if(head == EOF)
			{
				return;
			}
			else
			if(head >= 128)
			{
				repeat = head - 127;
				bcopy(data + file_offset, sample, bytes);
				file_offset += bytes;
			}
			else
			{
				direct = head + 1;
			}
		}

		if(repeat > 0)
		{
			for(int k = 0; k < bytes; k++)
			{
				row[k] = sample[k];
			}

			repeat--;
		}
		else
		{
			bcopy(data + file_offset, row, bytes);
			file_offset += bytes;

			direct--;
		}

		row += bytes;
	}
}

void FileTGA::rle_write(unsigned char *buffer,
	int width,
	int bytes,
	VFrame *frame,
	int64_t &file_offset)
{
	int repeat = 0;
	int direct = 0;
	unsigned char *from = buffer;
	unsigned char output;
	int x;

	for(x = 1; x < width; ++x)
	{
/* next pixel is different */
		if(memcmp(buffer, buffer + bytes, bytes))
		{
			if(repeat)
			{
				output = 128 + repeat;
				write_data(&output, frame, file_offset, 1);
				write_data(from, frame, file_offset, bytes);
				from = buffer + bytes;
				repeat = 0;
				direct = 0;
			}
			else
			{
				direct++;
			}
		}
		else
/* next pixel is the same */
		{
			if(direct)
			{
				output = direct - 1;
				write_data(&output, frame, file_offset, 1);
				write_data(from, frame, file_offset, bytes * direct);
				from = buffer;
				direct = 0;
				repeat = 1;
			}
			else
			{
				repeat++;
			}
		}

		if(repeat == 128)
		{
			output = 255;
			write_data(&output, frame, file_offset, 1);
			write_data(from, frame, file_offset, bytes);
			from = buffer + bytes;
			direct = 0;
			repeat = 0;
		}
		else
		if(direct == 128)
		{
			output = 127;
			write_data(&output, frame, file_offset, 1);
			write_data(from, frame, file_offset, direct * bytes);
			from = buffer + bytes;
			direct = 0;
			repeat = 0;
		}

		buffer += bytes;
	}

	if(repeat > 0)
	{
		output = 128 + repeat;
		write_data(&output, frame, file_offset, 1);
		write_data(from, frame, file_offset, bytes);
	}
	else
	{
		output = direct;
		write_data(&output, frame, file_offset, 1);
		write_data(from, frame, file_offset, bytes * (direct + 1));
	}
}


void FileTGA::bgr2rgb(unsigned char *dest, unsigned char *src,
		 int width, int bytes, int alpha)
{
	int x;
	unsigned char r, g, b;

	if(alpha) {
		for(x = 0; x < width; x++) {
			r = src[2]; g = src[1]; b = src[0];
			*(dest++) = r; *(dest++) = g; *(dest++) = b;
			*(dest++) = src[3]; src += bytes;
		}
	}
	else {
		for(x = 0; x < width; x++) {
			r = src[2]; g = src[1]; b = src[0];
			*(dest++) = r; *(dest++) = g; *(dest++) = b;
			src += bytes;
		}
	}
}

void FileTGA::upsample(unsigned char *dest,
	  unsigned char *src,
	  int width,
	  int bytes)
{
	int x;

	dest += (width - 1) * 3;
	src += (width - 1) * bytes;
	for(x = width - 1; x >= 0; x--) {
		dest[0] =  ((src[1] << 1) & 0xf8);
		dest[0] += (dest[0] >> 5);

		dest[1] =  ((src[0] & 0xe0) >> 2) + ((src[1] & 0x03) << 6);
		dest[1] += (dest[1] >> 5);

		dest[2] =  ((src[0] << 3) & 0xf8);
		dest[2] += (dest[2] >> 5);

		dest -= 3;
		src -= bytes;
	}
}


TGAUnit::TGAUnit(FileTGA *file, FrameWriter *writer)
 : FrameWriterUnit(writer)
{
	temp = 0;
	this->file = file;
}

TGAUnit::~TGAUnit()
{
	if(temp) delete temp;
}


TGAConfigVideo::TGAConfigVideo(BC_WindowBase *gui, Asset *asset)
 : BC_Window(_(PROGRAM_NAME ": Video Compression"),
	gui->get_abs_cursor_x(1), gui->get_abs_cursor_y(1),
	xS(400), yS(100))
{
	this->gui = gui;
	this->asset = asset;

	compression_items.append(new BC_ListBoxItem(FileTGA::compression_to_str(TGA_RGB_RLE)));
	compression_items.append(new BC_ListBoxItem(FileTGA::compression_to_str(TGA_RGBA_RLE)));
	compression_items.append(new BC_ListBoxItem(FileTGA::compression_to_str(TGA_RGB)));
	compression_items.append(new BC_ListBoxItem(FileTGA::compression_to_str(TGA_RGBA)));
// *** CONTEXT_HELP ***
	context_help_set_keyword("Single File Rendering");
}

TGAConfigVideo::~TGAConfigVideo()
{
	compression_items.remove_all_objects();
}

void TGAConfigVideo::create_objects()
{
	lock_window("TGAConfigVideo::create_objects");
	int x = xS(10), y = yS(10);

	add_subwindow(new BC_Title(x, y, _("Compression:")));
	TGACompression *textbox = new TGACompression(this,
			x + xS(110), y, asset, &compression_items);
	textbox->create_objects();
	add_subwindow(new BC_OKButton(this));
	show_window(1);
	unlock_window();
}

int TGAConfigVideo::close_event()
{
	set_done(0);
	return 1;
}


TGACompression::TGACompression(TGAConfigVideo *gui, int x, int y,
	Asset *asset, ArrayList<BC_ListBoxItem*> *compression_items)
 : BC_PopupTextBox(gui, compression_items,
	FileTGA::compression_to_str(gui->asset->vcodec),
	x, y, xS(200), yS(200))
{
	this->asset = asset;
}
int TGACompression::handle_event()
{
	strcpy(asset->vcodec, FileTGA::str_to_compression(get_text()));
	return 1;
}
