
/*
 * 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
 *
 */

#ifdef HAVE_OPENEXR

#include "asset.h"
#include "bcsignals.h"
#include "clip.h"
#include "file.h"
#include "fileexr.h"
#include "filesystem.h"
#include "interlacemodes.h"

#include "mwindow.inc"
#include "vframe.h"

#include "ImfChannelList.h"
#include "ImfChromaticities.h"
#include "ImfCompression.h"
#include "ImfIO.h"
#include "ImfInputFile.h"
#include "ImfOutputFile.h"
#include "ImfPixelType.h"
#include "ImfRgbaFile.h"
#include "ImfRgbaYca.h"
#include "ImfVersion.h"

class EXRIStream : public Imf::IStream
{
public:
	EXRIStream(char *data, int size);
	~EXRIStream();

	bool read (char c[], int n);
	uint64_t tellg ();
	void seekg (uint64_t pos);
	void clear ();

private:
	char *data;
	int size;
	int position;
};

class EXROStream : public Imf::OStream
{
public:
	EXROStream(VFrame *data);
	~EXROStream();

    virtual void write(const char c[], int n);
    virtual uint64_t tellp();
    virtual void seekp(uint64_t pos);

private:
	VFrame *data;
	int position;
};



EXRIStream::EXRIStream(char *data, int size)
 : Imf::IStream("mypath")
{
	this->data = data;
	this->size = size;
	position = 0;
}

EXRIStream::~EXRIStream()
{
}

bool EXRIStream::read(char c[], int n)
{
	int fragment = n;
	if(position + fragment > size)
	{
		fragment = size - position;
	}
	memcpy(c, data + position, fragment);
	position += fragment;

	if(n != fragment)
	{
		throw Iex::InputExc ("EXRIStream::read: Unexpected end of file.");
	}
	return position >= size;
}

uint64_t EXRIStream::tellg ()
{
	return position;
}

void EXRIStream::seekg(uint64_t pos)
{
	position = pos;
}

void EXRIStream::clear()
{
}


EXROStream::EXROStream(VFrame *data)
 : Imf::OStream("mypath")
{
	this->data = data;
	position = 0;
}
EXROStream::~EXROStream()
{
}

void EXROStream::write(const char c[], int n)
{
	if(position + n > data->get_compressed_allocated())
		data->allocate_compressed_data(MAX(position + n, data->get_compressed_allocated() * 2));

	memcpy(data->get_data() + position, c, n);
	position += n;
	data->set_compressed_size(MAX(position, data->get_compressed_size()));
}

uint64_t EXROStream::tellp()
{
	return position;
}

void EXROStream::seekp(uint64_t pos)
{
	position = pos;
}


FileEXR::FileEXR(Asset *asset, File *file)
 : FileList(asset, file, "EXRLIST", ".exr", FILE_EXR, FILE_EXR_LIST)
{
	native_cmodel = BC_RGB_FLOAT;
	is_yuv = 0;
	temp_y = 0;
	temp_u = 0;
	temp_v = 0;
}

FileEXR::~FileEXR()
{
	delete [] temp_y;
	delete [] temp_u;
	delete [] temp_v;
}

const char* FileEXR::compression_to_str(int compression)
{
	switch( compression ) {
	case NONE:	break;
	case PIZ:	return "PIZ";
	case ZIP:	return "ZIP";
	case ZIPS:	return "ZIPS";
	case RLE:	return "RLE";
	case PXR24:	return "PXR24";
	case B44:	return "B44";
	case B44A:	return "B44A";
	case DWAB:	return "DWAB";
	case DWAA:	return "DWAA";
	}
	return _("None");
}

int FileEXR::compression_to_exr(int compression)
{
	switch( compression ) {
	case NONE:	return Imf::NO_COMPRESSION;
	case PIZ:	return Imf::PIZ_COMPRESSION;
	case ZIP:	return Imf::ZIP_COMPRESSION;
	case ZIPS:	return Imf::ZIPS_COMPRESSION;
	case RLE:	return Imf::RLE_COMPRESSION;
	case PXR24:	return Imf::PXR24_COMPRESSION;
	case B44:	return Imf::B44_COMPRESSION;
	case B44A:	return Imf::B44A_COMPRESSION;
	case DWAA:	return Imf::DWAA_COMPRESSION;
	case DWAB:	return Imf::DWAB_COMPRESSION;
	}
	return Imf::NO_COMPRESSION;
}

int FileEXR::str_to_compression(char *string)
{
	if( !strcmp(compression_to_str(NONE), string) ) return NONE;
	if( !strcmp(compression_to_str(PIZ), string)  ) return PIZ;
	if( !strcmp(compression_to_str(ZIP), string)  ) return ZIP;
	if( !strcmp(compression_to_str(ZIPS), string) ) return ZIPS;
	if( !strcmp(compression_to_str(RLE), string)  ) return RLE;
	if( !strcmp(compression_to_str(RLE), string)  ) return B44;
	if( !strcmp(compression_to_str(RLE), string)  ) return B44A;
	if( !strcmp(compression_to_str(RLE), string)  ) return DWAA;
	if( !strcmp(compression_to_str(RLE), string)  ) return DWAB;
	if( !strcmp(compression_to_str(PXR24),string) ) return PXR24;
	return NONE;
}

int FileEXR::check_sig(Asset *asset, char *test)
{
	if(Imf::isImfMagic(test)) return 1;
	if(test[0] == 'E' && test[1] == 'X' && test[2] == 'R' &&
		test[3] == 'L' && test[4] == 'I' && test[5] == 'S' && test[6] == 'T')
	{
		return 1;
	}

	return 0;
}

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

int FileEXR::colormodel_supported(int colormodel)
{
	return native_cmodel;
}

int FileEXR::get_best_colormodel(Asset *asset, int driver)
{
	return asset->exr_use_alpha ? BC_RGBA_FLOAT : BC_RGB_FLOAT;
}

int64_t FileEXR::get_memory_usage()
{
	int64_t result = FileList::get_memory_usage();
	if(temp_y) result += (int64_t)asset->width * asset->height * 3 / 2;
	return result;
}


int FileEXR::read_frame_header(char *path)
{
	int result = 0;

// This may have been used by VFS
// 	FILE *stream;
//
// 	if(!(stream = fopen(path, "rb")))
// 	{
// 		perror("FileEXR::read_frame_header");
// 		return 1;
// 	}
// 	int size = FileSystem::get_size(path);
// 	char *buffer = new char[size];
// 	fread(buffer, size, 1, stream);
// 	fclose(stream);
//
// 	EXRIStream exr_stream(buffer, size);
// 	Imf::InputFile file(exr_stream);

	if( access(path, R_OK) ) return 1;
	Imf::InputFile file(path);

	Imath::Box2i dw = file.header().dataWindow();

	asset->width = dw.max.x - dw.min.x + 1;
	asset->height = dw.max.y - dw.min.y + 1;
	asset->interlace_mode = ILACE_MODE_NOTINTERLACED;

	const Imf::ChannelList &channels = file.header().channels();

	if(channels.findChannel("A"))
		native_cmodel = BC_RGBA_FLOAT;
	else
		native_cmodel = BC_RGB_FLOAT;
	asset->exr_use_alpha = BC_CModels::has_alpha(native_cmodel) ? 1 : 0;

	if(channels.findChannel("Y"))
		is_yuv = 1;
// for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i)
// {
// printf("%s\n", i.name());
// }

//	delete [] buffer;
	return result;
}

int FileEXR::read_frame(VFrame *frame, VFrame *data)
{

    try {

	Imf::setGlobalThreadCount(file->cpus);
	EXRIStream exr_stream((char*)data->get_data(), data->get_compressed_size());
	Imf::InputFile file(exr_stream);
	Imath::Box2i dw = file.header().dataWindow();
	int dx = dw.min.x, dy = dw.min.y;
	Imf::FrameBuffer framebuffer;
	float **rows = (float**)frame->get_rows();
	int components = BC_CModels::components(frame->get_color_model());

	if( is_yuv ) {
		if( !temp_y ) temp_y = new float[asset->width * asset->height];
		if( !temp_u ) temp_u = new float[asset->width * asset->height / 4];
		if( !temp_v ) temp_v = new float[asset->width * asset->height / 4];
		framebuffer.insert("Y", Imf::Slice(Imf::FLOAT,
			(char*)(temp_y - dy * asset->width - dx),
			sizeof(float),
			sizeof(float) * frame->get_w()));
		framebuffer.insert("BY", Imf::Slice(Imf::FLOAT,
			(char*)(temp_u - dy * asset->width / 4 - dx / 2),
			sizeof(float),
			sizeof(float) * frame->get_w() / 2,
			2,
			2));
		framebuffer.insert("RY", Imf::Slice(Imf::FLOAT,
			(char*)(temp_v - dy * asset->width / 4 - dx / 2),
			sizeof(float),
			sizeof(float) * frame->get_w() / 2,
			2,
			2));
	}
	else {
		framebuffer.insert("R", Imf::Slice(Imf::FLOAT,
			(char*)(&rows[-dy][-dx * components]),
			sizeof(float) * components,
			sizeof(float) * components * frame->get_w()));
		framebuffer.insert("G", Imf::Slice(Imf::FLOAT,
			(char*)(&rows[-dy][-dx * components + 1]),
			sizeof(float) * components,
			sizeof(float) * components * frame->get_w()));
		framebuffer.insert("B", Imf::Slice(Imf::FLOAT,
			(char*)(&rows[-dy][-dx * components + 2]),
			sizeof(float) * components,
			sizeof(float) * components * frame->get_w()));
	}

// Alpha always goes directly to the output frame
	if( components == 4 ) {
		framebuffer.insert("A", Imf::Slice(Imf::FLOAT,
			(char*)(&rows[-dy][-dx * components + 3]),
			sizeof(float) * components,
			sizeof(float) * components * frame->get_w()));
	}

	file.setFrameBuffer(framebuffer);
	file.readPixels (dw.min.y, dw.max.y);

	if( is_yuv ) {
// Convert to RGB using crazy ILM equations
		Imath::V3f yw;
		Imf::Chromaticities cr;
		yw = Imf::RgbaYca::computeYw(cr);

		for( int i=0; i<asset->height-1; i+=2 ) {
			float *y_row1 = temp_y + i * asset->width;
			float *y_row2 = temp_y + (i + 1) * asset->width;
			float *u_row = temp_u + (i * asset->width / 4);
			float *v_row = temp_v + (i * asset->width / 4);
			float *out_row1 = rows[i];
			float *out_row2 = rows[i + 1];
			for( int j=0; j<asset->width-1; j+=2 ) {
				float v = *u_row++;
				float u = *v_row++;
				float y;

				float r, g, b;
				y = *y_row1++;
				r = (u + 1) * y;
				b = (v + 1) * y;
				g = (y - r * yw.x - b * yw.z) / yw.y;
				*out_row1++ = r;
				*out_row1++ = g;
				*out_row1++ = b;
				if(components == 4) out_row1++;

				y = *y_row1++;
				r = (u + 1) * y;
				b = (v + 1) * y;
				g = (y - r * yw.x - b * yw.z) / yw.y;
				*out_row1++ = r;
				*out_row1++ = g;
				*out_row1++ = b;
				if(components == 4) out_row1++;

				y = *y_row2++;
				r = (u + 1) * y;
				b = (v + 1) * y;
				g = (y - r * yw.x - b * yw.z) / yw.y;
				*out_row2++ = r;
				*out_row2++ = g;
				*out_row2++ = b;
				if(components == 4) out_row1++;

				y = *y_row2++;
				r = (u + 1) * y;
				b = (v + 1) * y;
				g = (y - r * yw.x - b * yw.z) / yw.y;
				*out_row2++ = r;
				*out_row2++ = g;
				*out_row2++ = b;
				if(components == 4) out_row1++;
			}
		}
	}
	return 0;
    } catch (const std::exception &e) {
    std::cerr << "error reading EXR image file:" << e.what() << std::endl;
    return 1;
    }
}


int FileEXR::write_frame(VFrame *frame, VFrame *data, FrameWriterUnit *unit)
{
	EXRUnit *exr_unit = (EXRUnit*)unit;

	VFrame *output_frame;
	data->set_compressed_size(0);
	Imf::setGlobalThreadCount(file->cpus);

	int native_cmodel = asset->exr_use_alpha ? BC_RGBA_FLOAT : BC_RGB_FLOAT;
	int components = BC_CModels::components(native_cmodel);

	if( frame->get_color_model() != native_cmodel ) {
		if( !exr_unit->temp_frame ) exr_unit->temp_frame =
			new VFrame(asset->width, asset->height, native_cmodel, 0);
		BC_CModels::transfer(exr_unit->temp_frame->get_rows(), /* Leave NULL if non existent */
			frame->get_rows(),
			exr_unit->temp_frame->get_y(), /* Leave NULL if non existent */
			exr_unit->temp_frame->get_u(),
			exr_unit->temp_frame->get_v(),
			frame->get_y(), /* Leave NULL if non existent */
			frame->get_u(),
			frame->get_v(),
			0,        /* Dimensions to capture from input frame */
			0,
			asset->width,
			asset->height,
			0,       /* Dimensions to project on output frame */
			0,
			asset->width,
			asset->height,
			frame->get_color_model(),
			native_cmodel,
			0,         /* When transfering BC_RGBA8888 to non-alpha this is the background color in 0xRRGGBB hex */
			asset->width,       /* For planar use the luma rowspan */
			asset->height);
		output_frame = exr_unit->temp_frame;
	}
	else
		output_frame = frame;

	Imf::Header header(output_frame->get_w(), output_frame->get_h());
	header.compression() = (Imf::Compression)compression_to_exr(
		asset->exr_compression);
	header.channels().insert("R", Imf::Channel(Imf::FLOAT));
	header.channels().insert("G", Imf::Channel(Imf::FLOAT));
	header.channels().insert("B", Imf::Channel(Imf::FLOAT));
	if(asset->exr_use_alpha) header.channels().insert("A", Imf::Channel(Imf::FLOAT));

	EXROStream exr_stream(data);
	Imf::OutputFile file(exr_stream, header);
	Imf::FrameBuffer framebuffer;
	float **rows = (float**)output_frame->get_rows();
	framebuffer.insert("R",
		Imf::Slice(Imf::FLOAT,
			(char*)(rows[0]),
			sizeof(float) * components,
			sizeof(float) * components * output_frame->get_w()));
	framebuffer.insert("G",
		Imf::Slice(Imf::FLOAT,
			(char*)(rows[0] + 1),
			sizeof(float) * components,
			sizeof(float) * components * output_frame->get_w()));
	framebuffer.insert("B",
		Imf::Slice(Imf::FLOAT,
			(char*)(rows[0] + 2),
			sizeof(float) * components,
			sizeof(float) * components * output_frame->get_w()));
	if(asset->exr_use_alpha)
		framebuffer.insert("A",
			Imf::Slice(Imf::FLOAT,
				(char*)(rows[0] + 3),
				sizeof(float) * components,
				sizeof(float) * components * output_frame->get_w()));
	file.setFrameBuffer(framebuffer);
	file.writePixels(asset->height);
	return 0;
}

int FileEXR::can_copy_from(Asset *asset, int64_t position)
{
	if(asset->format == FILE_EXR ||
		asset->format == FILE_EXR_LIST)
		return 1;

	return 0;
}

FrameWriterUnit* FileEXR::new_writer_unit(FrameWriter *writer)
{
	return new EXRUnit(this, writer);
}


EXRUnit::EXRUnit(FileEXR *file, FrameWriter *writer)
 : FrameWriterUnit(writer)
{
	this->file = file;
	temp_frame = 0;
}

EXRUnit::~EXRUnit()
{
	if(temp_frame) delete temp_frame;
}


EXRConfigVideo::EXRConfigVideo(BC_WindowBase *parent_window, Asset *asset)
 : BC_Window(_(PROGRAM_NAME ": Video Compression"),
	parent_window->get_abs_cursor_x(1), parent_window->get_abs_cursor_y(1),
	xS(300), BC_OKButton::calculate_h() + yS(100))
{
	this->parent_window = parent_window;
	this->asset = asset;
// *** CONTEXT_HELP ***
	context_help_set_keyword("Single File Rendering");
}

EXRConfigVideo::~EXRConfigVideo()
{
}

void EXRConfigVideo::create_objects()
{
	lock_window("EXRConfigVideo::create_objects");
	int x = xS(10), y = yS(10);
	add_subwindow(new EXRUseAlpha(this, x, y));
	y += yS(30);
	EXRCompression *menu;
	add_subwindow(new BC_Title(x, y, _("Compression:")));
	x += xS(110);
	add_subwindow(menu = new EXRCompression(this, x, y, xS(100)));
	menu->create_objects();
	add_subwindow(new BC_OKButton(this));
	show_window(1);
	unlock_window();
}

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


EXRUseAlpha::EXRUseAlpha(EXRConfigVideo *gui, int x, int y)
 : BC_CheckBox(x, y, gui->asset->exr_use_alpha, _("Use alpha"))
{
	this->gui = gui;
}

int EXRUseAlpha::handle_event()
{
	gui->asset->exr_use_alpha = get_value();
	return 1;
}



EXRCompression::EXRCompression(EXRConfigVideo *gui, int x, int y, int w)
 : BC_PopupMenu(x, y, w,
	FileEXR::compression_to_str(gui->asset->exr_compression))
{
	this->gui = gui;
}
void EXRCompression::create_objects()
{
	add_item(new EXRCompressionItem(gui, FileEXR::NONE));
	add_item(new EXRCompressionItem(gui, FileEXR::PIZ));
	add_item(new EXRCompressionItem(gui, FileEXR::ZIP));
	add_item(new EXRCompressionItem(gui, FileEXR::ZIPS));
	add_item(new EXRCompressionItem(gui, FileEXR::RLE));
	add_item(new EXRCompressionItem(gui, FileEXR::PXR24));
	add_item(new EXRCompressionItem(gui, FileEXR::B44));
	add_item(new EXRCompressionItem(gui, FileEXR::B44A));
	add_item(new EXRCompressionItem(gui, FileEXR::DWAA));
	add_item(new EXRCompressionItem(gui, FileEXR::DWAB));
}

int EXRCompression::handle_event()
{
	return 1;
}

EXRCompressionItem::EXRCompressionItem(EXRConfigVideo *gui, int value)
 : BC_MenuItem(FileEXR::compression_to_str(value))
{
	this->gui = gui;
	this->value = value;
}

int EXRCompressionItem::handle_event()
{
	gui->asset->exr_compression = value;
	return 0;
}

#endif
