// 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
		\brief Implements the k3d::bitmap_preview namespace, which provides a MVC UI for boolean data sources
		\author Tim Shead (tshead@k-3d.com)
*/

#include "bitmap_preview.h"

#include <k3dsdk/application.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/istate_recorder.h>
#include <k3dsdk/ioptions.h>

#include <sdpgtk/sdpgtkloopevents.h>
#include <sdpgtk/sdpgtkutility.h>

namespace k3d
{

namespace bitmap_preview
{

namespace detail
{

const std::string control_rgb = "rgb";
const std::string control_alpha = "alpha";

sdpxml::Document& gtkml_template()
{
	static sdpxml::Document gtkml("empty");
	if(gtkml.Name() != "gtkml")
		{
			std::istringstream uitemplate(
				"<gtkml>"
					"<eventbox>"
						"<hbox homogeneous=\"true\">"
							"<event signal=\"destroy\" name=\"destroy\"/>"
							"<frame name=\"rgb\" expand=\"false\" fill=\"false\" size=\"64 64\"/>"
							"<frame name=\"alpha\" expand=\"false\" fill=\"false\" size=\"64 64\"/>"
						"</hbox>"
					"</eventbox>"
				"</gtkml>\n");

			assert(gtkml.Load(uitemplate, "bitmap_preview builtin template"));
		}

	return gtkml;
}

} // namespace detail

/////////////////////////////////////////////////////////////////////////////
// control

control::control(k3d::iunknown* const CommandNodeParent, const std::string CommandNodeName) :
	base(CommandNodeParent, CommandNodeName)
{
	return_if_fail(Load(detail::gtkml_template()));
}

control::~control()
{
	// No more events from this point forward ...
	DisconnectAllEvents();

	// Clean-up the GTK+ tree ...
	if(Root())
		RootWidget().Destroy();
}

const std::string control::CustomType() const
{
	return std::string("k3dbitmappreview");
}

bool control::Create(sdpGtkIObjectContainer* const ObjectContainer, sdpxml::Document& Document, sdpxml::Element& Element)
{
	// Sanity checks ...
	assert_warning(ObjectContainer);
	assert_warning(Element.Name() == "k3dbitmappreview");

	return true;
}

bool control::attach(std::auto_ptr<idata_proxy> Data, k3d::istate_recorder* const StateRecorder, const std::string StateChangeName)
{
	// Sanity checks ...
	return_val_if_fail(Data.get(), false);

	// Setup OpenGL for rendering previews ...
	sdpGtkContainer rgb(Container("rgb"));
	if(!m_rgb.Create(rgb, true, true, 8, 8, 8, 0))
		{
			if(!m_rgb.Create(rgb, true, true, 5, 5, 5, 0))
				{
					if(!m_rgb.Create(rgb, false, false, 4, 4, 4, 0))
						{
							std::cerr << error << "Could not find useable OpenGL visual for rgb preview" << std::endl;
						}
				}
		}
	if(m_rgb.Initialized())
		MapEvent("expose-event", "rgb", false, m_rgb, true);

	sdpGtkContainer alpha(Container("alpha"));
	if(!m_alpha.Create(alpha, true, true, 8, 8, 8, 0))
		{
			if(!m_alpha.Create(alpha, true, true, 5, 5, 5, 0))
				{
					if(!m_alpha.Create(alpha, false, false, 4, 4, 4, 0))
						{
							std::cerr << error << "Could not find useable OpenGL visual for alpha preview" << std::endl;
						}
				}
		}
	if(m_alpha.Initialized())
		MapEvent("expose-event", "alpha", false, m_alpha, true);

	// Take ownership of the data source ...
	m_data = Data;

	// Complete our own initialization ...
	return_val_if_fail(base::Attach(StateRecorder, StateChangeName), false);

	// Update the display ...
	update();

	// We want to be notified if the data source changes ...
	m_data->changed_signal().connect(SigC::slot(*this, &control::update));

	return true;
}

void control::update()
{
	// Sanity checks ...
	return_if_fail(m_data.get());
	
	// Create the preview image ...
	double ratio = 1.0;
	m_preview_image.reset(64, 64);

	const k3d::bitmap* const image = m_data->value();
	if(image)
		{
			if(image->height())
				ratio = static_cast<double>(image->width()) / static_cast<double>(image->height());
				
			k3d::scale_bitmap(*image, m_preview_image);
		}
	else
		{
			k3d::checkerboard_fill(m_preview_image, 8, 8, k3d::color(0.2, 0.2, 0.2), k3d::color(0.8, 0.8, 0.8));
		}

	// Update our frame aspect ratios to match the image ...
	const k3d::pixel_size_t widget_height = 64;
	Frame("rgb").SetSize(static_cast<k3d::pixel_size_t>(ratio * widget_height), widget_height);
	Frame("alpha").SetSize(static_cast<k3d::pixel_size_t>(ratio * widget_height), widget_height);
						
	m_rgb.QueueDraw();
	m_alpha.QueueDraw();
}

void control::OnEvent(sdpGtkEvent* Event)
{
	// Sanity checks ...
	assert_warning(Event);

	if(Event->Name() == "destroy")
		on_destroy();
	else if(Event->Name() == "rgb")
		on_redraw_rgb();
	else if(Event->Name() == "alpha")
		on_redraw_alpha();
	else
		base::OnEvent(Event);
}

void control::on_redraw_rgb()
{
	// Sanity checks ...
	if(0 == m_preview_image.width() || 0 == m_preview_image.height())
		return;
		
	if(!m_rgb.Initialized())
		return;

	// Get the preview image data ...
	preview_pixel_t* const imagedata = m_preview_image.data();
	const k3d::pixel_size_t imagewidth = m_preview_image.width();
	const k3d::pixel_size_t imageheight = m_preview_image.height();

	// If we don't have any data, we're done ...
	if(!imagedata || !imagewidth || !imageheight)
		return;

	// Start OpenGL rendering ...
	m_rgb.Begin();

	// Get the widget sizes ...
	unsigned long width = m_rgb.Width();
	unsigned long height = m_rgb.Height();

	glViewport(0, 0, width, height);

	// Setup the viewing projection ...
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	glShadeModel(GL_FLAT);

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imagewidth, imageheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imagedata);

	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glBegin(GL_POLYGON);
		glTexCoord2d(0.0, 0.0); glVertex2d(-1.0, 1.0);
		glTexCoord2d(1.0, 0.0); glVertex2d(1.0, 1.0);
		glTexCoord2d(1.0, 1.0); glVertex2d(1.0, -1.0);
		glTexCoord2d(0.0, 1.0); glVertex2d(-1.0, -1.0);
	glEnd();

	glFlush();

	m_rgb.SwapBuffers();
	m_rgb.End();
}

void control::on_redraw_alpha()
{
	// Sanity checks ...
	if(0 == m_preview_image.width() || 0 == m_preview_image.height())
		return;
		
	if(!m_alpha.Initialized())
		return;

	// Get image data ...
	preview_pixel_t* const imagedata = m_preview_image.data();
	const k3d::pixel_size_t imagewidth = m_preview_image.width();
	const k3d::pixel_size_t imageheight = m_preview_image.height();

	// If we don't have any data, we're done ...
	if(!imagedata || !imagewidth || !imageheight)
		return;

	// Start OpenGL rendering ...
	m_alpha.Begin();

	// Get the widget sizes ...
	unsigned long width = m_alpha.Width();
	unsigned long height = m_alpha.Height();

	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	glShadeModel(GL_FLAT);

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, imagewidth, imageheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imagedata);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	glDisable(GL_BLEND);
	glDisable(GL_TEXTURE_2D);
	glColor3d(0, 0, 0);
	glBegin(GL_POLYGON);
		glTexCoord2d(0.0, 0.0); glVertex2d(-1.0, 1.0);
		glTexCoord2d(1.0, 0.0); glVertex2d(1.0, 1.0);
		glTexCoord2d(1.0, 1.0); glVertex2d(1.0, -1.0);
		glTexCoord2d(0.0, 1.0); glVertex2d(-1.0, -1.0);
	glEnd();

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_TEXTURE_2D);
	glColor3d(1, 1, 1);
	glBegin(GL_POLYGON);
		glTexCoord2d(0.0, 0.0); glVertex2d(-1.0, 1.0);
		glTexCoord2d(1.0, 0.0); glVertex2d(1.0, 1.0);
		glTexCoord2d(1.0, 1.0); glVertex2d(1.0, -1.0);
		glTexCoord2d(0.0, 1.0); glVertex2d(-1.0, -1.0);
	glEnd();

	glFlush();

	m_alpha.SwapBuffers();
	m_alpha.End();
}

void control::on_destroy()
{
	DisconnectAllEvents();
	Clear();
}

} // namespace bitmap_preview

} // namespace k3d


