/*
    This file is part of Mitsuba, a physically based rendering system.

    Copyright (c) 2007-2011 by Wenzel Jakob and others.

    Mitsuba is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Mitsuba 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, see <http://www.gnu.org/licenses/>.
*/

#include <mitsuba/render/scene.h>

MTS_NAMESPACE_BEGIN

/**
 * Simple directional luminaire. In an untransformed state, the luminaire 
 * sends light into the positive Z direction. To change the direction,
 * either specify a "toWorld" transformation or set the "direction" parameter.
 */
class DirectionalLuminaire : public Luminaire {
public:
	DirectionalLuminaire(const Properties &props) : Luminaire(props) {
		m_intensity = props.getSpectrum("intensity", Spectrum(1.0f));
		m_diskRadius = 0;
		m_type = EDeltaDirection;
		if (props.hasProperty("toWorld") && props.hasProperty("direction"))
			Log(EError, "Please specify either a direction or a luminaire "
				"to world space transformation");
		else if (props.hasProperty("direction"))
			m_direction = normalize(props.getVector("direction"));
		else 
			m_direction = normalize(m_luminaireToWorld(Vector(0, 0, 1)));
	}

	DirectionalLuminaire(Stream *stream, InstanceManager *manager) 
		: Luminaire(stream, manager) {
		m_direction = Vector(stream);
		m_intensity = Spectrum(stream);
		m_diskOrigin = Point(stream);
		m_diskRadius = stream->readFloat();
		configure();
	}

	void configure() {
		m_surfaceArea = m_diskRadius * m_diskRadius * M_PI;
		m_invSurfaceArea = 1.0f / m_surfaceArea;
	}

	void serialize(Stream *stream, InstanceManager *manager) const {
		Luminaire::serialize(stream, manager);
		m_direction.serialize(stream);
		m_intensity.serialize(stream);
		m_diskOrigin.serialize(stream);
		stream->writeFloat(m_diskRadius);
	}

	void preprocess(const Scene *scene) {
		/* Get the scene's bounding sphere and slightly enlarge it */
		BSphere bsphere = scene->getBSphere();
		m_diskRadius = bsphere.radius;
		m_diskOrigin = bsphere.center - m_direction * bsphere.radius;

		configure();
	}

	Spectrum getPower() const {
		return m_intensity * m_surfaceArea;
	}

	Float pdf(const Point &p, const LuminaireSamplingRecord &lRec, bool delta) const {
		/* PDF is a delta function - zero probability when a sample point was not
		   generated using sample() */
		return delta ? 1.0f : 0.0f;
	}

	void sample(const Point &p, LuminaireSamplingRecord &lRec,
		const Point2 &sample) const {
		lRec.sRec.p = p - m_direction * (2 * m_diskRadius);
		lRec.d = m_direction;
		lRec.luminaire = this;
		lRec.pdf = 1.0f;
		lRec.value = m_intensity;
	}

	void sampleEmission(EmissionRecord &eRec, const Point2 &sample1, const Point2 &sample2) const {
		Point2 posOnDisk = squareToDiskConcentric(sample1) * m_diskRadius;
		eRec.sRec.p = m_diskOrigin + Frame(m_direction).toWorld(Vector(posOnDisk.x, posOnDisk.y, 0));
		eRec.d = m_direction;
		eRec.pdfArea = m_invSurfaceArea;
		eRec.pdfDir = 1;
		eRec.value = m_intensity;
	}

	void sampleEmissionArea(EmissionRecord &eRec, const Point2 &sample) const {
		Point2 posOnDisk = squareToDiskConcentric(sample) * m_diskRadius;
		eRec.sRec.p = m_diskOrigin + Frame(m_direction).toWorld(Vector(posOnDisk.x, posOnDisk.y, 0));
		eRec.pdfArea = m_invSurfaceArea;
		eRec.value = m_intensity;
	}

	Spectrum sampleEmissionDirection(EmissionRecord &eRec, const Point2 &sample) const {
		eRec.d = m_direction;
		eRec.pdfDir = 1;
		return Spectrum(1.0f);
	}

	Spectrum fArea(const EmissionRecord &eRec) const {
		return m_intensity;
	}

	Spectrum fDirection(const EmissionRecord &eRec) const {
		/* Directional luminaire beam is not part of the scene */
		return Spectrum(0.0f);
	}

	void pdfEmission(EmissionRecord &eRec, bool delta) const {
		eRec.pdfArea = delta ? 0.0f : m_invSurfaceArea;
		eRec.pdfDir = delta ? 1.0f : 0.0f;
		eRec.value = m_intensity;
	}

	std::string toString() const {
		std::ostringstream oss;
		oss << "DirectionalLuminaire[" << std::endl
			<< "  name = \"" << m_name << "\"," << std::endl
			<< "  intensity = " << m_intensity.toString() << "," << std::endl
			<< "  power = " << getPower().toString() << "," << std::endl
			<< "  direction = " << m_direction.toString() 
			<< "]";
		return oss.str();
	}

	MTS_DECLARE_CLASS()
private:
	Spectrum m_intensity;
	Vector m_direction;
	Float m_surfaceArea;
	Float m_invSurfaceArea;
	Float m_diskRadius;
	Point m_diskOrigin;
};

MTS_IMPLEMENT_CLASS_S(DirectionalLuminaire, false, Luminaire)
MTS_EXPORT_PLUGIN(DirectionalLuminaire, "Directional luminaire");
MTS_NAMESPACE_END
