#define MGML_GRAPHICS_GUARDIAN

#ifndef MGML_MATH_GUARDIAN
	#include "MGML_MATH.h"
#endif

#ifndef MGML_GUARDIAN
	#include "MGML.h"
#endif


//#ifndef KD_TREE_GUARDIAN
//	#include "kd_tree.h"
//#endif

//#include "MGML_MESH.h"

namespace MGML_GRAPHICS
{
 
template <int n_,class T> 
struct VERTEX;

template <int   n, 
		  class T, 
		  class INDEX> class TRIANGLE;


template <int   dim, 
		  class T,
		  class HAL> class MESH;

		  class D3D_HAL{};
		  class OpenGL_HAL{};

template <class T> struct OTC;

typedef MGML_MATH::VECTOR<3,unsigned char> RGB256;

template<int n>
inline universal_call RGB256 REAL_COLOR_TO_RGB256(const MGML_MATH::VECTOR<n,float>& real_color)
{
 //typedef st_assert<(n >= 3)> n_must_be_gt_3;

 float  r = real_color.x*255;
 float  g = real_color.y*255;
 float  b = real_color.z*255;

 ASSERT(r < 256 && r >= 0);
 ASSERT(g < 256 && g >= 0);
 ASSERT(b < 256 && b >= 0);

 RGB256 res;
 res.set(r,g,b);

 return res;
}

template<int n>
inline universal_call MGML_MATH::VECTOR<n,float> RGB256_TO_REAL_COLOR(const RGB256& clr)
{
	MGML_MATH::VECTOR<n,float> res;
	res.x = clr.x;
	res.y = clr.y;
	res.z = clr.z;
	
	res *= (1.0f/255.0f);

	return res;
}

inline universal_call UINT RGB256_TO_UINT32(const RGB256& clr)
{
	unsigned int result = 0;
	unsigned int red   = clr.x;
	unsigned int green = clr.y;
	unsigned int blue  = clr.z;

	result = red | (green << 8) | (blue << 16);

	return result;
}


inline universal_call MGML_MATH::VECTOR<4,float> UINT32_TO_REAL_COLOR(unsigned int clr)
{
	unsigned char red_b	  = (clr & 0x000000FF);
	unsigned char green_b = (clr & 0x0000FF00) >> 8;
	unsigned char blue_b  = (clr & 0x00FF0000) >> 16;

	float red	= (float)red_b;
	float green = (float)green_b;
	float blue	= (float)blue_b;

	float inv_255 = 1.0f/255.0f;

	MGML_MATH::VECTOR<4,float> res;
	res.set(inv_255*red,inv_255*green,inv_255*blue,1);

	return res;
}

template<int n>
inline universal_call unsigned int REAL_COLOR_TO_UNT32(const MGML_MATH::VECTOR<n,float>& real_color)
{
	float  r = real_color.M[0]*255.0f;
	float  g = real_color.M[1]*255.0f;
	float  b = real_color.M[2]*255.0f;

	//ASSERT(r < 256.0f && r >= 0.0f);
	//ASSERT(g < 256.0f && g >= 0.0f);
	//ASSERT(b < 256.0f && b >= 0.0f);

	unsigned char red = (unsigned char)r, green = (unsigned char)g, blue = (unsigned char)b;
	return red | (green << 8) | (blue << 16);
}


//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
class HVS_Color
{
public:
  
#ifndef __CUDACC__

  HVS_Color(float h, float s, float v) 
    { set(h,s,v); }
  HVS_Color()
    {set(0,0,0);}

#endif

  universal_call inline void setFromRGB(const MGML_MATH::VECTOR<3,float>& rgb)
  {
#ifndef __CUDACC__
    if(rgb.x < 0.0f || rgb.x > 1.0f)
      throw MGML_ERROR::Error("HVS_Color::fromRGB: incorrect r value");

    if(rgb.y < 0.0f || rgb.y > 1.0f)
      throw MGML_ERROR::Error("HVS_Color::fromRGB: incorrect g value");

    if(rgb.z < 0.0f || rgb.z > 1.0f)
      throw MGML_ERROR::Error("HVS_Color::fromRGB: incorrect b value");

#endif
    float r = rgb.x;
    float g = rgb.y;
    float b = rgb.z;

    float cMax = MGML_MATH::MAX(r,g,b);
    float cMin = MGML_MATH::MIN(r,g,b);

    if(cMax-cMin < MGML_MATH::EPSILON_E6)
      M[0] = 0.0f;
    else if(cMax == r && g >= b)
      M[0] = 60.0f*(g-b)/(cMax-cMin);
    else if(cMax == r && g < b)
      M[0] = 60.0f*(g-b)/(cMax-cMin) + 360;
    else if(cMax == g)
      M[0] = 60.0f*(b-r)/(cMax-cMin) + 120;
    else if(cMax == b)
      M[0] = 60.0f*(r-g)/(cMax-cMin) + 240;
    else
    {
#ifndef __CUDACC__
      throw MGML_ERROR::Error("HVS_Color::setFromRGB: unreached code!");
#endif
    }

    if(cMax == 0)
      M[1] = 0;
    else
      M[1] = 1.0f - cMin/cMax;

    M[2] = cMax;
  }

  universal_call inline MGML_MATH::VECTOR<3,float> toRGB()
  {
    int Hi = (((int)getH())/60) % 6;
    float f = getH()/60.0f - (((int)getH())/60);

    float p = getV()*(1.0f - getS());
    float q = getV()*(1.0f - f*getS());
    float t = getV()*(1.0f - (1.0f-f)*getS());

    float R,G,B;

    switch(Hi)
    {
    case 0:
      R = getV(); 	G = t; 	B = p;
      break;

    case 1:
      R = q; 	G = getV(); 	B = p;
      break;

    case 2:
      R = p; 	G = getV(); 	B = t;
      break;

    case 3:
      R = p; 	G = q; 	B = getV();
      break;

    case 4:
      R = t; 	G = p; 	B = getV();
      break;

    case 5:
      R = getV(); 	G = p; 	B = q;
      break;

    default:
#ifndef __CUDACC__
      throw MGML_ERROR::Error("HVS_Color::toRGB: unreached code!");
#endif
      break;
    };

    MGML_MATH::VECTOR<3,float> res;
    res.M[0] = R;
    res.M[1] = G;
    res.M[2] = B;
    return res;
  }


  universal_call inline void Saturate(float a_S = 1.0f)
  {
#ifndef __CUDACC__
    if(a_S < 0 || a_S > 1.0f)
      throw MGML_ERROR::Error("HVS_Color::Saturate: incorrect saturation value, must be between [0..1]");
#endif
    const float WHITE_TRESHOLD = 0.05f;
    if(getS() < a_S && getS() > WHITE_TRESHOLD)
      setS(a_S);
  }

  universal_call inline void set(float a_h, float a_s, float a_v)
  {
    setH(a_h);
    setS(a_s);
    setV(a_v);
  }

  universal_call inline void setH(float val)
  {
#ifndef __CUDACC__
    if(val < 0.0f || val >= 360.0f)
      throw MGML_ERROR::Error("HVS_Color::setH: incorrect h value");
#endif
    M[0] = val;
  }

  universal_call inline void setS(float val)
  {
#ifndef __CUDACC__
    if(val < 0.0f || val > 1.0f)
      throw MGML_ERROR::Error("HVS_Color::setH: incorrect s value");
#endif
    M[1] = val;
  }

  universal_call inline void setV(float val)
  {
#ifndef __CUDACC__
    if(val < 0.0f || val > 1.0f)
      throw MGML_ERROR::Error("HVS_Color::setH: incorrect v value");
#endif
    M[2] = val;
  }

  //universal_call inline float& h() {return M[0];}
  //universal_call inline float& s() {return M[1];}
  //universal_call inline float& v() {return M[2];}

  universal_call inline float getH() const {return M[0];}
  universal_call inline float getS() const {return M[1];}
  universal_call inline float getV() const {return M[2];}

protected:
  float M[3];

};


//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
class XYZ_Color
{

public:

#ifndef __CUDACC__
  XYZ_Color () { xyz.set(0,0,0);}
  XYZ_Color (float a_x, float a_y, float a_z) { xyz.set(a_x, a_y, a_z);}
  XYZ_Color(const MGML_MATH::VECTOR<3,float>& a_xyz) {xyz = a_xyz;}
#endif

  universal_call inline float& X() { return xyz[0];}
  universal_call inline float& Y() { return xyz[1];}
  universal_call inline float& Z() { return xyz[2];}

  universal_call inline const float X() const { return xyz[0];}
  universal_call inline const float Y() const { return xyz[1];}
  universal_call inline const float Z() const { return xyz[2];}

#ifndef __CUDACC__
  inline MGML_MATH::VECTOR<3,float> toRGB()
  {
    /*MGML_MATH::MATRIX<3,3,float> m(0.49, 0.31, 0.20,
    0.17697, 0.81240, 0.01063,
    0, 0.01, 0.99);
    m.Inverse();

    MGML_MATH::VECTOR<3,float> rgb = (0.17697f)*(xyz*m);*/


    MGML_MATH::MATRIX<3,3,float> mInv(3.2404542, -0.9692660,  0.0556434,
      -1.5371385,  1.8760108, -0.2040259,
      -0.4985314,  0.0415560,  1.0572252);

    MGML_MATH::VECTOR<3,float>  rgb = (0.17697f)*xyz*mInv; 

    //rgb.x = powf(rgb.x,1.0f/2.2f);
    //rgb.y = powf(rgb.y,1.0f/2.2f);
    //rgb.z = powf(rgb.z,1.0f/2.2f);

    for(int i=0;i<3;i++)
    {
      if(rgb[i] <= 0.0031308)
        rgb[i] = 12.92f*rgb[i];
      else
        rgb[i] = 1.055f*powf(rgb[i], 1.0f/2.2f - 0.055f);
    }

    return rgb;
  }
#endif

protected:
  MGML_MATH::VECTOR<3,float> xyz;

};

//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __CUDACC__
class Spectrum_Color
{

public:

  Spectrum_Color()
  {
    C_Intensity = new float[nCIE];
  }
  Spectrum_Color(int a_samples) 
  { 
    m_samples = a_samples;
    C_Intensity = new float[nCIE];
  }

  ~Spectrum_Color()
  {
    delete [] C_Intensity;
  }

  void setSampleNumber(int a_samples)
  {
    m_samples = a_samples;
  }

  MGML_MATH::VECTOR<3,float> toXYZ(int a_sampleNumber)
  {
    MGML_MATH::VECTOR<3,float> res(0,0,0);

    const int step = nCIE/m_samples;
    int start = a_sampleNumber*step;
    int end = start + step; 

    ASSERT(end <= nCIE);

    for(int i=start;i<end;i++)
    {
      res.x += CIE_X[i]*C_Intensity[i];
      res.y += CIE_Y[i]*C_Intensity[i];
      res.z += CIE_Z[i]*C_Intensity[i];
    }
    res /= step;

    return res;
  }

  int GetSampleLambda(int a_sampleNumber)
  {
    const int step = nCIE/m_samples;
    int start = a_sampleNumber*step;
    return CIEstart + start;
  }

  void LoadLightSpectum(const std::string& fileName)
  {
    std::ifstream fin(fileName.c_str());

    if(!fin.is_open())
      throw MGML_ERROR::File_Error("can not open file " + fileName);

    std::map<int, float> values;

    while(!fin.eof())
    {
      int lambda;
      char temp;
      float E;
      fin >> lambda >> temp >> E;
      values[lambda] = E;
    }

    for(int i=CIEstart;i<CIEend;i++)
    {
      C_Intensity[i-CIEstart] = values[i];
    }
  }

protected:

  int m_samples;
  float* C_Intensity;

  static const int CIEstart = 360;
  static const int CIEend = 700;
  static const int nCIE = CIEend-CIEstart+1;
  static const float CIE_X[830-CIEstart+1];
  static const float CIE_Y[830-CIEstart+1];
  static const float CIE_Z[830-CIEstart+1];
  //static const float C_Intensity[nCIE];

  //static float XWeight[COLOR_SAMPLES];
  //static float YWeight[COLOR_SAMPLES];
  //static float ZWeight[COLOR_SAMPLES];
};

#endif

//////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////      VERTEX        ///////////////////////////////////////// 
//////////////////////////////////////////////////////////////////////////////////////////////////
template <int n_,class T> 
struct VERTEX: public MGML_MATH::SSE_Aligned_Object
{
	MGML_MATH::VECTOR<n_,T> pos;
	MGML_MATH::VECTOR<n_,T> norm;
	MGML_MATH::VECTOR<2,T>  t;

	unsigned int material_id;
	unsigned char texture_id[4]; // multitexturing - max 4 textures

#ifndef __CUDACC__
	VERTEX()
	{
		for(int i=0;i<4;i++)
			texture_id[i] = 255;
	}
#endif

	universal_call VERTEX& operator*=(const MGML_MATH::MATRIX<n_,n_,T>& m)
	{
		pos  = pos*m;
		norm = norm*m;
		norm.Normalize();
		return *this;
	}

	friend universal_call VERTEX operator*(const MGML_MATH::MATRIX<n_,n_,T>& m,const VERTEX& v)
	{
		VERTEX res;
		res.pos  = m*v.pos;
		res.norm = m*v.norm;
		res.norm.Normalize();
		return res;
	}

	inline bool HasSameMaterialAndTextures(const VERTEX& rhs) const
	{
		bool textures_the_same = true;
		for(int i=0;i<4;i++)
			textures_the_same = textures_the_same && (texture_id[i] == rhs.texture_id[i]);

		return material_id == rhs.material_id && textures_the_same;
	}
	//перегрузить c помощью SSE для производительности на CPU:
	//VERTEX& operator=(VERTEX& v);
 
};

//////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////      MATERIAL        /////////////////////////////////////// 
//////////////////////////////////////////////////////////////////////////////////////////////////


template<int _n>
struct MATERIAL : public MGML_MATH::Auto_Align<_n,float>
{
	typedef MGML_MATH::VECTOR<_n,float> vec;
public:

#ifndef __CUDACC__

  MATERIAL(){}

  template<int k>
  MATERIAL(const MATERIAL<k>& a_mat)
  {
    const int copycat = MGML_MATH::MIN<int>(_n,k);
  
    for(int i=0;i<copycat;i++)
    {
      ka[i] = a_mat.ka[i];
      kd[i] = a_mat.kd[i];
      ks[i] = a_mat.ks[i];
    }
    
    power = a_mat.power;
    k_reflect = a_mat.k_reflect;
    k_refract = a_mat.k_refract;
    this->n = a_mat.n;
    BRDF_id = a_mat.BRDF_id;
  }

  template<int k>
  MATERIAL<_n> operator=(const MATERIAL<k>& a_mat)
  {
    MATERIAL::MATERIAL<n>(a_mat)
    return *this;
  }
#endif

	universal_call float getReflection() const {return k_reflect;}
	universal_call float getRefraction() const {return k_refract;}

  universal_call vec getColor() const 
  {
    vec color = ka + kd + ks;
    color.Normalize();
    return color;
  }

	// phong model {
	vec  ka;			  // ambient
	vec  kd;			  // diffuse
	vec  ks;			  // specular
	float  power;		  // power of cos in phong model
	// } end phong model

	float  k_reflect;	  // reflections component
	float  k_refract;	  // refractive component
	float  n;			  // refractive exponent
  float  nRange;
	unsigned int BRDF_id; // BRDF index if BRDF used

	//перегрузить c помощью SSE для производительности на CPU:
	//VERTEX& operator=(VERTEX& v);
};


//////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////      TRIANGLE        /////////////////////////////////////// 
//////////////////////////////////////////////////////////////////////////////////////////////////

class Triangle: public MGML_MATH::SSE_Aligned_Object
{
public:
 
	Triangle(){}

	Triangle(const int a,
			 const int b,
			 const int c)
	{
		A = a;
		B = b;
		C = c;
	}

	union
	{
		int v[3];
		struct
		{
			int A,B,C;
		};
	};

	union
	{
		int  attr32pack;
		struct
		{
			unsigned char texture_id[4];
		};
	};
	
 	
};




template<int n,class T>
struct Light_Source_Struct : public MGML_MATH::Auto_Align<n,T>
{
	typedef MGML_MATH::VECTOR<n,T> vec;

#ifndef __CUDACC__
	universal_call Light_Source_Struct()
	{
		intensity = 1.0f;
		color.set(1,1,1);
	}
	universal_call ~Light_Source_Struct(){}
#endif

	universal_call void setColor(unsigned char r,unsigned char g,unsigned char b)
	{
		this->color.set(r,g,b);
		vec tv(r,g,b,0);
		tv.Normalize();
		color = tv;
	}

  universal_call T GetAverageAmbientLevel() const {return 0.15f;}
  universal_call T GetAverageDiffuseLevel() const {return 0.80f;}
  universal_call T GetAverageSpecularLevel() const {return 1.0f;}
	
	vec	color;
	T	intensity;
  T kc, kl, kq;
};


template<int n,class T>
struct Point_Light: public Light_Source_Struct<n,T>
{
	typedef MGML_MATH::VECTOR<n,T> vec;

	Point_Light(){ this->kc = 1.0; this->kl = 0.01; this->kq = 0.005;}
	
	vec pos;
};


template<int n,class T>
struct Infinite_Light: public Light_Source_Struct<n,T>
{
	typedef MGML_MATH::VECTOR<n,T> vec;

    // use _pos to store normal
	universal_call void       setNorm(const vec& p){_norm = p;}
	universal_call const vec& getNorm() const {return _norm;}
protected:
	MGML_MATH::VECTOR<n,T> _norm;
};


template<int n,class T>
struct Spot_Light: public Point_Light<n,T>
{
	typedef MGML_MATH::VECTOR<n,T> vec;

protected:
  MGML_MATH::VECTOR<n,T> _norm;
  T ang1;
	T ang2;
};

template<int n,class T>
struct Sphere_Light: public Point_Light<n,T>
{

protected:
    MGML_MATH::SPHERE<n,T> _sphere; 
};

template<int n,class T>
struct Flat_Light: public Light_Source_Struct<n,T>
{

  MGML_MATH::VECTOR<n,T> pos;
	MGML_MATH::VECTOR<n,T> norm;

	MGML_MATH::VECTOR<2,T> size;
};






};

