// 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 Tim Shead <tshead@k-3d.com>
*/

#include "ilight.h"

#include <k3dsdk/color.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/module.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/vectors.h>
#include <k3dsdk/viewport.h>

#include <sdpgl/sdpgl.h>

namespace libk3dyafray
{

/////////////////////////////////////////////////////////////////////////////
// light

template<typename base_t>
class light :
	public base_t,
	public ilight
{
public:
	light(k3d::idocument& Document) :
		base_t(Document),
		m_emit(k3d::init_name("emit") + k3d::init_description("Emit Light [boolean]") + k3d::init_value(true) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_emit));
		register_property(m_emit);
	}

protected:
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_emit;
};

/////////////////////////////////////////////////////////////////////////////
// colored_light

template<typename base_t>
class colored_light :
	public base_t
{
public:
	colored_light(k3d::idocument& Document) :
		base_t(Document),
		m_color(k3d::init_name("color") + k3d::init_description("Light color [object]") + k3d::init_value(k3d::color(1, 1, 1)) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_color));
		register_property(m_color);
	}

protected:
	k3d_data_property(k3d::color, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_color;
};

/////////////////////////////////////////////////////////////////////////////
// optional_shadows

template<typename base_t>
class optional_shadows :
	public base_t
{
public:
	optional_shadows(k3d::idocument& Document) :
		base_t(Document),
		m_cast_shadows(k3d::init_name("cast_shadows") + k3d::init_description("Cast shadows [boolean]") + k3d::init_value(true) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_cast_shadows));
		register_property(m_cast_shadows);
	}

protected:
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_cast_shadows;
};

/////////////////////////////////////////////////////////////////////////////
// drawable_light

template<typename base_t>
class drawable_light :
	public k3d::viewport::drawable<base_t>
{
	typedef k3d::viewport::drawable<base_t> base;
	
public:
	drawable_light(k3d::idocument& Document) :
		base(Document)
	{
	}

	void on_viewport_draw(const k3d::viewport::render_state& State)
	{
		sdpgl::store_attributes attributes();
	
		const bool emitting = m_emit.property_value();

		glDisable(GL_LIGHTING);
		glDisable(GL_TEXTURE_1D);
		glDisable(GL_TEXTURE_2D);

		if(emitting)
			glColor3d(1, 1, 1);
		else
			glColor3d(0, 0, 0);

		glLineWidth(1.0f);
		glDisable(GL_LINE_STIPPLE);

		draw_geometry();
	}

	void on_viewport_select(const k3d::viewport::render_state& State)
	{
		sdpgl::store_attributes attributes();
		draw_geometry();
	}

private:
	void draw_geometry()
	{
		glBegin(GL_LINES);

		k3d::vector3 coords(0, 0, 0);

		glVertex3d(coords[0] + 1.0, coords[1], coords[2]);
		glVertex3d(coords[0] - 1.0, coords[1], coords[2]);
		glVertex3d(coords[0], coords[1] + 1.0, coords[2]);
		glVertex3d(coords[0], coords[1] - 1.0, coords[2]);
		glVertex3d(coords[0], coords[1], coords[2] + 1.0);
		glVertex3d(coords[0], coords[1], coords[2] - 1.0);

		glVertex3d(coords[0] + 0.4, coords[1] + 0.4, coords[2] + 0.4);
		glVertex3d(coords[0] - 0.4, coords[1] - 0.4, coords[2] - 0.4);
		glVertex3d(coords[0] - 0.4, coords[1] + 0.4, coords[2] + 0.4);
		glVertex3d(coords[0] + 0.4, coords[1] - 0.4, coords[2] - 0.4);
		glVertex3d(coords[0] + 0.4, coords[1] + 0.4, coords[2] - 0.4);
		glVertex3d(coords[0] - 0.4, coords[1] - 0.4, coords[2] + 0.4);
		glVertex3d(coords[0] - 0.4, coords[1] + 0.4, coords[2] - 0.4);
		glVertex3d(coords[0] + 0.4, coords[1] - 0.4, coords[2] + 0.4);

		glEnd();
	}
};

/////////////////////////////////////////////////////////////////////////////
// point_light

class point_light :
	public drawable_light<optional_shadows<colored_light<light<k3d::transformable<k3d::persistent<k3d::object> > > > > >
{
	typedef drawable_light<optional_shadows<colored_light<light<k3d::transformable<k3d::persistent<k3d::object> > > > > > base;

public:
	point_light(k3d::idocument& Document) :
		base(Document),
		m_power(k3d::init_name("power") + k3d::init_description("Power [number]") + k3d::init_value(400.0) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_power));
		register_property(m_power);
	}

	void setup_light(std::ostream& Stream)
	{
		if(!m_emit.property_value())
			return;

		const k3d::vector3 position = k3d::object_to_world_matrix(*this) * k3d::vector3(0, 0, 0);
		const k3d::color color = m_color.property_value();

		Stream << "<light type=\"pointlight\" name=\"" << name() << "\" power=\"" << m_power.property_value() << "\" cast_shadows=\"" << (m_cast_shadows.property_value() ? "yes" : "no") << "\">" << std::endl;
		Stream << "	<from x=\"" << std::fixed << -position[0] << "\" y=\"" << std::fixed << position[1] << "\" z=\"" << std::fixed << position[2] << "\"/>" << std::endl;
		Stream << "	<color r=\"" << color.red << "\" g=\"" << color.green << "\" b=\"" << color.blue << "\"/>" << std::endl;
		Stream << "</light>" << std::endl;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<point_light>,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > factory(
			k3d::uuid(0xd693bb64, 0xd73943ce, 0x80852061, 0x24fd242e),
			"YafRayPointLight",
			"YafRay Point Light",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_power;
};

/////////////////////////////////////////////////////////////////////////////
// sun_light

class sun_light :
	public drawable_light<optional_shadows<colored_light<light<k3d::transformable<k3d::persistent<k3d::object> > > > > >
{
	typedef drawable_light<optional_shadows<colored_light<light<k3d::transformable<k3d::persistent<k3d::object> > > > > > base;

public:
	sun_light(k3d::idocument& Document) :
		base(Document),
		m_power(k3d::init_name("power") + k3d::init_description("Power [number]") + k3d::init_value(40.0) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_power));
		register_property(m_power);
	}

	void setup_light(std::ostream& Stream)
	{
		if(!m_emit.property_value())
			return;

		const k3d::vector3 position = k3d::object_to_world_matrix(*this) * k3d::vector3(0, 0, 0);
		const k3d::color color = m_color.property_value();

		Stream << "<light type=\"sunlight\" name=\"" << name() << "\" power=\"" << m_power.property_value() << "\" cast_shadows=\"" << (m_cast_shadows.property_value() ? "yes" : "no") << "\">" << std::endl;
		Stream << "	<from x=\"" << std::fixed << -position[0] << "\" y=\"" << std::fixed << position[1] << "\" z=\"" << std::fixed << position[2] << "\"/>" << std::endl;
		Stream << "	<color r=\"" << color.red << "\" g=\"" << color.green << "\" b=\"" << color.blue << "\"/>" << std::endl;
		Stream << "</light>" << std::endl;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<sun_light>,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > factory(
			k3d::uuid(0x9a48777d, 0xa68345b5, 0xacb9fc07, 0x8af3e7f6),
			"YafRaySunLight",
			"YafRay Sun Light",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_power;
};

/////////////////////////////////////////////////////////////////////////////
// soft_light

class soft_light :
	public drawable_light<colored_light<light<k3d::transformable<k3d::persistent<k3d::object> > > > >
{
	typedef drawable_light<colored_light<light<k3d::transformable<k3d::persistent<k3d::object> > > > > base;

public:
	soft_light(k3d::idocument& Document) :
		base(Document),
		m_power(k3d::init_name("power") + k3d::init_description("Power [number]") + k3d::init_value(400.0) + k3d::init_document(Document)),
		m_radius(k3d::init_name("radius") + k3d::init_description("Softness [number]") + k3d::init_value(5.0) + k3d::init_document(Document)),
		m_resolution(k3d::init_name("resolution") + k3d::init_description("Shadowmap resolution [positive integer]") + k3d::init_value(256) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(0UL, k3d::constraint::maximum(4096UL)))),
		m_bias(k3d::init_name("bias") + k3d::init_description("Shadowmap bias [number]") + k3d::init_value(0.01) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_power));
		enable_serialization(k3d::persistence::proxy(m_radius));
		enable_serialization(k3d::persistence::proxy(m_resolution));
		enable_serialization(k3d::persistence::proxy(m_bias));
		
		register_property(m_power);
		register_property(m_radius);
		register_property(m_resolution);
		register_property(m_bias);
	}

	void setup_light(std::ostream& Stream)
	{
		if(!m_emit.property_value())
			return;

		const k3d::vector3 position = k3d::object_to_world_matrix(*this) * k3d::vector3(0, 0, 0);
		const k3d::color color = m_color.property_value();

		Stream << "<light type=\"softlight\" name=\"" << name() << "\" power=\"" << m_power.property_value() << "\" res=\"" << m_resolution.property_value()  << "\" radius=\"" << m_radius.property_value() << "\" bias=\"" << m_bias.property_value() << "\">" << std::endl;
		Stream << "	<from x=\"" << std::fixed << -position[0] << "\" y=\"" << std::fixed << position[1] << "\" z=\"" << std::fixed << position[2] << "\"/>" << std::endl;
		Stream << "	<color r=\"" << color.red << "\" g=\"" << color.green << "\" b=\"" << color.blue << "\"/>" << std::endl;
		Stream << "</light>" << std::endl;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<soft_light>,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > factory(
			k3d::uuid(0x2fcaffb5, 0xed294a0d, 0x82133a8f, 0x48df4988),
			"YafRaySoftLight",
			"YafRay Soft Light",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_power;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_radius;
	k3d_data_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_resolution;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_bias;
};

/////////////////////////////////////////////////////////////////////////////
// spot_light

class spot_light :
	public drawable_light<colored_light<light<k3d::transformable<k3d::persistent<k3d::object> > > > >
{
	typedef drawable_light<colored_light<light<k3d::transformable<k3d::persistent<k3d::object> > > > > base;

public:
	spot_light(k3d::idocument& Document) :
		base(Document),
		m_power(k3d::init_name("power") + k3d::init_description("Power [number]") + k3d::init_value(400.0) + k3d::init_document(Document)),
		m_size(k3d::init_name("size") + k3d::init_description("Angle [number]") + k3d::init_value(k3d::radians(30.0)) + k3d::init_document(Document)),
		m_nblend(k3d::init_name("nblend") + k3d::init_description("Edge softness [number]") + k3d::init_value(5.0) + k3d::init_document(Document)),
		m_beam_falloff(k3d::init_name("beam_falloff") + k3d::init_description("Beam falloff [number]") + k3d::init_value(10.0) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_power));
		enable_serialization(k3d::persistence::proxy(m_size));
		enable_serialization(k3d::persistence::proxy(m_nblend));
		enable_serialization(k3d::persistence::proxy(m_beam_falloff));
		
		register_property(m_power);
		register_property(m_size);
		register_property(m_nblend);
		register_property(m_beam_falloff);
	}

	void setup_light(std::ostream& Stream)
	{
		if(!m_emit.property_value())
			return;

		const k3d::vector3 position = k3d::object_to_world_matrix(*this) * k3d::vector3(0, 0, 0);
		const k3d::color color = m_color.property_value();
		const double power = m_power.property_value();
		const double size = k3d::degrees(m_size.property_value());
		const double nblend = m_nblend.property_value();
		const double beam_falloff = m_beam_falloff.property_value();

		Stream << "<light type=\"spotlight\" name=\"" << name() << "\" power=\"" << power << "\" size=\"" << size << "\" nblend=\"" << nblend << "\" beam_falloff=\"" << beam_falloff << "\">" << std::endl;
		Stream << "	<from x=\"" << std::fixed << -position[0] << "\" y=\"" << std::fixed << position[1] << "\" z=\"" << std::fixed << position[2] << "\"/>" << std::endl;
		Stream << "	<color r=\"" << color.red << "\" g=\"" << color.green << "\" b=\"" << color.blue << "\"/>" << std::endl;
		Stream << "</light>" << std::endl;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<spot_light>,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > factory(
			k3d::uuid(0x5e363371, 0xf8464895, 0x99f0ddf0, 0x4e26ee4a),
			"YafRaySpotLight",
			"YafRay Spot Light",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_power;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_size;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_nblend;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_beam_falloff;
};

/////////////////////////////////////////////////////////////////////////////
// hemi_light

class hemi_light :
	public colored_light<light<k3d::persistent<k3d::object> > >
{
	typedef colored_light<light<k3d::persistent<k3d::object> > > base;

public:
	hemi_light(k3d::idocument& Document) :
		base(Document),
		m_power(k3d::init_name("power") + k3d::init_description("Power [number]") + k3d::init_value(20.0) + k3d::init_document(Document)),
		m_samples(k3d::init_name("samples") + k3d::init_description("Samples [positive integer]") + k3d::init_value(256) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(0UL, k3d::constraint::maximum(1024UL))))
	{
		enable_serialization(k3d::persistence::proxy(m_power));
		enable_serialization(k3d::persistence::proxy(m_samples));

		register_property(m_power);
		register_property(m_samples);

		// We override the default white color ...		
		m_color.value() = k3d::color(0.9, 0.9, 1.0);
	}

	void setup_light(std::ostream& Stream)
	{
		if(!m_emit.property_value())
			return;

		const k3d::color color = m_color.property_value();

		Stream << "<light type=\"hemilight\" name=\"" << name() << "\" power=\"" << m_power.property_value() << "\" samples=\"" << m_samples.property_value() << "\">" << std::endl;
		Stream << "	<color r=\"" << color.red << "\" g=\"" << color.green << "\" b=\"" << color.blue << "\"/>" << std::endl;
		Stream << "</light>" << std::endl;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<hemi_light> > factory(
			k3d::uuid(0xa0661dc7, 0x52cd4990, 0x8e6a0aa8, 0x87bdd89d),
			"YafRayHemiLight",
			"YafRay Hemi Light",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_power;
	k3d_data_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_samples;
};

k3d::iplugin_factory& point_light_factory()
{
	return point_light::get_factory();
}

k3d::iplugin_factory& sun_light_factory()
{
	return sun_light::get_factory();
}

k3d::iplugin_factory& soft_light_factory()
{
	return soft_light::get_factory();
}

k3d::iplugin_factory& spot_light_factory()
{
	return spot_light::get_factory();
}

k3d::iplugin_factory& hemi_light_factory()
{
	return hemi_light::get_factory();
}

} // namespace libk3dyafray


