#ifndef SCENE_HXX
#define SCENE_HXX

#include "Primitive.hxx"
#include "PerspectiveCamera.hxx"

#include "Triangle.hxx"
#include "SmoothTriangle.hxx"
#include "TexturedSmoothTriangle.hxx"

#include "Shader.hxx"

using namespace std;

#include "BVH.h"
#include "Light.hxx"

class Scene
{
public:
	Vec3f bgColor; // background color

	vector<Primitive *> primitive; // primitives
	std::vector<Light *> lights; // lights

	Camera *camera; 

	BVH* bvhTree;
	Box boundingBox;

	bool m_useBVH;

	Scene(bool aUseBVH)
		: bgColor(Vec3f(0,0,0))
	{
		m_useBVH = aUseBVH;
		camera = NULL;
	};

	virtual ~Scene() 
	{};

#define DECL_FACTORY_FUNC \
	virtual Primitive* createPrimitive( \
		const Vec3f &v1, const Vec3f &v2, const Vec3f &v3, \
		const Vec3f &vn1, const Vec3f &vn2, const Vec3f &vn3, \
		const Vec3f &vt1, const Vec3f &vt2, const Vec3f &vt3 \
		)

	struct PrimitiveFactory
	{
		DECL_FACTORY_FUNC = 0;
	};

	struct TriangleFactory : public PrimitiveFactory
	{
		Shader *shader;
		DECL_FACTORY_FUNC
		{ return new Triangle(v1, v2, v3, shader); }
	};

	struct SmoothTriangleFactory : public PrimitiveFactory
	{
		Shader *shader;
		DECL_FACTORY_FUNC
		{ return new SmoothTriangle(v1, v2, v3, vn1, vn2, vn3, shader); }
	};

	struct TexturedSmoothTriangleFactory : public PrimitiveFactory
	{
		Shader *shader;
		DECL_FACTORY_FUNC
		{ return new TexturedSmoothTriangle(v1, v2, v3, vn1, vn2, vn3, vt1, vt2, vt3, shader); }
	};

	void ParseOBJ(char *fileName, PrimitiveFactory *aFactory);

	// add a new primitive
	void Add(Primitive *prim)
	{ primitive.push_back(prim); };

	void Add(Light *light)
	{ lights.push_back(light); };

	// intersect all contained objects
	virtual bool Intersect(Ray &ray)
	{
		if(m_useBVH)
		{
			return bvhTree->Intersect(ray);
		}
		else
		{
			bool hit = false;

			for (unsigned int i=0;i<primitive.size();i++)
				hit |= primitive[i]->Intersect(ray);

			return hit;
		}
	};

	// find occluder
	bool Occluded(Ray &ray)
	{
		if(m_useBVH)
		{
			return bvhTree->Intersect(ray);
		}
		else
		{
			for (unsigned int i=0;i<primitive.size();i++)
				if (primitive[i]->Occluded(ray))
					return true;

			return false;
		}
	};

	virtual Vec3f GetNormal(Ray &ray)
	{
		std::cerr << "Scene::GetNormal is not defined!" << std::endl;
		return Vec3f(0,0,0); // dummy !
	};

	Box CalcBounds()
	{
		boundingBox.Clear();

		for (int i=0;i<(int)primitive.size();i++) 
		{
			Box box = primitive[i]->CalcBounds();
			boundingBox.Extend(box);
		}

		return boundingBox;
	}

	void BuildAccelStructure()
	{
		if(m_useBVH)
		{
			boundingBox = CalcBounds();
			bvhTree = new BVH(boundingBox, primitive);

			cout << "Bounds are : " << boundingBox.min << " " << boundingBox.max << endl;
		}
	}

	// trace the given ray and shade it and
	// return the color of the shaded ray
	Vec3f RayTrace(Ray &ray)
	{
		static int maxReflections = 3;

		Vec3f result;

		// only shoot another ray if the maximum recursion depth is not exceeded
		if (maxReflections>=0 && Intersect(ray)) {
			maxReflections--; 
			result = ray.hit->shader->Shade(ray); // shade the primitive
			maxReflections++; 

			return result;
		}
		else
			return bgColor; // ray missed geometric primitives
	};
};

#endif
