/*
    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/bsdf.h>
#include <mitsuba/core/fresolver.h>
#include <mitsuba/hw/basicshader.h>

MTS_NAMESPACE_BEGIN

/*!\plugin{conductor}{Smooth conductor}
 * \order{5}
 * \parameters{
 *     \parameter{material}{\String}{Name of a material preset, see 
 *           \tblref{conductor-iors}.\!\default{\texttt{Cu} / copper}}
 *     \parameter{eta}{\Spectrum}{Real part of the material's index 
 *           of refraction \default{based on the value of \texttt{material}}}
 *     \parameter{k}{\Spectrum}{Imaginary part of the material's index of 
 *             refraction, also known as absorption coefficient.
 *             \default{based on the value of \texttt{material}}}
 *     \parameter{specular\showbreak Reflectance}{\Spectrum\Or\Texture}{
 *        Optional factor used to modulate the reflectance component
 *        \default{1.0}}
 * }
 * \renderings{
 *     \rendering{Measured copper material (the default)}
 *         {bsdf_conductor_copper.jpg}
 *     \rendering{Measured gold material (\lstref{conductor-gold})}
 *         {bsdf_conductor_gold.jpg}
 * }
 
 * This plugin implements a perfectly smooth interface to a conducting material, 
 * such as a metal. For a similar model that instead describes a rough surface 
 * microstructure, take a look at the seperately available 
 * \pluginref{roughconductor} plugin.

 * In contrast to dielectric materials, conductors do not transmit 
 * any light. Their index of refraction is complex-valued and tends to undergo
 * considerable changes throughout the visible color spectrum. 
 * 
 * To faciliate the tedious task of specifying spectrally-varying index of 
 * refraction information, Mitsuba ships with a set of measured data for  
 * several materials, where visible-spectrum information was publicly 
 * available\footnote{
 *   These index of refraction values are identical to the data distributed 
 *   with PBRT. They are originally from the Luxpop database
 *   (\url{www.luxpop.com}) and are based on data by Palik et al. 
 *   \cite{Palik1998Handbook} and measurements of atomic scattering factors 
 *   made by the Center For X-Ray Optics (CXRO) at Berkeley and the 
 *   Lawrence Livermore National Laboratory (LLNL).
 * }. 
 * 
 * Note that \tblref{conductor-iors} also includes several popular optical 
 * coatings, which are not actually conductors. These materials can also 
 * be used with this plugin, though note that the plugin will ignore any 
 * refraction component that the actual material might have had.
 * The table also contains a few birefingent materials, which are split into
 * separate measurements correponding to their two indices of 
 * refraction (named ``ordinary'' and ``extraordinary ray'').
 *
 * When using this plugin, you should ideally compile Mitsuba with support for 
 * spectral rendering to get the most accurate results. While it also works 
 * in RGB mode, the computations will be much more approximate in this case.
 * Also note that this material is one-sided---that is, observed from the 
 * back side, it will be completely black. If this is undesirable, 
 * consider using the \pluginref{twosided} BRDF adapter plugin.\vspace{4mm}
 *
 * \begin{xml}[caption=A material configuration for a smooth conductor with 
 *    measured gold data, label=lst:conductor-gold]
 * <shape type="...">
 *     <bsdf type="conductor">
 *         <string name="material" value="Au"/>
 *     </bsdf>
 * <shape>
 * \end{xml}
 * \vspace{5mm}
 * It is also possible to load spectrally varying index of refraction data from 
 * two external files containing the real and imaginary components,
 * respectively (see \secref{format-spectra} for details on the file 
 * format):
 * \begin{xml}[caption=Rendering a smooth conductor with custom data]
 * <shape type="...">
 *     <bsdf type="conductor">
 *         <spectrum name="eta" filename="conductorIOR.eta.spd"/>
 *         <spectrum name="k" filename="conductorIOR.k.spd"/>
 *     </bsdf>
 * <shape>
 * \end{xml}
 * \vspace{1.5cm}
 * \begin{table}[hb!]
 * \centering
 * \scriptsize
 * \begin{tabular}{>{\ttfamily}llp{1mm}>{\ttfamily}ll}
 * \toprule
 * \rmfamily\textbf{Preset(s)} & \textbf{Description} &&
 * \rmfamily\textbf{Preset(s)} & \textbf{Description}\\
 * \cmidrule{1-2} \cmidrule{4-5}
 * a-C                   & Amorphous carbon &&            Na\_palik             & Sodium \\
 * Ag                    & Silver &&                      Nb, Nb\_palik         & Niobium \\
 * Al                    & Aluminium &&                   Ni\_palik             & Nickel \\
 * AlAs, AlAs\_palik     & Cubic aluminium arsenide &&    Rh, Rh\_palik         & Rhodium \\
 * AlSb, AlSb\_palik     & Cubic aluminium antimonide &&  Se, Se\_palik         & Selenium (ord. ray) \\
 * Au                    & Gold &&                        Se-e, Se-e\_palik     & Selenium (extr. ray) \\
 * Be, Be\_palik         & Polycrystalline beryllium &&   SiC, SiC\_palik       & Hexagonal silicon carbide \\
 * Cr                    & Chromium &&                    SnTe, SnTe\_palik     & Tin telluride\\
 * CsI, CsI\_palik       & Cubic caesium iodide &&        Ta, Ta\_palik         & Tantalum \\
 * Cu, Cu\_palik         & Copper &&                      Te, Te\_palik         & Trigonal tellurium (ord. ray) \\
 * Cu2O, Cu2O\_palik     & Copper (I) oxide &&            Te-e, Te-e\_palik     & Trigonal tellurium (extr. ray) \\
 * CuO, CuO\_palik       & Copper (II) oxide &&           ThF4, ThF4\_palik     & Polycryst. thorium (IV) fluoride \\
 * d-C, d-C\_palik       & Cubic diamond &&               TiC, TiC\_palik       & Polycrystalline titanium carbide \\
 * Hg, Hg\_palik         & Mercury &&                     TiN, TiN\_palik       & Titanium nitride \\
 * HgTe, HgTe\_palik     & Mercury telluride &&           TiO2, TiO2\_palik     & Tetragonal titan. dioxide (ord. ray) \\
 * Ir, Ir\_palik         & Iridium &&                     TiO2-e, TiO2-e\_palik & Tetragonal titan. dioxide (extr. ray) \\
 * K, K\_palik           & Polycrystalline potassium &&   VC, VC\_palik         & Vanadium carbide \\
 * Li, Li\_palik         & Lithium &&                     V\_palik              & Vanadium \\
 * MgO, MgO\_palik       & Magnesium oxide &&             VN, VN\_palik         & Vanadium nitride \\
 * Mo, Mo\_palik         & Molybdenum &&                  W                     & Tungsten\\
 * \bottomrule
 * \end{tabular}
 * \caption{
 *     \label{tbl:conductor-iors}
 *      This table lists all supported materials that can be passed into the
 *      \pluginref{conductor} and \pluginref{roughconductor} plugins. Note that
 *      some of them are not actually conductors---this is not a problem, 
 *      they can be used regardless (though only the reflection component and
 *      no transmission will be simulated). In most cases, there are 
 *      multiple entries for each material, which represent measurements by 
 *      different authors.
 * }
 * \end{table}
 */
class SmoothConductor : public BSDF {
public:
	SmoothConductor(const Properties &props) : BSDF(props) {
		ref<FileResolver> fResolver = Thread::getThread()->getFileResolver();

		m_specularReflectance = new ConstantSpectrumTexture(
			props.getSpectrum("specularReflectance", Spectrum(1.0f)));

		std::string material = props.getString("material", "Cu");
		Spectrum materialEta, materialK;
		materialEta.fromContinuousSpectrum(InterpolatedSpectrum(
			fResolver->resolve("data/ior/" + material + ".eta.spd")));
		materialK.fromContinuousSpectrum(InterpolatedSpectrum(
			fResolver->resolve("data/ior/" + material + ".k.spd")));

		m_eta = props.getSpectrum("eta", materialEta);
		m_k = props.getSpectrum("k", materialK);

		m_components.push_back(EDeltaReflection | EFrontSide);
		m_usesRayDifferentials = false;
	}

	SmoothConductor(Stream *stream, InstanceManager *manager) 
			: BSDF(stream, manager) {
		m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream));
		m_eta = Spectrum(stream);
		m_k = Spectrum(stream);
		m_components.push_back(EDeltaReflection | EFrontSide);
		m_usesRayDifferentials = 
			m_specularReflectance->usesRayDifferentials();
	}

	virtual ~SmoothConductor() { }

	void serialize(Stream *stream, InstanceManager *manager) const {
		BSDF::serialize(stream, manager);
		manager->serialize(stream, m_specularReflectance.get());
		m_eta.serialize(stream);
		m_k.serialize(stream);
	}

	void addChild(const std::string &name, ConfigurableObject *child) {
		if (child->getClass()->derivesFrom(MTS_CLASS(Texture)) && name == "specularReflectance") {
			m_specularReflectance = static_cast<Texture *>(child);
			m_usesRayDifferentials |= m_specularReflectance->usesRayDifferentials();
		} else {
			BSDF::addChild(name, child);
		}
	}

	void configure() {
		BSDF::configure();

		/* Verify the input parameters and fix them if necessary */
		m_specularReflectance = ensureEnergyConservation(
			m_specularReflectance, "specularReflectance", 1.0f);
	}

	/// Reflection in local coordinates
	inline Vector reflect(const Vector &wi) const {
		return Vector(-wi.x, -wi.y, wi.z);
	}

	Spectrum eval(const BSDFQueryRecord &bRec, EMeasure measure) const {
		bool sampleReflection   = (bRec.typeMask & EDeltaReflection)
				&& (bRec.component == -1 || bRec.component == 0);

		/* Verify that the provided direction pair matches an ideal
		   specular reflection; tolerate some roundoff errors */
		if (!sampleReflection || measure != EDiscrete ||
			Frame::cosTheta(bRec.wi) <= 0 ||
			Frame::cosTheta(bRec.wo) <= 0 ||
			std::abs(1 - dot(reflect(bRec.wi), bRec.wo)) > Epsilon)
			return Spectrum(0.0f);

		return m_specularReflectance->getValue(bRec.its) *
			fresnelConductor(Frame::cosTheta(bRec.wi), m_eta, m_k);
	}

	Float pdf(const BSDFQueryRecord &bRec, EMeasure measure) const {
		bool sampleReflection   = (bRec.typeMask & EDeltaReflection)
				&& (bRec.component == -1 || bRec.component == 0);

		/* Verify that the provided direction pair matches an ideal
		   specular reflection; tolerate some roundoff errors */
		if (!sampleReflection || measure != EDiscrete ||
			Frame::cosTheta(bRec.wi) <= 0 ||
			Frame::cosTheta(bRec.wo) <= 0 ||
			std::abs(1 - dot(reflect(bRec.wi), bRec.wo)) > Epsilon)
			return 0.0f;

		return 1.0f;
	}

	Spectrum sample(BSDFQueryRecord &bRec, const Point2 &sample) const {
		bool sampleReflection   = (bRec.typeMask & EDeltaReflection)
				&& (bRec.component == -1 || bRec.component == 0);
		
		if (!sampleReflection || Frame::cosTheta(bRec.wi) <= 0)
			return Spectrum(0.0f);

		bRec.sampledComponent = 0;
		bRec.sampledType = EDeltaReflection;
		bRec.wo = reflect(bRec.wi);

		return m_specularReflectance->getValue(bRec.its) *
			fresnelConductor(Frame::cosTheta(bRec.wi), m_eta, m_k);
	}

	Spectrum sample(BSDFQueryRecord &bRec, Float &pdf, const Point2 &sample) const {
		bool sampleReflection   = (bRec.typeMask & EDeltaReflection)
				&& (bRec.component == -1 || bRec.component == 0);
		
		if (!sampleReflection || Frame::cosTheta(bRec.wi) <= 0)
			return Spectrum(0.0f);

		bRec.sampledComponent = 0;
		bRec.sampledType = EDeltaReflection;
		bRec.wo = reflect(bRec.wi);
		pdf = 1;

		return m_specularReflectance->getValue(bRec.its) *
			fresnelConductor(Frame::cosTheta(bRec.wi), m_eta, m_k);
	}

	std::string toString() const {
		std::ostringstream oss;
		oss << "SmoothConductor[" << endl
			<< "  name = \"" << getName() << "\"," << endl
			<< "  eta = " << m_eta.toString() << "," << endl
			<< "  k = " << m_k.toString() << "," << endl
			<< "  specularReflectance = " << indent(m_specularReflectance->toString()) << endl
			<< "]";
		return oss.str();
	}

	Shader *createShader(Renderer *renderer) const; 

	MTS_DECLARE_CLASS()
private:
	ref<Texture> m_specularReflectance;
	Spectrum m_eta;
	Spectrum m_k;
};

/* Smooth conductor shader -- it is really hopeless to visualize
   this material in the VPL renderer, so let's try to do at least 
   something that suggests the presence of a specularly-reflecting
   conductor.

   The code below is an isotropic version of the shader in
   roughconductor.cpp, with \alpha fixed to 0.4f
*/
class SmoothConductorShader : public Shader {
public:
	SmoothConductorShader(Renderer *renderer, const Texture *specularReflectance,
			const Spectrum &eta, const Spectrum &k) : Shader(renderer, EBSDFShader), 
			m_specularReflectance(specularReflectance) {
		m_specularReflectanceShader = renderer->registerShaderForResource(m_specularReflectance.get());

		/* Compute the reflectance at perpendicular incidence */
		m_R0 = fresnelConductor(1.0f, eta, k);

		m_alpha = 0.4f;
	}

	bool isComplete() const {
		return m_specularReflectanceShader.get() != NULL;
	}

	void putDependencies(std::vector<Shader *> &deps) {
		deps.push_back(m_specularReflectanceShader.get());
	}

	void cleanup(Renderer *renderer) {
		renderer->unregisterShaderForResource(m_specularReflectance.get());
	}

	void resolve(const GPUProgram *program, const std::string &evalName, std::vector<int> &parameterIDs) const {
		parameterIDs.push_back(program->getParameterID(evalName + "_R0", false));
	}

	void bind(GPUProgram *program, const std::vector<int> &parameterIDs, int &textureUnitOffset) const {
		program->setParameter(parameterIDs[0], m_R0);
	}

	void generateCode(std::ostringstream &oss,
			const std::string &evalName,
			const std::vector<std::string> &depNames) const {
		oss << "uniform vec3 " << evalName << "_R0;" << endl
			<< endl
			<< "float " << evalName << "_D(vec3 m, float alpha) {" << endl
			<< "    alpha = 2 / (alpha * alpha) - 2;" << endl
			<< "    return (alpha + 2) * 0.15915 * pow(cosTheta(m), alpha);" << endl
			<< "}" << endl
			<< endl
			<< "float " << evalName << "_G(vec3 m, vec3 wi, vec3 wo) {" << endl
			<< "    if ((dot(wi, m) * cosTheta(wi)) <= 0 || " << endl
			<< "        (dot(wo, m) * cosTheta(wo)) <= 0)" << endl
			<< "        return 0.0;" << endl
			<< "    float nDotM = cosTheta(m);" << endl
			<< "    return min(1.0, min(" << endl
			<< "        abs(2 * nDotM * cosTheta(wo) / dot(wo, m))," << endl
			<< "        abs(2 * nDotM * cosTheta(wi) / dot(wi, m))));" << endl
			<< "}" << endl
			<< endl
			<< "vec3 " << evalName << "_schlick(vec3 wi) {" << endl
			<< "    float ct = cosTheta(wi), ctSqr = ct*ct," << endl
			<< "          ct5 = ctSqr*ctSqr*ct;" << endl
			<< "    return " << evalName << "_R0 + (vec3(1.0) - " << evalName << "_R0) * ct5;" << endl
			<< "}" << endl
			<< endl
			<< "vec3 " << evalName << "(vec2 uv, vec3 wi, vec3 wo) {" << endl
			<< "   if (cosTheta(wi) <= 0 || cosTheta(wo) <= 0)" << endl
			<< "    	return vec3(0.0);" << endl
			<< "   vec3 H = normalize(wi + wo);" << endl
			<< "   vec3 reflectance = " << depNames[0] << "(uv);" << endl
			<< "   float D = " << evalName << "_D(H, " << m_alpha << ")" << ";" << endl
			<< "   float G = " << evalName << "_G(H, wi, wo);" << endl
			<< "   vec3 Fr = " << evalName << "_schlick(wi);" << endl
			<< "   return reflectance * Fr * (D * G / (4*cosTheta(wi)));" << endl
			<< "}" << endl
			<< endl
			<< "vec3 " << evalName << "_diffuse(vec2 uv, vec3 wi, vec3 wo) {" << endl
			<< "    return " << evalName << "_R0 * 0.31831 * cosTheta(wo);"<< endl
			<< "}" << endl;
	}
	MTS_DECLARE_CLASS()
private:
	ref<const Texture> m_specularReflectance;
	ref<Shader> m_specularReflectanceShader;
	Spectrum m_R0;
	Float m_alpha;
};

Shader *SmoothConductor::createShader(Renderer *renderer) const { 
	return new SmoothConductorShader(renderer,
		m_specularReflectance.get(), m_eta, m_k);
}

MTS_IMPLEMENT_CLASS(SmoothConductorShader, false, Shader)
MTS_IMPLEMENT_CLASS_S(SmoothConductor, false, BSDF)
MTS_EXPORT_PLUGIN(SmoothConductor, "Smooth conductor");
MTS_NAMESPACE_END
