// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// 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

/** \file
		\author Anders Dahnielson (anders@dahnielson.com)
*/

#include "bitmap_functions.h"

#include <k3dsdk/bitmap.h>
#include <k3dsdk/frames.h>
#include <k3dsdk/file_filter.h>
#include <k3dsdk/ianimation_render_engine.h>
#include <k3dsdk/ibitmap_sink.h>
#include <k3dsdk/ibitmap_write_format.h>
#include <k3dsdk/irender_farm.h>
#include <k3dsdk/istill_render_engine.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/module.h>
#include <k3dsdk/object.h>
#include <k3dsdk/path_data.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/property.h>
#include <k3dsdk/time_source.h>

#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/path.hpp>

#include <fstream>
#include <iomanip>
#include <iterator>

namespace libk3dbitmap
{

/////////////////////////////////////////////////////////////////////////////
// bitmap_sequence_out_implementation

class bitmap_sequence_out_implementation :
	public k3d::persistent<k3d::object>,
	public k3d::ianimation_render_engine,
	public k3d::istill_render_engine,
	public k3d::ibitmap_sink
{
	typedef k3d::persistent<k3d::object> base;

public:
	bitmap_sequence_out_implementation(k3d::idocument& Document) :
		base(Document),
		m_input(k3d::init_name("input") + k3d::init_description("Input bitmap [bitmap]") + k3d::init_value<k3d::bitmap*>(0) + k3d::init_document(Document)),
		m_width(k3d::init_name("pixel_width") + k3d::init_description("Output pixel width [positive integer]") + k3d::init_value(320) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_height(k3d::init_name("pixel_height") + k3d::init_description("Output pixel height [positive integer]") + k3d::init_value(240) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar)))
	{
		enable_serialization(k3d::persistence::proxy(m_width));
		enable_serialization(k3d::persistence::proxy(m_height));
		
		register_property(m_input);
		register_property(m_width);
		register_property(m_height);
	}

	k3d::iproperty& bitmap_sink_input()
	{
		return m_input;
	}

	bool render_preview()
	{
		// Start a new render job ...
		k3d::irender_job& job = k3d::application().render_farm().create_job("k3d-preview");

		// Add a single render frame to the job ...
		k3d::irender_frame& frame = job.create_frame("frame");

		// Create an output image path ...
		const boost::filesystem::path outputimagepath = frame.add_output_file("outputimage.tiff");
		return_val_if_fail(!outputimagepath.empty(), false);

		// Look up file format ...
		k3d::ibitmap_write_format* const format = k3d::auto_file_filter<k3d::ibitmap_write_format>(outputimagepath);
		return_val_if_fail(format, false);

		// View the output image when it's done ...
		frame.add_view_operation(outputimagepath);

		// Render it (visible rendering) ...
		return_val_if_fail(render(format, frame, outputimagepath, true), false);

		// Start the job running ...
		k3d::application().render_farm().start_job(job);

		return true;
	}

	bool render_frame(const boost::filesystem::path& OutputImage, const bool ViewImage)
	{
		// Sanity checks ...
		return_val_if_fail(!OutputImage.empty(), false);

		// Start a new render job ...
		k3d::irender_job& job = k3d::application().render_farm().create_job("k3d-render-frame");

		// Add a single render frame to the job ...
		k3d::irender_frame& frame = job.create_frame("frame");

		// Look up file format ...
		k3d::ibitmap_write_format* const format = k3d::auto_file_filter<k3d::ibitmap_write_format>(OutputImage);
		return_val_if_fail(format, false);

		// View the output image when it's done ...
		if(ViewImage)
			frame.add_view_operation(OutputImage);

		// Render it (hidden rendering) ...
		return_val_if_fail(render(format, frame, OutputImage, false), false);

		// Start the job running ...
		k3d::application().render_farm().start_job(job);

		return true;
	}

	bool render_animation(const boost::filesystem::path& OutputImages, const bool ViewCompletedImages)
	{
		// Sanity checks ...
		return_val_if_fail(!OutputImages.empty(), false);

		// Ensure that the document has animation capabilities, first ...
		k3d::iproperty* const start_time_property = k3d::get_start_time(document());
		k3d::iproperty* const end_time_property = k3d::get_end_time(document());
		k3d::iproperty* const frame_rate_property = k3d::get_frame_rate(document());
		k3d::iwritable_property* const time_property = dynamic_cast<k3d::iwritable_property*>(k3d::get_time(document()));
		return_val_if_fail(start_time_property && end_time_property && frame_rate_property && time_property, false);

		// Test the output images filepath to make sure it can hold all the frames we're going to generate ...
		const double start_time = boost::any_cast<double>(k3d::get_property_value(document().dag(), *start_time_property));
		const double end_time = boost::any_cast<double>(k3d::get_property_value(document().dag(), *end_time_property));
		const double frame_rate = boost::any_cast<double>(k3d::get_property_value(document().dag(), *frame_rate_property));
	
		const long start_frame = static_cast<long>(k3d::round(frame_rate * start_time));
		const long end_frame = static_cast<long>(k3d::round(frame_rate * end_time));
	
		k3d::frames frames(OutputImages, start_frame, end_frame);
		return_val_if_fail(frames.max_frame() >= end_frame, false);

		// Start a new render job ...
		k3d::irender_job& job = k3d::application().render_farm().create_job("k3d-render-animation");

		// For each frame to be rendered ...
		for(long view_frame = start_frame; view_frame < end_frame; ++view_frame)
		{
			// Set the frame time ...
			time_property->set_value(view_frame / frame_rate);
			
			// Add a render frame to the job ...
			std::stringstream buffer;
			buffer << "frame-" << std::setw(frames.frame_digits()) << std::setfill('0') << view_frame;
			k3d::irender_frame& frame = job.create_frame(buffer.str());

			// Create an output image path ...
			boost::filesystem::path destination;
			frames.frame(view_frame, destination);
			
			// Look up file format ...
			k3d::ibitmap_write_format* const format = k3d::auto_file_filter<k3d::ibitmap_write_format>(destination);
			return_val_if_fail(format, false);

			// View the output image when it's done ...
			if(ViewCompletedImages)
				frame.add_view_operation(destination);
			
			// Render it (hidden rendering) ...
			return_val_if_fail(render(format, frame, destination, false), false);
		}
		
		// Start the job running ...
		k3d::application().render_farm().start_job(job);

		return true;
	}

	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<bitmap_sequence_out_implementation>,
			k3d::interface_list<k3d::ibitmap_sink> > factory(
				k3d::uuid(0x38c86011, 0x7ff04d55, 0xa9401958, 0x63612b6c),
				"BitmapSequenceOut",
				"Save sequence of bitmaps to the filesystem",
				"Objects",
				k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(k3d::bitmap*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_width;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_height;

	bool render(k3d::ibitmap_write_format* const FileFormat, k3d::irender_frame& Frame, const boost::filesystem::path& OutputImagePath, const bool VisibleRender)
	{
		// Sanity checks ...
		return_val_if_fail(!OutputImagePath.empty(), false);
		return_val_if_fail(FileFormat, false);

		// Get the input bitmap ...
		k3d::bitmap* const input = m_input.property_value();
		if (!input)
			return false;

		// Cache properties ...
		const k3d::pixel_size_t width = m_width.property_value();
		const k3d::pixel_size_t height = m_height.property_value();

		// Crop and pad bitmap ...
		const long width_diff = width - input->width();
		const long height_diff = height - input->height();

 		k3d::bitmap* output = new k3d::bitmap(*input);
		k3d::pixel_size_t left, right, top, bottom;

 		left = right = width_diff > 0 ? (width_diff/2) : 0;
 		top = bottom = height_diff > 0 ? (height_diff/2) : 0;
		if (left != 0 || right != 0 || top != 0 || bottom != 0)
		{
			k3d::bitmap* const output_padded = new k3d::bitmap(left + output->width() + right, top + output->height() + bottom);
			bitmap_padding(*output, *output_padded, left, right, top, bottom);
			output = output_padded;
		}

 		left = right = width_diff < 0 ? std::abs(width_diff/2) : 0;
 		top = bottom = height_diff < 0 ? std::abs(height_diff/2) : 0;
		if (left != 0 || right != 0 || top != 0 || bottom != 0)
		{
			k3d::bitmap* const output_cropped = new k3d::bitmap(output->width() - left - right, output->height() - top - bottom);
			bitmap_croping(*output, *output_cropped, left, right, top, bottom);
			output = output_cropped;
		}

		// Save bitmap ...
		return_val_if_fail(FileFormat->write_file(OutputImagePath, *output), false);

		return true;
	}
};

/////////////////////////////////////////////////////////////////////////////
// bitmap_sequence_out_factory

k3d::iplugin_factory& bitmap_sequence_out_factory()
{
	return bitmap_sequence_out_implementation::get_factory();
}

} // namespace libk3dbitmap

