#ifndef SCENE_HXX
#define SCENE_HXX

#include <vector>

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

#include "Sphere.hxx"
#include "InfinitePlane.hxx"
#include "Quadric.hxx"
#include "Triangle.hxx"

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

#include "Shader.hxx"

#include "BVH.h"

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;
		virtual ~PrimitiveFactory(){};
	};

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

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

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

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

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

	// Add a new light source
        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 < static_cast<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, 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
