// 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 Material K-3D object, which controls the surface appearance of rendered geometry
		\author Tim Shead (tshead@k-3d.com)
*/

#include <k3dsdk/classes.h>
#include <k3dsdk/iapplication.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/imaterial_collection.h>
#include <k3dsdk/iviewport.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/module.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/vectors.h>
#include <k3dsdk/viewport.h>

namespace k3d { class imaterial_collection; }

namespace libk3drenderman
{

/////////////////////////////////////////////////////////////////////////////
// material_implementation

const GLfloat m_BlackData[4] = {0.0f, 0.0f, 0.0f, 1.0f};
const GLfloat m_WhiteData[4] = {1.0f, 1.0f, 1.0f, 1.0f};

const std::string shadowtype_none = "None";
const std::string shadowtype_opaque = "Opaque";
const std::string shadowtype_opacity = "Opacity";
const std::string shadowtype_shaded = "Shaded";

class material_implementation :
	public k3d::persistent<k3d::object> ,
	public k3d::imaterial,
	public k3d::viewport::imaterial,
	public k3d::ri::imaterial
{
	typedef k3d::persistent<k3d::object>  base;

public:
	material_implementation(k3d::idocument& Document) :
		base(Document),
		m_surface_shader(k3d::init_name("surface_shader") + k3d::init_description("Surface shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_displacement_shader(k3d::init_name("displacement_shader") + k3d::init_description("Displacement shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_atmosphere_shader(k3d::init_name("atmosphere_shader") + k3d::init_description("Atmosphere shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_interior_shader(k3d::init_name("interior_shader") + k3d::init_description("Interior shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_exterior_shader(k3d::init_name("exterior_shader") + k3d::init_description("Exterior shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_ShadowType(k3d::init_name("shadowtype") + k3d::init_description("") + k3d::init_value(shadowtype_opacity) + k3d::init_document(Document)),
		m_TrueDisplacement(k3d::init_name("truedisplacement") + k3d::init_description("Enable true displacement during rendering (BMRT specific) [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_TrueDisplacementBounds(k3d::init_name("truedisplacementbounds") + k3d::init_description("Displacement bounds [number]") + k3d::init_value(1.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance)) + k3d::init_document(Document)),
		m_Matte(k3d::init_name("matte") + k3d::init_description("Render geometry as a matte [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_CameraRays(k3d::init_name("camerarays") + k3d::init_description("Render camera rays (BMRT specific) [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_ReflectionRays(k3d::init_name("reflectionrays") + k3d::init_description("Render reflection rays (BMRT specific) [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_Color(k3d::init_name("color") + k3d::init_description("Color [object]") + k3d::init_value(k3d::color(1, 1, 1)) + k3d::init_document(Document)),
		m_Opacity(k3d::init_name("opacity") + k3d::init_description("Opacity [object]") + k3d::init_value(k3d::color(1, 1, 1)) + k3d::init_document(Document))
	{
		deleted_signal().connect(SigC::slot(*this, &material_implementation::on_deleted));
	
		enable_serialization(k3d::persistence::object_proxy(m_surface_shader));
		enable_serialization(k3d::persistence::object_proxy(m_displacement_shader));
		enable_serialization(k3d::persistence::object_proxy(m_atmosphere_shader));
		enable_serialization(k3d::persistence::object_proxy(m_interior_shader));
		enable_serialization(k3d::persistence::object_proxy(m_exterior_shader));
		enable_serialization(k3d::persistence::proxy(m_ShadowType));
		enable_serialization(k3d::persistence::proxy(m_TrueDisplacement));
		enable_serialization(k3d::persistence::proxy(m_TrueDisplacementBounds));
		enable_serialization(k3d::persistence::proxy(m_Matte));
		enable_serialization(k3d::persistence::proxy(m_CameraRays));
		enable_serialization(k3d::persistence::proxy(m_ReflectionRays));
		enable_serialization(k3d::persistence::proxy(m_Color));
		enable_serialization(k3d::persistence::proxy(m_Opacity));

		register_property(m_surface_shader);
		register_property(m_displacement_shader);
		register_property(m_atmosphere_shader);
		register_property(m_interior_shader);
		register_property(m_exterior_shader);
		register_property(m_TrueDisplacement);
		register_property(m_TrueDisplacementBounds);
		register_property(m_Matte);
		register_property(m_CameraRays);
		register_property(m_ReflectionRays);
		register_property(m_Color);
		register_property(m_Opacity);

		m_Color.changed_signal().connect(SigC::slot(*this, &material_implementation::on_async_redraw_all));

		m_RiTexture = 0;
	}

	void on_async_redraw_all()
	{
		k3d::viewport::redraw_all(document(), k3d::iviewport::ASYNCHRONOUS);
	}

	void on_deleted()
	{
	}

	void setup_viewport_material()
	{
		// Cache some current values for speed ...
		k3d::color color = m_Color.property_value();
		m_DiffuseData[0] = (GLfloat)color.red;
		m_DiffuseData[1] = (GLfloat)color.green;
		m_DiffuseData[2] = (GLfloat)color.blue;
		m_DiffuseData[3] = 1.0f;

		glDepthFunc(GL_LESS);
		glEnable(GL_DEPTH_TEST);
		glDisable(GL_TEXTURE_2D);

		glDepthMask(GL_TRUE);
		glDisable(GL_BLEND);

		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, m_BlackData);
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, m_DiffuseData);
		glColor4fv(m_DiffuseData);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, m_WhiteData);
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 64.0f);
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, m_BlackData);
	}

	void setup_renderman_material(const k3d::ri::render_state& State)
	{
		// We only generate RIB on the final sample ...
		if(!k3d::ri::last_sample(State))
			return;

		// Set "render" attributes ...
		k3d::ri::parameter_list render_attributes;

		// Set visibility parameters
		k3d::ri::integer visibility = 0;
		if(m_CameraRays.value())
			visibility |= 1;

		if(m_ReflectionRays.value())
			visibility |= 2;

		if(shadowtype_none != m_ShadowType.value())
			visibility |= 4;

		render_attributes.push_back(k3d::ri::parameter("visibility", k3d::ri::UNIFORM, visibility));

		// Set shadow-casting parameters
		if(shadowtype_none == m_ShadowType.value())
			render_attributes.push_back(k3d::ri::parameter("casts_shadows", k3d::ri::UNIFORM, k3d::ri::string("none")));
		else if(shadowtype_opaque == m_ShadowType.value())
			render_attributes.push_back(k3d::ri::parameter("casts_shadows", k3d::ri::UNIFORM, k3d::ri::string("opaque")));
		else if(shadowtype_opacity == m_ShadowType.value())
			render_attributes.push_back(k3d::ri::parameter("casts_shadows", k3d::ri::UNIFORM, k3d::ri::string("Os")));
		else if(shadowtype_shaded == m_ShadowType.value())
			render_attributes.push_back(k3d::ri::parameter("casts_shadows", k3d::ri::UNIFORM, k3d::ri::string("shade")));
		else
			std::cerr << __PRETTY_FUNCTION__  << ": Unknown shadow type [" << m_ShadowType.value() << "]" << std::endl;

		// Enable / disable true displacement
		render_attributes.push_back(k3d::ri::parameter("truedisplacement", k3d::ri::UNIFORM, k3d::ri::integer(m_TrueDisplacement.value() ? 1 : 0)));

		// Make the render attributes happen ...
		State.engine.RiAttributeV("render", render_attributes);

		// Setup displacement bounds ...
		k3d::ri::parameter_list displacement_attributes;
		displacement_attributes.push_back(k3d::ri::parameter("sphere", k3d::ri::UNIFORM, static_cast<k3d::ri::real>(m_TrueDisplacementBounds.value())));
		displacement_attributes.push_back(k3d::ri::parameter("coordinatesystem", k3d::ri::UNIFORM, k3d::ri::string("world")));
		State.engine.RiAttributeV("displacementbound", displacement_attributes);

		// Set base color
		State.engine.RiColor(m_Color.property_value());

		// Set opacity
		State.engine.RiOpacity(m_Opacity.property_value());

		// Set the matte attribute
		State.engine.RiMatte(m_Matte.value() ? 1 : 0);

		// Setup shaders ...
		if(m_surface_shader.interface())
			m_surface_shader.interface()->setup_renderman_surface_shader(State);
		if(m_displacement_shader.interface())
			m_displacement_shader.interface()->setup_renderman_displacement_shader(State);
		if(m_atmosphere_shader.interface())
			m_atmosphere_shader.interface()->setup_renderman_atmosphere_shader(State);
		if(m_interior_shader.interface())
			m_interior_shader.interface()->setup_renderman_interior_shader(State);
		if(m_exterior_shader.interface())
			m_exterior_shader.interface()->setup_renderman_exterior_shader(State);
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<material_implementation>, k3d::interface_list<k3d::imaterial, k3d::interface_list<k3d::viewport::imaterial, k3d::interface_list<k3d::ri::imaterial> > > > factory(
			k3d::classes::RenderManMaterial(),
			"RenderManMaterial",
			"A RenderMan surface / displacement material",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_object_property(k3d::ri::isurface_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_surface_shader;
	k3d_object_property(k3d::ri::idisplacement_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_displacement_shader;
	k3d_object_property(k3d::ri::ivolume_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_atmosphere_shader;
	k3d_object_property(k3d::ri::ivolume_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_interior_shader;
	k3d_object_property(k3d::ri::ivolume_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_exterior_shader;

	k3d_data_property(std::string, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_ShadowType;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_TrueDisplacement;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_TrueDisplacementBounds;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_Matte;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_CameraRays;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_ReflectionRays;

	k3d_data_property(k3d::color, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_Color;
	k3d_data_property(k3d::color, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_Opacity;

	GLfloat m_AmbientData[4];
	GLfloat m_DiffuseData[4];
	GLfloat m_SpecularData[4];

	k3d::ri::itexture* m_RiTexture;
};

/////////////////////////////////////////////////////////////////////////////
// material_factory

k3d::iplugin_factory& material_factory()
{
	return material_implementation::get_factory();
}

} // namespace libk3drenderman


