// 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 RenderManShader plugin, which encapsulates a single RenderMan shader instance
		\author Tim Shead (tshead@k-3d.com)
*/

#include <k3dsdk/application.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/color.h>
#include <k3dsdk/irenderman.h>
#include <k3dsdk/module.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/xml_utility.h>

#ifdef	WIN32
#ifdef	interface
#undef	interface
#endif	//interface
#endif	//WIN32

#ifdef	WIN32
#ifdef	interface
#undef	interface
#endif	//interface
#endif	//WIN32

namespace libk3drenderman
{

/////////////////////////////////////////////////////////////////////////////
// shader_implementation

/// Abstract base class that provides most of the implementation for our shader objects
class shader_implementation :
	public k3d::persistent<k3d::object>
{
	typedef k3d::persistent<k3d::object> base;

public:
	shader_implementation(k3d::idocument& Document, const sdpsl::shader::type_t ShaderType) :
		base(Document),
		m_shader_name(k3d::init_name("shader_name") + k3d::init_description("Shader Name [string]") + k3d::init_value<std::string>("") + k3d::init_values(shader_values(ShaderType)) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_shader_name));
		register_property(m_shader_name);

		m_shader_name.changed_signal().connect(SigC::slot(*this, &shader_implementation::on_shader_changed));
	}

	~shader_implementation()
	{
		clear();
	}

	void load(sdpxml::Document& Document, sdpxml::Element& Element)
	{
		// Look for a shader name, and setup our arguments accordingly, so they can load their own values in the base-class implementation
		m_shader_name.set_value(sdpxml::GetAttribute(k3d::xml::safe_element(k3d::xml::safe_element(Element, "variables"), sdpxml::Element("variable", "", sdpxml::Attribute("name", "shader_name"))), "value", std::string()));
	
		base::load(Document, Element);
	}

protected:
	const std::string shader_name()
	{
		return m_shader_name.value();
	}

	k3d::ri::parameter_list shader_arguments(const k3d::ri::render_state& State)
	{
		k3d::ri::parameter_list results;

		for(arguments_t::iterator argument = m_arguments.begin(); argument != m_arguments.end(); ++argument)
			{
				if(argument->storage.empty())
					{
						// Empty storage is a placeholder for shader argument types that we don't currently support
					}
				else if(argument->storage.type() == typeid(string_storage_t*))
					{
						string_storage_t& storage = *boost::any_cast<string_storage_t*>(argument->storage);
						results.push_back(k3d::ri::parameter(argument->name, k3d::ri::CONSTANT, static_cast<k3d::ri::string>(storage.value())));
					}
				else if(argument->storage.type() == typeid(scalar_storage_t*))
					{
						scalar_storage_t& storage = *boost::any_cast<scalar_storage_t*>(argument->storage);
						results.push_back(k3d::ri::parameter(argument->name, k3d::ri::CONSTANT, static_cast<k3d::ri::real>(storage.value())));
					}
				else if(argument->storage.type() == typeid(color_storage_t*))
					{
						color_storage_t& storage = *boost::any_cast<color_storage_t*>(argument->storage);
						results.push_back(k3d::ri::parameter(argument->name, k3d::ri::CONSTANT, static_cast<k3d::ri::color>(storage.value())));
					}
				else if(argument->storage.type() == typeid(texture_storage_t*))
					{
						texture_storage_t& storage = *boost::any_cast<texture_storage_t*>(argument->storage);
						if(!storage.interface())
							continue;

						results.push_back(k3d::ri::parameter(argument->name, k3d::ri::CONSTANT, static_cast<k3d::ri::string>(storage.interface()->renderman_texture_path(State).native_file_string())));
					}
				else
					{
						std::cerr << error << __PRETTY_FUNCTION__ << ": unknown storage type for shader argument [" << argument->name << "] will be ignored" << std::endl;
						continue;
					}
			}

		return results;
	}

private:
	/// Helper functor for searching for shaders by name
	struct same_name
	{
		same_name(const std::string& Name) : name(Name) {}

		template<typename T>
		bool operator()(const T& LHS)
		{
			return LHS.name == name;
		}

		const std::string name;
	};

	void on_shader_changed()
	{
		setup_arguments();
	}

	void clear()
	{
		// Get rid of old property / persistence proxies
		k3d::persistence::container::clear();

		for(arguments_t::iterator argument = m_arguments.begin(); argument != m_arguments.end(); ++argument)
			{
				// Empty storage is a placeholder for shader argument types that we don't currently support
				if(argument->storage.empty())
					{
					}
				else if(argument->storage.type() == typeid(scalar_storage_t*))
					{
						unregister_property(*boost::any_cast<scalar_storage_t*>(argument->storage));
					}
				else if(argument->storage.type() == typeid(color_storage_t*))
					{
						unregister_property(*boost::any_cast<color_storage_t*>(argument->storage));
					}
				else if(argument->storage.type() == typeid(string_storage_t*))
					{
						unregister_property(*boost::any_cast<string_storage_t*>(argument->storage));
					}
				else if(argument->storage.type() == typeid(texture_storage_t*))
					{
						unregister_property(*boost::any_cast<texture_storage_t*>(argument->storage));
					}
				else
					{
						std::cerr << error << __PRETTY_FUNCTION__ << ": unknown argument storage type for [" << argument->name << "] will not be destroyed" << std::endl;
					}
			}

		for(arguments_t::iterator argument = m_arguments.begin(); argument != m_arguments.end(); ++argument)
			{
				// Empty storage is a placeholder for shader argument types that we don't currently support
				if(argument->storage.empty())
					{
					}
				else if(argument->storage.type() == typeid(scalar_storage_t*))
					{
						k3d::undoable_delete(boost::any_cast<scalar_storage_t*>(argument->storage), document());
					}
				else if(argument->storage.type() == typeid(color_storage_t*))
					{
						k3d::undoable_delete(boost::any_cast<color_storage_t*>(argument->storage), document());
					}
				else if(argument->storage.type() == typeid(string_storage_t*))
					{
						k3d::undoable_delete(boost::any_cast<string_storage_t*>(argument->storage), document());
					}
				else if(argument->storage.type() == typeid(texture_storage_t*))
					{
						k3d::undoable_delete(boost::any_cast<texture_storage_t*>(argument->storage), document());
					}
				else
					{
						std::cerr << error << __PRETTY_FUNCTION__ << ": unknown argument storage type for [" << argument->name << "] will not be destroyed" << std::endl;
					}
			}

		m_arguments.clear();
	}

	class pause_recording
	{
	public:
		pause_recording(k3d::idocument& Document) :
			m_document(Document)
		{
			if(m_document.state_recorder().current_change_set())
				m_change_set = m_document.state_recorder().stop_recording();
		}

		~pause_recording()
		{
			if(m_change_set.get())
				m_document.state_recorder().start_recording(m_change_set);
		}
	
	private:
		k3d::idocument& m_document;
		std::auto_ptr<k3d::istate_change_set> m_change_set;
	};

	void setup_arguments()
	{
		// At the moment, we don't support undo-able changes to the argument-set, so temporarily disable any recording ...
		pause_recording pause(document());
	
		clear();

		enable_serialization(k3d::persistence::proxy(m_shader_name));

		// Try to find a shader description that matches the new shader name ...
		const std::string shader_name = m_shader_name.property_value();
		if(shader_name.empty())
			return;
			
		const sdpsl::shaders_t::const_iterator shader = std::find_if(k3d::application().shaders().begin(), k3d::application().shaders().end(), same_name(shader_name));
		if(shader == k3d::application().shaders().end())
			{
				std::cerr << error << "Unknown shader [" << shader_name << "]" << std::endl;
				return;
			}

		for(sdpsl::shader::arguments_t::const_iterator argument = shader->arguments.begin(); argument != shader->arguments.end(); ++argument)
			{
				switch(argument->extended_type)
					{
						case sdpsl::argument::EX_FLOAT:
						case sdpsl::argument::EX_TIME:
						case sdpsl::argument::EX_ANGLE:
						case sdpsl::argument::EX_DISTANCE:
						case sdpsl::argument::EX_AREA:
						case sdpsl::argument::EX_VOLUME:
						case sdpsl::argument::EX_MASS:
						case sdpsl::argument::EX_FORCE:
						case sdpsl::argument::EX_PRESSURE:
							{
								scalar_storage_t* const scalar_storage = new scalar_storage_t(k3d::init_name(argument->name) + k3d::init_description("Shader Argument") + k3d::init_document(document()) + k3d::init_value(k3d::from_string<double>(argument->default_value, 0.0)));

								enable_serialization(k3d::persistence::proxy(*scalar_storage));
								register_property(*scalar_storage);

								m_arguments.push_back(argument_t(argument->name, boost::any(scalar_storage)));
								break;
							}
						case sdpsl::argument::EX_COLOR:
							{
								color_storage_t* const color_storage = new color_storage_t(k3d::init_name(argument->name) + k3d::init_description("Shader Argument") + k3d::init_document(document()) + k3d::init_value(k3d::from_string<k3d::color>(argument->default_value, k3d::color(1, 1, 1))));

								enable_serialization(k3d::persistence::proxy(*color_storage));
								register_property(*color_storage);

								m_arguments.push_back(argument_t(argument->name, boost::any(color_storage)));
								break;
							}
						case sdpsl::argument::EX_STRING:
						case sdpsl::argument::EX_SPACE:
							{
								string_storage_t* const string_storage = new string_storage_t(k3d::init_name(argument->name) + k3d::init_description("Shader Argument") + k3d::init_document(document()) + k3d::init_value(argument->default_value));

								enable_serialization(k3d::persistence::proxy(*string_storage));
								register_property(*string_storage);

								m_arguments.push_back(argument_t(argument->name, boost::any(string_storage)));
								break;
							}
						case sdpsl::argument::EX_TEXTURE:
							{
								texture_storage_t* const texture_storage = new texture_storage_t(k3d::init_name(argument->name) + k3d::init_description("Shader Argument") + k3d::init_document(document()) + k3d::init_object_value(0));

								enable_serialization(k3d::persistence::object_proxy(*texture_storage));
								register_property(*texture_storage);

								m_arguments.push_back(argument_t(argument->name, boost::any(texture_storage)));
								break;
							}
						case sdpsl::argument::EX_POINT:
						case sdpsl::argument::EX_VECTOR:
						case sdpsl::argument::EX_NORMAL:
						case sdpsl::argument::EX_MATRIX:
							{
								// Empty storage is a placeholder for shader argument types that we don't currently support
								m_arguments.push_back(argument_t(argument->name, boost::any()));
								break;
							}
						default:
							{
								m_arguments.push_back(argument_t(argument->name, boost::any()));
								std::cerr << error << __PRETTY_FUNCTION__ << ": unknown extended argument type for [" << argument->name << "] will not receive storage" << std::endl;
							}
					}
			}
	}

	/// Storage for the shader name
	k3d_list_property(std::string, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_shader_name;

	/// Defines storage for a single shader argument instance
	struct argument_t
	{
		argument_t(const std::string& Name, const boost::any Storage) : name(Name), storage(Storage) {}
		const std::string name;
		const boost::any storage;
	};

	/// Defines storage for a collection of shader arguments
	typedef std::list<argument_t> arguments_t;

	/// Stores the current set of shader arguments
	arguments_t m_arguments;

	/// Defines storage for a scalar argument
	typedef k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) scalar_storage_t;
	/// Defines storage for a color argument
	typedef k3d_data_property(k3d::color, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) color_storage_t;
	/// Defines storage for a string argument
	typedef k3d_data_property(std::string, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) string_storage_t;
	/// Defines storage for a texture argument
	typedef k3d_object_property(k3d::ri::itexture, k3d::immutable_name, k3d::no_undo, k3d::local_storage) texture_storage_t;

	/// Returns lists of available shaders of the given type
	const k3d::ilist_property<std::string>::values_t& shader_values(const sdpsl::shader::type_t Type)
	{
		static bool initialized = false;
		static k3d::ilist_property<std::string>::values_t deformation_shader_values;
		static k3d::ilist_property<std::string>::values_t imager_shader_values;
		static k3d::ilist_property<std::string>::values_t light_shader_values;
		static k3d::ilist_property<std::string>::values_t surface_shader_values;
		static k3d::ilist_property<std::string>::values_t transformation_shader_values;
		static k3d::ilist_property<std::string>::values_t volume_shader_values;
		static k3d::ilist_property<std::string>::values_t empty_shader_values;

		if(!initialized)
			{
				for(sdpsl::shaders_t::const_iterator shader = k3d::application().shaders().begin(); shader != k3d::application().shaders().end(); ++shader)
					{
						switch(shader->type)
							{
								case sdpsl::shader::DISPLACEMENT:
									deformation_shader_values.push_back(shader->name);
									break;

								case sdpsl::shader::IMAGER:
									imager_shader_values.push_back(shader->name);
									break;

								case sdpsl::shader::LIGHT:
									light_shader_values.push_back(shader->name);
									break;

								case sdpsl::shader::SURFACE:
									surface_shader_values.push_back(shader->name);
									break;

								case sdpsl::shader::TRANSFORMATION:
									transformation_shader_values.push_back(shader->name);
									break;

								case sdpsl::shader::VOLUME:
									volume_shader_values.push_back(shader->name);
									break;
							}
					}

				deformation_shader_values.push_back(std::string());
				imager_shader_values.push_back(std::string());
				light_shader_values.push_back(std::string());
				surface_shader_values.push_back(std::string());
				transformation_shader_values.push_back(std::string());
				volume_shader_values.push_back(std::string());

				std::sort(deformation_shader_values.begin(), deformation_shader_values.end());
				std::sort(imager_shader_values.begin(), imager_shader_values.end());
				std::sort(light_shader_values.begin(), light_shader_values.end());
				std::sort(surface_shader_values.begin(), surface_shader_values.end());
				std::sort(transformation_shader_values.begin(), transformation_shader_values.end());
				std::sort(volume_shader_values.begin(), volume_shader_values.end());

				initialized = true;
			}

		switch(Type)
			{
				case sdpsl::shader::DISPLACEMENT:
					return deformation_shader_values;

				case sdpsl::shader::IMAGER:
					return imager_shader_values;

				case sdpsl::shader::LIGHT:
					return light_shader_values;

				case sdpsl::shader::SURFACE:
					return surface_shader_values;

				case sdpsl::shader::TRANSFORMATION:
					return transformation_shader_values;

				case sdpsl::shader::VOLUME:
					return volume_shader_values;
			}

		return empty_shader_values;
	}

};

/////////////////////////////////////////////////////////////////////////////
// displacement_shader_implementation

/// Implements a RenderMan displacement shader plugin
class displacement_shader_implementation :
	public shader_implementation,
	public k3d::ri::idisplacement_shader
{
	typedef shader_implementation base;

public:
	displacement_shader_implementation(k3d::idocument& Document) :
		base(Document, sdpsl::shader::DISPLACEMENT)
	{
	}

	void setup_renderman_displacement_shader(const k3d::ri::render_state& State)
	{
		State.engine.RiDisplacementV(shader_name(), shader_arguments(State));
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<displacement_shader_implementation>,
				k3d::interface_list<k3d::ri::idisplacement_shader> > factory(
			k3d::classes::RenderManDisplacementShader(),
			"RenderManDisplacementShader",
			"Encapsulates a RenderMan displacement shader instance",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}
};

/////////////////////////////////////////////////////////////////////////////
// imager_shader_implementation

/// Implements a RenderMan imager shader plugin
class imager_shader_implementation :
	public shader_implementation,
	public k3d::ri::iimager_shader
{
	typedef shader_implementation base;

public:
	imager_shader_implementation(k3d::idocument& Document) :
		base(Document, sdpsl::shader::IMAGER)
	{
	}

	void setup_renderman_imager_shader(const k3d::ri::render_state& State)
	{
		State.engine.RiImagerV(shader_name(), shader_arguments(State));
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<imager_shader_implementation>,
				k3d::interface_list<k3d::ri::iimager_shader> > factory(
			k3d::classes::RenderManImagerShader(),
			"RenderManImagerShader",
			"Encapsulates a RenderMan imager shader instance",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}
};

/////////////////////////////////////////////////////////////////////////////
// light_shader_implementation

/// Implements a RenderMan light shader plugin
class light_shader_implementation :
	public shader_implementation,
	public k3d::ri::ilight_shader
{
	typedef shader_implementation base;

public:
	light_shader_implementation(k3d::idocument& Document) :
		base(Document, sdpsl::shader::LIGHT)
	{
	}

	void setup_renderman_light_shader(const k3d::ri::render_state& State)
	{
		State.engine.RiLightSourceV(shader_name(), shader_arguments(State));
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<light_shader_implementation>,
				k3d::interface_list<k3d::ri::ilight_shader> > factory(
			k3d::classes::RenderManLightShader(),
			"RenderManLightShader",
			"Encapsulates a RenderMan light shader instance",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}
};

/////////////////////////////////////////////////////////////////////////////
// surface_shader_implementation

/// Implements a RenderMan surface shader plugin
class surface_shader_implementation :
	public shader_implementation,
	public k3d::ri::isurface_shader
{
	typedef shader_implementation base;

public:
	surface_shader_implementation(k3d::idocument& Document) :
		base(Document, sdpsl::shader::SURFACE)
	{
	}

	void setup_renderman_surface_shader(const k3d::ri::render_state& State)
	{
		State.engine.RiSurfaceV(shader_name(), shader_arguments(State));
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<surface_shader_implementation>,
				k3d::interface_list<k3d::ri::isurface_shader> > factory(
			k3d::classes::RenderManSurfaceShader(),
			"RenderManSurfaceShader",
			"Encapsulates a RenderMan surface shader instance",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}
};

/////////////////////////////////////////////////////////////////////////////
// volume_shader_implementation

/// Implements a RenderMan volume shader plugin
class volume_shader_implementation :
	public shader_implementation,
	public k3d::ri::ivolume_shader
{
	typedef shader_implementation base;

public:
	volume_shader_implementation(k3d::idocument& Document) :
		base(Document, sdpsl::shader::VOLUME)
	{
	}

	void setup_renderman_atmosphere_shader(const k3d::ri::render_state& State)
	{
		State.engine.RiAtmosphereV(shader_name(), shader_arguments(State));
	}

	void setup_renderman_interior_shader(const k3d::ri::render_state& State)
	{
		State.engine.RiInteriorV(shader_name(), shader_arguments(State));
	}

	void setup_renderman_exterior_shader(const k3d::ri::render_state& State)
	{
		State.engine.RiExteriorV(shader_name(), shader_arguments(State));
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<volume_shader_implementation>,
				k3d::interface_list<k3d::ri::ivolume_shader> > factory(
			k3d::classes::RenderManVolumeShader(),
			"RenderManVolumeShader",
			"Encapsulates a RenderMan volume shader instance",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}
};

/////////////////////////////////////////////////////////////////////////////
// shader factories

k3d::iplugin_factory& displacement_shader_factory()
{
	return displacement_shader_implementation::get_factory();
}

k3d::iplugin_factory& imager_shader_factory()
{
	return imager_shader_implementation::get_factory();
}

k3d::iplugin_factory& light_shader_factory()
{
	return light_shader_implementation::get_factory();
}

k3d::iplugin_factory& surface_shader_factory()
{
	return surface_shader_implementation::get_factory();
}

k3d::iplugin_factory& volume_shader_factory()
{
	return volume_shader_implementation::get_factory();
}

} // namespace libk3drenderman


