// 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 <k3dsdk/ibitmap_sink.h>
#include <k3dsdk/ibitmap_source.h>
#include <k3dsdk/bitmap.h>
#include <k3dsdk/object.h>
#include <k3dsdk/property_collection.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>

namespace libk3dbitmap
{

/////////////////////////////////////////////////////////////////////////////
// bitmap_matte_keymix_implementation

class bitmap_matte_keymix_implementation :
	public k3d::persistent<k3d::object>,
	public k3d::ibitmap_source,
	public k3d::ibitmap_sink
{
	typedef k3d::persistent<k3d::object> base;

public:
	bitmap_matte_keymix_implementation(k3d::idocument& Document) :
		base(Document),
		m_input_a(k3d::init_name("input_a") + k3d::init_description("A input bitmap [bitmap]") + k3d::init_value<k3d::bitmap*>(0) + k3d::init_document(Document)),
		m_input_b(k3d::init_name("input_b") + k3d::init_description("B input bitmap [bitmap]") + k3d::init_value<k3d::bitmap*>(0) + k3d::init_document(Document)),
		m_input_c(k3d::init_name("input_c") + k3d::init_description("C input bitmap [bitmap]") + k3d::init_value<k3d::bitmap*>(0) + k3d::init_document(Document)),
		m_output(k3d::init_name("output") + k3d::init_description("Output bitmap [bitmap]") + k3d::init_document(Document)),
		m_value(k3d::init_name("value") + k3d::init_description("Threshold value [double]") + k3d::init_value(0.0) + k3d::init_document(Document))
	{
		register_property(m_input_a);
		register_property(m_input_b);
		register_property(m_input_c);
		register_property(m_output);
		register_property(m_value);

		m_input_a.changed_signal().connect(SigC::slot(*this, &bitmap_matte_keymix_implementation::on_reset_bitmap));
		m_input_b.changed_signal().connect(SigC::slot(*this, &bitmap_matte_keymix_implementation::on_reset_bitmap));
		m_input_c.changed_signal().connect(SigC::slot(*this, &bitmap_matte_keymix_implementation::on_reset_bitmap));
		m_output.need_data_signal().connect(SigC::slot(*this, &bitmap_matte_keymix_implementation::on_create_bitmap));
		m_value.changed_signal().connect(SigC::slot(*this, &bitmap_matte_keymix_implementation::on_reset_bitmap));
	}

	k3d::iproperty& bitmap_source_output()
	{
		return m_output;
	}
	
	k3d::iproperty& bitmap_sink_input()
	{
		return m_input_a;
	}
	
	void on_reset_bitmap()
	{
		m_output.reset();
	}

	k3d::bitmap* on_create_bitmap()
	{
		// If we don't have any input bitmap, we're done ...
		k3d::bitmap* const A_bitmap = m_input_a.property_value();
		k3d::bitmap* const B_bitmap = m_input_b.property_value();
		k3d::bitmap* const C_bitmap = m_input_c.property_value();
		if(!A_bitmap && !B_bitmap)
			return 0;
		else if (!B_bitmap)
			return new k3d::bitmap(*A_bitmap);
		else if (!A_bitmap)
			return new k3d::bitmap(*B_bitmap);

		// Find the output bounding box
		k3d::bounding_box O_daw;
		O_daw.insert(A_bitmap->data_window());
		O_daw.insert(B_bitmap->data_window());

		// Create output bitmap
		k3d::bitmap* const O_bitmap = new k3d::bitmap(static_cast<int>(O_daw.px - O_daw.nx + 1), static_cast<int>(O_daw.py - O_daw.ny + 1));
		O_bitmap->data_window(static_cast<int>(O_daw.nx), static_cast<int>(O_daw.ny));

		// Offset from output origin
		int A_xoffset = static_cast<int>(O_daw.nx - A_bitmap->data_window().nx);
		int A_yoffset = static_cast<int>(O_daw.ny - A_bitmap->data_window().ny);
		int B_xoffset = static_cast<int>(O_daw.nx - B_bitmap->data_window().nx);
		int B_yoffset = static_cast<int>(O_daw.ny - B_bitmap->data_window().ny);
		int C_xoffset = static_cast<int>(O_daw.nx - C_bitmap->data_window().nx);
		int C_yoffset = static_cast<int>(O_daw.ny - C_bitmap->data_window().ny);

		// Produce output
		k3d::bitmap::iterator O = O_bitmap->begin();
		for(k3d::pixel_size_t y = 0; y < O_bitmap->height(); ++y)
		{
			for(k3d::pixel_size_t x = 0; x < O_bitmap->width(); ++x)
			{
				k3d::pixel A = get_pixel(A_bitmap, x+A_xoffset, y+A_yoffset);
				k3d::pixel B = get_pixel(B_bitmap, x+B_xoffset, y+B_yoffset);
				k3d::pixel C = get_pixel(B_bitmap, x+C_xoffset, y+C_yoffset);

				double A_red   = A.red   * 0.0039215686274509803;
				double A_green = A.green * 0.0039215686274509803;
				double A_blue  = A.blue  * 0.0039215686274509803;

				double B_red   = B.red   * 0.0039215686274509803;
				double B_green = B.green * 0.0039215686274509803;
				double B_blue  = B.blue  * 0.0039215686274509803;
				double B_alpha = B.alpha * 0.0039215686274509803;

				double C_alpha = C.alpha * 0.0039215686274509803;

				double val = m_value.property_value() * C_alpha;
				double invval = 1 - m_value.property_value() * C_alpha;

				(*O).red   = static_cast<boost::uint8_t>(( invval * A_red   + (val * B_red)   ) * 255);
				(*O).green = static_cast<boost::uint8_t>(( invval * A_green + (val * B_green) ) * 255);
				(*O).blue  = static_cast<boost::uint8_t>(( invval * A_blue  + (val * B_blue)  ) * 255);
				(*O).alpha = static_cast<boost::uint8_t>(( invval * B_alpha ) * 255);

				++O;
			}
		}

		return O_bitmap;;
	}

	k3d::pixel get_pixel(k3d::bitmap* Bitmap, int u, int v)
	{
		// We must be inside the data at least
		if (u < 0 || v < 0 || u > static_cast<int>(Bitmap->width()) || v > static_cast<int>(Bitmap->height()))
			return k3d::pixel(0,0,0,1);

		// Return the pixel
		return *(Bitmap->begin() + (u + (Bitmap->width() * v)));
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<bitmap_matte_keymix_implementation>,
			k3d::interface_list<k3d::ibitmap_source,
			k3d::interface_list<k3d::ibitmap_sink> > > factory(
				k3d::uuid(0x65d26c93, 0xd5514790, 0xa7a36a07, 0x1c220ca6),
				"BitmapMatteKeyMix",
				"Mix two images through a third mask",
				"Objects",
				k3d::iplugin_factory::EXPERIMENTAL);

		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_a;
	k3d_data_property(k3d::bitmap*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_b;
	k3d_data_property(k3d::bitmap*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_c;
	k3d_read_only_data_property(k3d::bitmap*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::demand_storage, k3d::no_constraint) m_output;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_value;
};

/////////////////////////////////////////////////////////////////////////////
// bitmap_matte_keymix_factory

k3d::iplugin_factory& bitmap_matte_keymix_factory()
{
	return bitmap_matte_keymix_implementation::get_factory();
}

} // namespace libk3dbitmap

