/*
 * Copyright 2011-2013 Blender Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CCL_NAMESPACE_BEGIN

/* Direction Emission */
ccl_device_noinline float3 direct_emissive_eval(KernelGlobals *kg,
                                                ShaderData *emission_sd,
                                                LightSample *ls,
                                                ccl_addr_space PathState *state,
                                                float3 I,
                                                differential3 dI,
                                                float t,
                                                float time)
{
	/* setup shading at emitter */
	float3 eval;

	int shader_flag = kernel_tex_fetch(__shader_flag, (ls->shader & SHADER_MASK)*SHADER_SIZE);

#ifdef __BACKGROUND_MIS__
	if(ls->type == LIGHT_BACKGROUND) {
		Ray ray;
		ray.D = ls->D;
		ray.P = ls->P;
		ray.t = 1.0f;
#  ifdef __OBJECT_MOTION__
		ray.time = time;
#  endif
		ray.dP = differential3_zero();
		ray.dD = dI;

		shader_setup_from_background(kg, emission_sd, &ray);

		path_state_modify_bounce(state, true);
		eval = shader_eval_background(kg, emission_sd, state, 0, SHADER_CONTEXT_EMISSION);
		path_state_modify_bounce(state, false);
	}
	else
#endif
	if(shader_flag & SD_HAS_CONSTANT_EMISSION)
	{
		eval.x = __int_as_float(kernel_tex_fetch(__shader_flag, (ls->shader & SHADER_MASK)*SHADER_SIZE + 2));
		eval.y = __int_as_float(kernel_tex_fetch(__shader_flag, (ls->shader & SHADER_MASK)*SHADER_SIZE + 3));
		eval.z = __int_as_float(kernel_tex_fetch(__shader_flag, (ls->shader & SHADER_MASK)*SHADER_SIZE + 4));
		if((ls->prim != PRIM_NONE) && dot(ls->Ng, I) < 0.0f) {
			ls->Ng = -ls->Ng;
		}
	}
	else
	{
		shader_setup_from_sample(kg, emission_sd,
		                         ls->P, ls->Ng, I,
		                         ls->shader, ls->object, ls->prim,
		                         ls->u, ls->v, t, time, false);

		ls->Ng = ccl_fetch(emission_sd, Ng);

		/* no path flag, we're evaluating this for all closures. that's weak but
		 * we'd have to do multiple evaluations otherwise */
		path_state_modify_bounce(state, true);
		shader_eval_surface(kg, emission_sd, NULL, state, 0.0f, 0, SHADER_CONTEXT_EMISSION);
		path_state_modify_bounce(state, false);

		/* evaluate emissive closure */
		if(ccl_fetch(emission_sd, flag) & SD_EMISSION)
			eval = shader_emissive_eval(kg, emission_sd);
		else
			eval = make_float3(0.0f, 0.0f, 0.0f);
	}
	
	eval *= ls->eval_fac;

	return eval;
}

ccl_device_noinline bool direct_emission(KernelGlobals *kg,
                                         ShaderData *sd,
                                         ShaderData *emission_sd,
                                         LightSample *ls,
                                         ccl_addr_space PathState *state,
                                         Ray *ray,
                                         BsdfEval *eval,
                                         bool *is_lamp)
{
	if(ls->pdf == 0.0f)
		return false;

	/* todo: implement */
	differential3 dD = differential3_zero();

	/* evaluate closure */

	float3 light_eval = direct_emissive_eval(kg,
	                                         emission_sd,
	                                         ls,
	                                         state,
	                                         -ls->D,
	                                         dD,
	                                         ls->t,
	                                         ccl_fetch(sd, time));

	if(is_zero(light_eval))
		return false;

	/* evaluate BSDF at shading point */

#ifdef __VOLUME__
	if(ccl_fetch(sd, prim) != PRIM_NONE)
		shader_bsdf_eval(kg, sd, ls->D, eval, ls->pdf, ls->shader & SHADER_USE_MIS);
	else {
		float bsdf_pdf;
		shader_volume_phase_eval(kg, sd, ls->D, eval, &bsdf_pdf);
		if(ls->shader & SHADER_USE_MIS) {
			/* Multiple importance sampling. */
			float mis_weight = power_heuristic(ls->pdf, bsdf_pdf);
			light_eval *= mis_weight;
		}
	}
#else
	shader_bsdf_eval(kg, sd, ls->D, eval, ls->pdf, ls->shader & SHADER_USE_MIS);
#endif

	bsdf_eval_mul(eval, light_eval/ls->pdf);

#ifdef __PASSES__
	/* use visibility flag to skip lights */
	if(ls->shader & SHADER_EXCLUDE_ANY) {
		if(ls->shader & SHADER_EXCLUDE_DIFFUSE) {
			eval->diffuse = make_float3(0.0f, 0.0f, 0.0f);
			eval->subsurface = make_float3(0.0f, 0.0f, 0.0f);
		}
		if(ls->shader & SHADER_EXCLUDE_GLOSSY)
			eval->glossy = make_float3(0.0f, 0.0f, 0.0f);
		if(ls->shader & SHADER_EXCLUDE_TRANSMIT)
			eval->transmission = make_float3(0.0f, 0.0f, 0.0f);
		if(ls->shader & SHADER_EXCLUDE_SCATTER)
			eval->scatter = make_float3(0.0f, 0.0f, 0.0f);
	}
#endif

	if(bsdf_eval_is_zero(eval))
		return false;

	if(ls->shader & SHADER_CAST_SHADOW) {
		/* setup ray */
		bool transmit = (dot(ccl_fetch(sd, Ng), ls->D) < 0.0f);
		ray->P = ray_offset(ccl_fetch(sd, P), (transmit)? -ccl_fetch(sd, Ng): ccl_fetch(sd, Ng));

		if(ls->t == FLT_MAX) {
			/* distant light */
			ray->D = ls->D;
			ray->t = ls->t;
		}
		else {
			/* other lights, avoid self-intersection */
			ray->D = ray_offset(ls->P, ls->Ng) - ray->P;
			ray->D = normalize_len(ray->D, &ray->t);
		}

		ray->dP = ccl_fetch(sd, dP);
		ray->dD = differential3_zero();
	}
	else {
		/* signal to not cast shadow ray */
		ray->t = 0.0f;
	}

	/* return if it's a lamp for shadow pass */
	*is_lamp = (ls->prim == PRIM_NONE && ls->type != LIGHT_BACKGROUND);

	return true;
}

/* Indirect Primitive Emission */

ccl_device_noinline float3 indirect_primitive_emission(KernelGlobals *kg, ShaderData *sd, float t, int path_flag, float bsdf_pdf)
{
	/* evaluate emissive closure */
	float3 L = shader_emissive_eval(kg, sd);

#ifdef __HAIR__
	if(!(path_flag & PATH_RAY_MIS_SKIP) && (ccl_fetch(sd, flag) & SD_USE_MIS) && (ccl_fetch(sd, type) & PRIMITIVE_ALL_TRIANGLE))
#else
	if(!(path_flag & PATH_RAY_MIS_SKIP) && (ccl_fetch(sd, flag) & SD_USE_MIS))
#endif
	{
		/* multiple importance sampling, get triangle light pdf,
		 * and compute weight with respect to BSDF pdf */
		float pdf = triangle_light_pdf(kg, ccl_fetch(sd, Ng), ccl_fetch(sd, I), t);
		float mis_weight = power_heuristic(bsdf_pdf, pdf);

		return L*mis_weight;
	}

	return L;
}

/* Indirect Lamp Emission */

ccl_device_noinline bool indirect_lamp_emission(KernelGlobals *kg,
                                                ShaderData *emission_sd,
                                                ccl_addr_space PathState *state,
                                                Ray *ray,
                                                float3 *emission)
{
	bool hit_lamp = false;

	*emission = make_float3(0.0f, 0.0f, 0.0f);

	for(int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
		LightSample ls;

		if(!lamp_light_eval(kg, lamp, ray->P, ray->D, ray->t, &ls))
			continue;

#ifdef __PASSES__
		/* use visibility flag to skip lights */
		if(ls.shader & SHADER_EXCLUDE_ANY) {
			if(((ls.shader & SHADER_EXCLUDE_DIFFUSE) && (state->flag & PATH_RAY_DIFFUSE)) ||
			   ((ls.shader & SHADER_EXCLUDE_GLOSSY) &&
			    ((state->flag & (PATH_RAY_GLOSSY|PATH_RAY_REFLECT)) == (PATH_RAY_GLOSSY|PATH_RAY_REFLECT))) ||
			   ((ls.shader & SHADER_EXCLUDE_TRANSMIT) && (state->flag & PATH_RAY_TRANSMIT)) ||
			   ((ls.shader & SHADER_EXCLUDE_SCATTER) && (state->flag & PATH_RAY_VOLUME_SCATTER)))
				continue;
		}
#endif

		float3 L = direct_emissive_eval(kg,
		                                emission_sd,
		                                &ls,
		                                state,
		                                -ray->D,
		                                ray->dD,
		                                ls.t,
		                                ray->time);

#ifdef __VOLUME__
		if(state->volume_stack[0].shader != SHADER_NONE) {
			/* shadow attenuation */
			Ray volume_ray = *ray;
			volume_ray.t = ls.t;
			float3 volume_tp = make_float3(1.0f, 1.0f, 1.0f);
			kernel_volume_shadow(kg, emission_sd, state, &volume_ray, &volume_tp);
			L *= volume_tp;
		}
#endif

		if(!(state->flag & PATH_RAY_MIS_SKIP)) {
			/* multiple importance sampling, get regular light pdf,
			 * and compute weight with respect to BSDF pdf */
			float mis_weight = power_heuristic(state->ray_pdf, ls.pdf);
			L *= mis_weight;
		}

		*emission += L;
		hit_lamp = true;
	}

	return hit_lamp;
}

/* Indirect Background */

ccl_device_noinline float3 indirect_background(KernelGlobals *kg,
                                               ShaderData *emission_sd,
                                               ccl_addr_space PathState *state,
                                               ccl_addr_space Ray *ray)
{
#ifdef __BACKGROUND__
	int shader = kernel_data.background.surface_shader;

	/* use visibility flag to skip lights */
	if(shader & SHADER_EXCLUDE_ANY) {
		if(((shader & SHADER_EXCLUDE_DIFFUSE) && (state->flag & PATH_RAY_DIFFUSE)) ||
		   ((shader & SHADER_EXCLUDE_GLOSSY) &&
		    ((state->flag & (PATH_RAY_GLOSSY|PATH_RAY_REFLECT)) == (PATH_RAY_GLOSSY|PATH_RAY_REFLECT))) ||
		   ((shader & SHADER_EXCLUDE_TRANSMIT) && (state->flag & PATH_RAY_TRANSMIT)) ||
		   ((shader & SHADER_EXCLUDE_CAMERA) && (state->flag & PATH_RAY_CAMERA)) ||
		   ((shader & SHADER_EXCLUDE_SCATTER) && (state->flag & PATH_RAY_VOLUME_SCATTER)))
			return make_float3(0.0f, 0.0f, 0.0f);
	}

	/* evaluate background closure */
#  ifdef __SPLIT_KERNEL__
	Ray priv_ray = *ray;
	shader_setup_from_background(kg, emission_sd, &priv_ray);
#  else
	shader_setup_from_background(kg, emission_sd, ray);
#  endif

	path_state_modify_bounce(state, true);
	float3 L = shader_eval_background(kg, emission_sd, state, state->flag, SHADER_CONTEXT_EMISSION);
	path_state_modify_bounce(state, false);

#ifdef __BACKGROUND_MIS__
	/* check if background light exists or if we should skip pdf */
	int res = kernel_data.integrator.pdf_background_res;

	if(!(state->flag & PATH_RAY_MIS_SKIP) && res) {
		/* multiple importance sampling, get background light pdf for ray
		 * direction, and compute weight with respect to BSDF pdf */
		float pdf = background_light_pdf(kg, ray->P, ray->D);
		float mis_weight = power_heuristic(state->ray_pdf, pdf);

		return L*mis_weight;
	}
#endif

	return L;
#else
	return make_float3(0.8f, 0.8f, 0.8f);
#endif
}

CCL_NAMESPACE_END

