#include "ai.h"
#include <string>

using namespace std;

AI_SHADER_NODE_EXPORT_METHODS(apBoolean)

enum BooleaOp {
	kUnion = 0,
	kDifference,
	kIntersection
};

enum apBooleanParams {
	p_operator,
	p_object_a,
	p_set_a,
	p_shader_a,
	p_object_b,
	p_set_b,
	p_shader_b,
	p_swap,
	p_max_depth
};

static const char* OperatorNames[] = {
	"Union (A+B)",
	"Difference (A-B)",
	"Intersection (A & B)",
	NULL
};

struct ShaderData
{
	int boolean_operator;
	bool swap;
	int max_depth;
};

typedef struct 
{
	bool inside_a;
	bool inside_b;
	bool p_inside_a;
	bool p_inside_b;

} RayState;

node_parameters
{
	AiParameterEnum("operator", 0, OperatorNames);
	AiParameterNode("object_a", NULL);
	AiParameterStr("trace_set_a", "");
	AiParameterClosure("shader_a");
	AiParameterNode("object_b", NULL);
	AiParameterStr("trace_set_b", "");
	AiParameterClosure("shader_b");
	AiParameterBool("swap", false);
	AiParameterInt("max_depth", 10);
}

node_initialize
{
	ShaderData *data = new ShaderData;
	AiNodeSetLocalData(node, data);
}

node_update
{
	ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);
}

node_finish
{
	ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);
	AiFree(data);
}

shader_evaluate
{
	ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);

	int bop = AiShaderEvalParamBool(p_operator);
	bool swap = AiShaderEvalParamBool(p_swap);
	int max_depth = AiShaderEvalParamInt(p_max_depth);

	AtNode *object_a = (AtNode *)AiShaderEvalParamPtr(p_object_a);
	AtClosureList closure_a = AiShaderEvalParamClosure(p_shader_a);
	AtString object_a_name(AiNodeGetName(object_a));
	//AiMsgInfo("A: %s", object_a_name.c_str());

	AtNode *object_b = (AtNode *)AiShaderEvalParamPtr(p_object_b);
	AtClosureList closure_b = AiShaderEvalParamClosure(p_shader_b);
	AtString object_b_name(AiNodeGetName(object_b));
	//AiMsgInfo("B: %s", object_b_name.c_str());
	AtNode *object = sg->Op;
	AtString object_name(AiNodeGetName(object));

	bool inv_normal = AiV3Dot(-sg->Rd, sg->N) < 0;

	if (sg->Rt & AI_RAY_SHADOW) {

		// First shadow call
		int	first_shadow, cur_first_shadow;
		//int cur_first_shadow = AiStateGetMsgInt(AtString("first_shadow"), &first_shadow) ? 0 : 1;
		if (AiStateGetMsgInt(AtString("first_shadow"), &first_shadow))
			cur_first_shadow = 0;
		else
			cur_first_shadow = 1;

		AiStateSetMsgInt(AtString("first_shadow"), cur_first_shadow);

		//cg_mat
		bool inside_a;
		bool inside_b;
		bool p_inside_a;
		bool p_inside_b;

		/*RayState *shadow_state;
		RayState csg_mat;
		if (!AiStateGetMsgPtr(AtString("shadow_state"), (void**)&shadow_state))
		{
			csg_mat = *shadow_state;
		}*/

		// Initialize it
		if (!AiStateGetMsgBool(AtString("inside_a"), &inside_a))
			inside_a = false;
		if (!AiStateGetMsgBool(AtString("inside_b"), &inside_a))
			inside_b = false;
		if (!AiStateGetMsgBool(AtString("p_inside_a"), &p_inside_a))
			p_inside_a = false;
		if (!AiStateGetMsgBool(AtString("p_inside_b"), &p_inside_a))
			p_inside_b = false;

		if (cur_first_shadow > 0) {
			if (object_name == object_a_name)
				if (!AiV3Dot(sg->Rd, sg->psg->Ng))
					inside_a = true;
			if (object_name == object_b_name)
				if (!AiV3Dot(sg->Rd, sg->psg->Ng))
					inside_b = true;
		}

		bool inside_a_shadow;
		bool inside_b_shadow;
		bool p_inside_a_shadow;
		bool p_inside_b_shadow;

		if (!AiStateGetMsgBool(AtString("inside_a_shadow"), &inside_a_shadow))
			inside_a_shadow = false;
		if (!AiStateGetMsgBool(AtString("inside_b_shadow"), &inside_b_shadow))
			inside_b_shadow = false;
		if (!AiStateGetMsgBool(AtString("p_inside_a_shadow"), &p_inside_a_shadow))
			p_inside_a_shadow = false;
		if (!AiStateGetMsgBool(AtString("p_inside_b_shadow"), &p_inside_b_shadow))
			p_inside_a_shadow = false;

		AtRGB shadow_transparency = AI_RGB_ZERO;

		if (bop == BooleaOp::kUnion) {
			AtClosureList closures;
			closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
			sg->out.CLOSURE() = closures;
			return;
		}

		if (bop == BooleaOp::kDifference) {
			
			if (cur_first_shadow > 0) 
			{
				if (AiV3Dot(sg->Rd, sg->Ng) && (inside_a || inside_b))
				{
					inside_a = p_inside_a;
					inside_b = p_inside_b;
				}

				if (inside_a && inside_b) //inside A & B
				{
					
					if (object_name == object_a_name)
					{
						AiStateSetMsgBool(AtString("inside_a_shadow"), (inv_normal) ? false : true);
						AtClosureList closures;
						closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
						sg->out.CLOSURE() = closures;
						return;
					}
					if (object_name == object_b_name)
					{
						AiStateSetMsgBool(AtString("inside_b_shadow"), (inv_normal) ? false : true);

						/*AtScrSample shadow_sample;
						AtRay ray = AiMakeRay(AI_RAY_SHADOW, sg->P, &sg->Rd, AI_INFINITE, sg);

						if (AiTrace(ray, AI_RGB_WHITE, shadow_sample))
						{
							AtClosureList closures;
							closures.add(AiClosureTransparent(sg, shadow_sample.opacity));
							sg->out.CLOSURE() = closures;
						}*/
						AtClosureList closures;
						closures.add(AiClosureTransparent(sg, AI_RGB_WHITE));
						sg->out.CLOSURE() = closures;
						return;
					}
					// Not match any object names
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
					sg->out.CLOSURE() = closures;
					return;
				}

				if (inside_a)
				{
					if (object_name == object_a_name || object_name == object_b_name)
					{
						if (object_name == object_a_name)
							AiStateSetMsgBool(AtString("inside_a_shadow"), (inv_normal) ? false : true);

						if (object_name == object_b_name)
							AiStateSetMsgBool(AtString("inside_b_shadow"), (inv_normal) ? false : true);

						/*AtScrSample shadow_sample;
						AtRay ray = AiMakeRay(AI_RAY_SHADOW, sg->P, &sg->Rd, AI_INFINITE, sg);

						if (AiTrace(ray, AI_RGB_WHITE, shadow_sample))
						{
							AtClosureList closures;
							closures.add(AiClosureTransparent(sg, shadow_sample.opacity));
							sg->out.CLOSURE() = closures;
						}*/
						AtClosureList closures;
						closures.add(AiClosureTransparent(sg, AI_RGB_WHITE));
						sg->out.CLOSURE() = closures;
					}
					return;
				}

				if (inside_b)
				{
					if (object_name == object_b_name || object_name == object_a_name)
					{
						if (object_name == object_b_name)
							AiStateSetMsgBool(AtString("inside_b_shadow"), (inv_normal) ? false : true);

						if (object_name == object_a_name)
							AiStateSetMsgBool(AtString("inside_a_shadow"), (inv_normal) ? false : true);

						AtClosureList closures;
						closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
						sg->out.CLOSURE() = closures;
					}
					return;
				}
				//
				if (object_name == object_a_name)
				{
					AiStateSetMsgBool(AtString("inside_a_shadow"), (inv_normal) ? false : true);
					/*AtScrSample shadow_sample;
					AtRay ray = AiMakeRay(AI_RAY_SHADOW, sg->P, &sg->Rd, AI_INFINITE, sg);
					if (AiTrace(ray, AI_RGB_WHITE, shadow_sample))
					{
						AtClosureList closures;
						closures.add(AiClosureTransparent(sg, shadow_sample.opacity));
						sg->out.CLOSURE() = closures;
					}*/
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg, AI_RGB_WHITE));
					sg->out.CLOSURE() = closures;
				}
				else
				{
					AiStateSetMsgBool(AtString("inside_b_shadow"), (inv_normal) ? false : true);
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
					sg->out.CLOSURE() = closures;
				}
				AtClosureList closures;
				closures.add(AiClosureTransparent(sg, AI_RGB_WHITE));
				sg->out.CLOSURE() = closures;
				return;
			}
			else // secondary shadow call
			{
				if (inside_a_shadow && inside_b_shadow)
				{
					if (object_name == object_a_name || object_name == object_b_name)
					{
						if (object_name == object_a_name)
							AiStateSetMsgBool(AtString("inside_a_shadow"), AiV3Dot(sg->Rd, sg->Ng) ? false : true);
						
						if (object_name == object_b_name)
							AiStateSetMsgBool(AtString("inside_b_shadow"), AiV3Dot(sg->Rd, sg->Ng) ? false : true);
					}
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
					sg->out.CLOSURE() = closures;
					return;
				}
				
				// inside A
				if (inside_a_shadow)
				{
					if (object_name == object_a_name)
					{
						AiStateSetMsgBool(AtString("inside_a_shadow"), AiV3Dot(sg->Rd, sg->Ng) ? false : true);
						/*AtScrSample shadow_sample;
						AtRay ray = AiMakeRay(AI_RAY_SHADOW, sg->P, &sg->Rd, AI_INFINITE, sg);

						if (AiTrace(ray, AI_RGB_WHITE, shadow_sample))
						{
							AtClosureList closures;
							closures.add(AiClosureTransparent(sg, shadow_sample.opacity));
							sg->out.CLOSURE() = closures;
						}*/
						AtClosureList closures;
						closures.add(AiClosureTransparent(sg, AI_RGB_WHITE));
						sg->out.CLOSURE() = closures;

					}
					if (object_name == object_b_name)
					{
						AiStateSetMsgBool(AtString("inside_a_shadow"), AiV3Dot(sg->Rd, sg->Ng) ? false : true);
						AtClosureList closures;
						closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
						sg->out.CLOSURE() = closures;
					}
					return;
				}
				// inside B
				if (inside_b_shadow)
				{
					if (object_name == object_a_name || object_name == object_b_name)
					{
						if (object_name == object_b_name)
						{
							AiStateSetMsgBool(AtString("inside_b_shadow"), AiV3Dot(sg->Rd, sg->Ng) ? false : true);
							/*AtScrSample shadow_sample;
							AtRay ray = AiMakeRay(AI_RAY_SHADOW, sg->P, &sg->Rd, AI_INFINITE, sg);

							if (AiTrace(ray, AI_RGB_WHITE, shadow_sample))
							{
								AtClosureList closures;
								closures.add(AiClosureTransparent(sg, shadow_sample.opacity));
								sg->out.CLOSURE() = closures;
							}*/
							AtClosureList closures;
							closures.add(AiClosureTransparent(sg, AI_RGB_WHITE));
							sg->out.CLOSURE() = closures;
						}
						if (object_name == object_a_name)
						{
							AiStateSetMsgBool(AtString("inside_a_shadow"), AiV3Dot(sg->Rd, sg->Ng) ? false : true);
							AtClosureList closures;
							closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
							sg->out.CLOSURE() = closures;
						}
					}
					return;
				}
				// inside None
				if (object_name == object_a_name)
				{
					AiStateSetMsgBool(AtString("inside_a_shadow"), AiV3Dot(sg->Rd, sg->Ng) ? false : true);
					/*AtScrSample shadow_sample;
					AtRay ray = AiMakeRay(AI_RAY_SHADOW, sg->P, &sg->Rd, AI_INFINITE, sg);

					if (AiTrace(ray, AI_RGB_WHITE, shadow_sample))
					{
						AtClosureList closures;
						closures.add(AiClosureTransparent(sg, shadow_sample.opacity));
						sg->out.CLOSURE() = closures;
					}*/
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg, AI_RGB_WHITE));
					sg->out.CLOSURE() = closures;
					return;
				} 
				else 
				{
					AiStateSetMsgBool(AtString("inside_b_shadow"), AiV3Dot(sg->Rd, sg->Ng) ? false : true);
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
					sg->out.CLOSURE() = closures;
					return;
				}
				AtClosureList closures;
				closures.add(AiClosureTransparent(sg, AI_RGB_WHITE));
				sg->out.CLOSURE() = closures;
			}
			return;
		}
		AtClosureList closures;
		//closures.add(AiClosureTransparent(sg, AI_RGB_ZERO));
		closures.add(AiClosureTransparent(sg, shadow_transparency));
		sg->out.CLOSURE() = closures;
		return;
	}

	bool inside_a = false;
	bool inside_b = false;

	AiStateGetMsgBool(AtString("inside_a"), &inside_a);
	AiStateGetMsgBool(AtString("inside_b"), &inside_b);

	// Union
	if (bop == BooleaOp::kUnion)
	{
		if (inside_a && inside_b)
		{
			if (object_name == object_a_name)
				AiStateSetMsgBool(AtString("inside_a"), (inv_normal) ? false : true);
			else if (object_name == object_b_name)
				AiStateSetMsgBool(AtString("inside_b"), (inv_normal) ? false : true);
			else
			{
				AtClosureList closures;
				closures.add(AiClosureEmission(sg, AI_RGB_RED));
				return;
			}
			AtClosureList closures;
			closures.add(AiClosureTransparent(sg));
			sg->out.CLOSURE() = closures;
			return;
		}
		else
		{
			if (inside_a)
			{
				if (object_name == object_a_name)
				{
					AiStateSetMsgBool(AtString("inside_a"), (inv_normal) ? false : true);
					sg->out.CLOSURE() = closure_a;
					return;
				}
				else
				{
					AiStateSetMsgBool(AtString("inside_b"), (inv_normal) ? false : true);
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg));
					sg->out.CLOSURE() = closures;
					return;
				}
			}
			else
			{
				if (object_name == object_b_name)
				{
				}
			}
		}
	}
	// Difference
	if (bop == BooleaOp::kDifference) {
		// Inside A & B
		if (inside_a == true && inside_b == true) 
		{
			if (object_name == object_a_name) 
			{
				AiStateSetMsgBool(AtString("inside_a"), (inv_normal) ? false : true);
				sg->out.CLOSURE() = closure_a;
				return;
			}
			if (object_name == object_b_name) {
				AiStateSetMsgBool(AtString("inside_b"), (inv_normal) ? false : true);
				AtClosureList closures;
				closures.add(AiClosureTransparent(sg));
				sg->out.CLOSURE() = closures;
				return;
			}
			
		} else {
			// Inside A
			if (inside_a) {
				if (object_name == object_a_name) {
					if (inv_normal)
						AiStateSetMsgBool(AtString("inside_a"), false);
					else
						AiStateSetMsgBool(AtString("inside_a"), true);
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg));
					sg->out.CLOSURE() = closures;
					return;

				} else {
					if (inv_normal)
						AiStateSetMsgBool(AtString("inside_b"), false);
					else
						AiStateSetMsgBool(AtString("inside_b"), true);
					AtClosureList closures;
					closures.add(AiClosureTransparent(sg));
					sg->out.CLOSURE() = closures;
					return;
				}
			} else {
				// Inside B
				if (inside_b) { 
					if (object_name == object_b_name) {
						if (inv_normal)
							AiStateSetMsgBool(AtString("inside_b"), false);
						else
							AiStateSetMsgBool(AtString("inside_b"), true);
						sg->out.CLOSURE() = closure_b;
						return;

					} else {
						if (inv_normal)
							AiStateSetMsgBool(AtString("inside_a"), false);
						else
							AiStateSetMsgBool(AtString("inside_a"), true);
						sg->out.CLOSURE() = closure_a;
						return;
					}
					
				} else { 
					// Inside None
					if (object_name == object_a_name) {
						if (inv_normal == false)
							AiStateSetMsgBool(AtString("inside_a"), true);
						
						AtClosureList closures;
						closures.add(AiClosureTransparent(sg));
						sg->out.CLOSURE() = closures;
						return;
					} else {
						if (inv_normal == false)
							AiStateSetMsgBool(AtString("inside_b"), true);
						sg->out.CLOSURE() = closure_b;
						return;
					}
				}
			}
		}
	}
	if (bop == BooleaOp::kIntersection){

	}
	sg->out.CLOSURE() = AiClosureEmission(sg, AtRGB(1.0, 0, 0));
}
node_loader
{
	if (i > 0)
		return false;

	node->methods = apBoolean;
	node->output_type = AI_TYPE_CLOSURE;
	node->name = "apBoolean";
	node->node_type = AI_NODE_SHADER;
	strcpy(node->version, AI_VERSION);
	return true;
}