/**
* $Id:
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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.
 *
 * The Original Code is Copyright (C) 2006 Blender Foundation
 * All rights reserved.
 *
 * Contributors: Matt Ebb.
 *
 * ***** END GPL LICENSE BLOCK *****
 */
 
#include <math.h>	// !
#include <string.h>

#include "BLI_arithb.h"
#include "BLI_blenlib.h"	// !
#include "BLI_rand.h"	// !

#include "BKE_global.h" // !

#include "DNA_lamp_types.h"	// !
#include "DNA_material_types.h"
#include "DNA_group_types.h"

#include "RE_shader_ext.h"

#include "lamp.h"		// !
#include "render_types.h"
#include "sampling.h"	// !
#include "shader_util.h"
#include "shadbuf.h"	// !


typedef struct ShadeVars_BlinnPhong {
	float shininess;
} ShadeVars_BlinnPhong;

struct Bsdf;

/* callback for shading */
typedef void (*BSDF_Shade) (struct Bsdf *bsdf, ShadeInfo *si, float *Ci, float *Cl);
typedef float (*BSDF_Sample) (struct Bsdf *bsdf, ShadeInfo *si, float *R, float *u);
typedef void (*BSDF_Pdf) (struct Bsdf *bsdf, ShadeInfo *si, float *Ci, float *R, float *Cl, float *pdf, float *u);

typedef struct Bsdf {
	BSDF_Shade shade_func;
	BSDF_Sample sample_func;
//	BSDF_Pdf pdf_func;
	
	void *shade_vars;
} Bsdf;

static void irradiance_sample_lamp(Render *re, ShadeInfo *si, LampRen *lar, float *Cl)
{
	float pdf;
	float u[2];
	
	u[0] = BLI_thread_frand(si->thread);
	u[1] = BLI_thread_frand(si->thread);
	
	if (lar->type == LA_AREA) {
		lamp_sample_area_solidangle(re, lar, si->P, si->Pl, si->L, Cl, &pdf, u);
	} else if (lar->type == LA_SPOT) {
		lamp_sample_spot_solidangle(re, lar, si->P, si->Pl, si->L, Cl, &pdf, u);
	}
	
	VecCopyf(si->Ln, si->L);
	Normalize(si->Ln);
	si->NdotLn = Inpf(si->N, si->Ln);
}

static void attenuate_sample_lamp(ShadeInfo *si, LampRen *lar, float *Cl)
{
	const float dist = VecLength(si->L);
	float atten=0.f;
	
	if (lar->type == LA_AREA) {
		
		/* area light irradiance is function of visible surface area: lambert's law */
		atten = Inpf(si->Ln, lar->vec);
	} 
	else if (lar->type == LA_SPOT) {
		float lamp_dot;
		float t, i;
		
		lamp_dot = Inpf(si->Ln, lar->vec);

		if (lamp_dot < lar->spotsi) {
			VecMulf(Cl, 0.f);
			return;
		}
		
		/* spotlight cone 'blur' */
		t = lamp_dot - lar->spotsi;
		if(t<lar->spotbl && lar->spotbl!=0.0f) {
			i= t/lar->spotbl;
			t= i*i;
			lamp_dot *= (3.0f*t-2.0f*t*i);
		}
		atten = lamp_dot;
	}
	
	/* inverse square */
	atten *= 1.f / (dist*dist);
	
	VecMulf(Cl, atten);
}

static void lamp_visibility(Render *re, ShadeInfo *si, LampRen *lar, float *Cl)
{
	float vis=1.f;
	
	if(lar->shb) {
		/* shadow buffer */
		LampShadowSubSample *lss= &(lar->shadsamp[si->thread].s[si->sample]);
		
		if(si->depth || lss->samplenr!=si->samplenr) {
			
			vis = testshadowbuf(re, lar->shb, si->P, si->dPdx, si->dPdy, si->NdotLn, 0.0);
			
			if(si->depth==0) {
				lss->shadfac[3] = vis;
				lss->samplenr= si->samplenr;
			}
		} else {
			vis = lss->shadfac[3];
		}
	}
	else if (lar->mode & LA_SHAD_RAY) {
		/* raytrace shadow */
		vis = (1.0f - trace_occlusion(re, si->face, si->obi, si->P, si->Pl, NULL));
	}

	VecMulf(Cl, vis);
}

#define MIN_IRRADIANCE	0.0001f

#define SAMPLE_LIGHTS		1
#define SAMPLE_GEOMETRY		2
#define SAMPLE_ENVIRONMENT	4

static int check_spread(ShadeInfo *si, float *vec, float spread)
{
	float dot;
	
	if (vec == si->N) {
		/* common scenario, sampling hemisphere around N
		* NdotLn is already calculated */
		
		dot = si->NdotLn;
	} else {
		/* get angle between irradiance sample and nominated vector */
		float S[3];
		
		VecSubf(S, si->P, si->Pl);
		Normalize(S);
		dot = Inpf(vec, S);
	}
	
	if ((1.f - dot) < spread/M_PI) return 1;
	else return 0;
}

/* integration */
void directlighting(Render *re, ShadeInfo *si, Bsdf *bsdf, float *Ci, float *vec, float spread, int flag)
{
	if (flag & SAMPLE_LIGHTS) {
		ListBase *lights;
		GroupObject *go;
		LampRen *lar;
		float lights_pdf;
		
		lights= &re->lights;
		lights_pdf = 1.0f / (float)BLI_countlist(lights);
		
		for(go=lights->first; go; go= go->next)
		{
			int i, totsamp;
			float samples_pdf;
			
			lar= go->lampren;	
			totsamp = lar->ray_samp*lar->ray_samp;
			samples_pdf= 1.f / (float)(totsamp);
			
			for (i=0; i < totsamp; i++) {
				float Cl[3] = {0.f, 0.f, 0.f};	/* lamp radiance colour */

				/* get sample location and colour */
				irradiance_sample_lamp(re, si, lar, Cl);
				
				if (!check_spread(si, vec, spread)) continue;
				if (rgb_to_luminance(Cl[0], Cl[1], Cl[2]) < MIN_IRRADIANCE) continue;
				
				/* attenuate for distance and lamp angle */
				attenuate_sample_lamp(si, lar, Cl);
				
				if (rgb_to_luminance(Cl[0], Cl[1], Cl[2]) < MIN_IRRADIANCE) continue;
				
				/* shadowing */
				lamp_visibility(re, si, lar, Cl);
				
				if (rgb_to_luminance(Cl[0], Cl[1], Cl[2]) < MIN_IRRADIANCE) continue;
				
				VecMulf(Cl, samples_pdf);
				
				/* illuminance loop */
				bsdf->shade_func(bsdf, si, Ci, Cl);
			}
		}
	}
	/* testing: only 1 bounce of geom reflection */
	if (flag & SAMPLE_GEOMETRY) {
		if (si->depth ==0) {
			int i, gsamples=64;
			float Cg[3];
			
			for (i=0; i<gsamples; i++)
			{
				float R[3];
				float u[3];
				u[0] = BLI_thread_frand(si->thread);
				u[1] = BLI_thread_frand(si->thread);
				
				bsdf->sample_func(bsdf, si, R, u);
				trace_color(re, si, R, Cg);
				
				VecMulf(Cg, (1.f / gsamples));
				VecAddf(Ci, Ci, Cg);
			}
		}
	}

}

static float f(float *R, float *N)
{
	float r2[3];
	float cont;
	
	VecCopyf(r2, R);
	VecMulf(r2, -1.f);
	cont = Inpf(N, r2);
	CLAMP(cont, 0.f, 1.f);
	return cont;
}

void pathtrace(Render *re, ShadeInfo *si, Bsdf *bsdf, float *Ci)
{
	int i;
	int samples = (si->depth==0)?(G.rt + 1):1;
	float col[3] = {0.f, 0.f, 0.f};
	float Cp[3] = {0.f, 0.f, 0.f};
	
	if ((si->depth > 4) && (BLI_thread_frand(si->thread) > 0.5f)) {
		return;
	}
	
	for (i=0; i < samples; i++) {
		float R[3];
		float u[3];
		
		float r2[3];
		float cont;
		
		u[0] = BLI_thread_frand(si->thread);
		u[1] = BLI_thread_frand(si->thread);
		col[0] = col[1] = col[2] = 0.f;
		
		/* at this point, find direct lighting contribution */
		directlighting(re, si, bsdf, col, si->N, M_PI, SAMPLE_LIGHTS);
		VecAddf(Cp, Cp, col);
		
		/* plus indirect contribution from further along the path */			
		bsdf->sample_func(bsdf, si, R, u);
		trace_color(re, si, R, col);
		
		/* XXX only lambert testing */
		//VecMulf(col, f(R, si->N));
		
		VecAddf(Cp, Cp, col);
	}
	
	VecMulf(Cp, 1.f/samples);
	
	VecAddf(Ci, Ci, Cp);
	
}

void create_shadeinfo(Render *re, ShadeInfo *si, ShadeInput *shi)
{
	VecCopyf(si->P, shi->co);
	VecCopyf(si->dPdx, shi->dxco);
	VecCopyf(si->dPdy, shi->dyco);
	VecCopyf(si->N, shi->vn);
	VecCopyf(si->I, shi->view);

	si->thread = shi->thread;
	si->depth = shi->depth;
	si->sample = shi->sample;
	si->samplenr = shi->samplenr;

	si->face = shi->vlr;
	si->obi = shi->obi;
	
}

/* ********** stuff above here is for convenience, not actually part of this shader ********** */

/* ********** begin shader ********** */
void brdf_lambert_shade(Bsdf *bsdf, ShadeInfo *si, float *Ci, float *Cl)
{
	if (Ci != NULL) {
		/* shade colour */

		/* NdotLn is already > 0 due to the si->N and M_PI inputs */
		float dot = si->NdotLn;
		VecMulf(Cl, dot);
		
		VecMulf(Cl, 1.f/M_PI);	/* rho - normalization factor */
		VecAddf(Ci, Ci, Cl);
	}
}

float brdf_lambert_sample(Bsdf *bsdf, ShadeInfo *si, float *R, float *u)
{
	float s[3], r[3];
	float sn[3], tn[3];	/* XXX use proper tangents */
		
	warp_sampleHemiCosine(s, u);
		
	VecMulf(s, -1.f);
		
	VecOrthoBasisf(si->N, sn, tn);
	local_to_camera(R, s, si->N, sn, tn);
	
	return 1.f/M_PI;
}

void brdf_blinnphong_shade(Bsdf *bsdf, ShadeInfo *si, float *Ci, float *Cl)
{
	ShadeVars_BlinnPhong *sbp = (ShadeVars_BlinnPhong *)bsdf->shade_vars;
	/* shade colour */
	float h[3], dot, phong;
	
	/* blinn-phong, uses half vector */
	VecAddf(h, si->Ln, si->I);
	Normalize(h);
	dot = Inpf(si->N, h);
			
	phong = powf( dot, sbp->shininess);
	
	VecMulf(Cl, phong);
	
	/* rho - normalization factor (approx: see http://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/ ) */
	VecMulf(Cl, (sbp->shininess + 6) / (8 * M_PI));
	
	VecAddf(Ci, Ci, Cl);
}
	
float brdf_blinnphong_sample(Bsdf *bsdf, ShadeInfo *si, float *R, float *u)
{
	ShadeVars_BlinnPhong *sbp = (ShadeVars_BlinnPhong *)bsdf->shade_vars;
	float s[3], n_perturbed[3];
	float sn[3], tn[3];	/* XXX use proper tangents */

	/* XXX try the mathematical form with all the saacos-es instead of my intuitive form :) */

	/* rather than sampling around the reflection vector,
	 * we perturb the normal vector and reflect around it.
	 * This gives same results to using the halfway vector for phong reflectance */
	warp_sampleHemiPhong(s, u, sbp->shininess);
	VecOrthoBasisf(si->N, sn, tn);
	local_to_camera(n_perturbed, s, si->N, sn, tn);
	 
	reflect(R, n_perturbed, si->I);
	Normalize(R);
	
	return (sbp->shininess + 6) / (8 * M_PI);
}

void brdf_perfectspecular_shade(Bsdf *bsdf, ShadeInfo *si, float *Ci, float *Cl)
{
	/* do nothing */
	/* black result for delta distribution */
}

float brdf_perfectspecular_sample(Bsdf *bsdf, ShadeInfo *si, float *R, float *u)	
{	
	/* perfect mirror reflection */
	
	/* XXX maybe can use derivatives/ray differentials 
	 * to jitter these reflection directions
	 * over reflected solid angle, for oversampling? prob doesn't belong here... */
	reflect(R, si->N, si->I);
	Normalize(R);
	
	return 1.f;
}




void shade_diffuse(Render *re, ShadeInput *shi, ShadeResult *shr)
{
	float Ci[3] = {0.f, 0.f, 0.f};
	float Cs[3]= {0.8f, 0.8f, 0.8f}; /* diffuse colour */
	float emit[3]= {0.8f, 0.8f, 0.8f};
	ShadeInfo si;
	
	memset(shr, 0, sizeof(ShadeResult));
	shr->alpha = shr->combined[3] = 1.f;
	
	create_shadeinfo(re, &si, shi);
	
	VecCopyf(Cs, &shi->mat->r);
	VecCopyf(emit, &shi->mat->r);
	VecMulf(emit, shi->mat->emit);
	
	if (G.rt<1) {
		if (shi->mat->spec_shader==0) {
			Bsdf lambert;
			lambert.shade_func = brdf_lambert_shade;
			lambert.sample_func = brdf_lambert_sample;
			
			directlighting(re, &si, &lambert, Ci, si.N, M_PI, SAMPLE_LIGHTS);
			VecMulVecf(Ci, Ci, Cs);
		} else if (shi->mat->spec_shader == MA_SPEC_PHONG) {
			Bsdf blinnphong;
			ShadeVars_BlinnPhong sbp;
			
			blinnphong.shade_func = brdf_blinnphong_shade;
			blinnphong.sample_func = brdf_blinnphong_sample;
			blinnphong.shade_vars = &sbp;
			sbp.shininess = shi->mat->har;
			
			directlighting(re, &si, &blinnphong, Ci, si.N, M_PI, SAMPLE_LIGHTS|SAMPLE_GEOMETRY);
		} else {
			Bsdf specular;
			
			specular.shade_func = brdf_perfectspecular_shade;
			specular.sample_func = brdf_perfectspecular_sample;

			directlighting(re, &si, &specular, Ci, si.N, M_PI, SAMPLE_LIGHTS|SAMPLE_GEOMETRY);
		}
		
		
	}
	else {
		if (shi->mat->spec_shader==0) {
			Bsdf lambert;
			lambert.shade_func = brdf_lambert_shade;
			lambert.sample_func = brdf_lambert_sample;
			
			pathtrace(re, &si, &lambert, Ci);
			VecMulVecf(Ci, Ci, Cs);
		} else if (shi->mat->spec_shader == MA_SPEC_PHONG) {
			Bsdf blinnphong;
			ShadeVars_BlinnPhong sbp;
			
			blinnphong.shade_func = brdf_blinnphong_shade;
			blinnphong.sample_func = brdf_blinnphong_sample;
			blinnphong.shade_vars = &sbp;
			sbp.shininess = shi->mat->har;
			
			pathtrace(re, &si, &blinnphong, Ci);
		} else {
			Bsdf specular;
			
			specular.shade_func = brdf_perfectspecular_shade;
			specular.sample_func = brdf_perfectspecular_sample;
			
			pathtrace(re, &si, &specular, Ci);
		}
	}
	
	VecAddf(Ci, Ci, emit);
	
	
	VecAddf(shr->combined, shr->combined, Ci);
	shr->combined[3] = shr->alpha = 1.f;
	VecCopyf(shr->diff, shr->combined);
}

