#ifndef RAINBOW_HXX
#define RAINBOW_HXX


/**
   Represents a rainbow.
*/
class Rainbow
{
 public:
    /**
       Creates a rainbow.
       @param sun - the direction of the sun light
       @param angle - the angle under which the rainbow is perceived
                      use 0.733 (= 42 degrees) for a primary bow
		      and 0.89 (= 51 degrees) for a secondary bow
       @param width - the width of the bow (specified by an angle)
       @param distFull - behind this distance the rainbow is perceived
       @param distFade - between distFull and distFade the rainbow fades
       @param transp - the transparency of the rainbow
       @param invert - use true for a secondary bow to invert the sequence
                       of colors (in comparison to a primary bow)
    */
    Rainbow(Vec3f sun, float angle, float width, float distFull, float distFade,
	    float transp, bool invert)
	: _sun(sun), _distanceFull(distFull), _distanceFade(distFade),
	  _transparency(transp), _invert(invert)
    {
	_sun.normalize();
	_cosMax = cos(angle - width/2);
	_cosMin = cos(angle + width/2);
    }

    /** Combines the color of the rainbow with the given color. */
    Vec3f rayTrace( const Vec3f &oldColor, const Ray &ray ) const {
	// compute the cosinus of the angle between the viewing direction
	// and the direction of the sun light
	float cosinus = std::abs(dot(ray.dir,_sun));
	// ignore the rainbow if the ray is out of the specified range
	if ((cosinus < _cosMin) || (cosinus > _cosMax) || (ray.t < _distanceFade))
	    return oldColor;
	// compute the wavelength of the light
	float wavelength = (1 - (cosinus - _cosMin) / (_cosMax - _cosMin)) *
	    (_wavelengthMax - _wavelengthMin) + _wavelengthMin;
	// if wanted, invert the wavelength (in order to invert the sequence of colors)
	if (_invert) wavelength = _wavelengthMin + _wavelengthMax - wavelength;
	// compute the color of the light with the already computed wavelength
	Vec3f color = getColor((int)wavelength);
	// compute the opacity (the contrary of the transparency)
	float opacity = 1 - _transparency;
	// adapt the opacity if the hit point is between distFade and distFull
	if (ray.t < _distanceFull) opacity *= (ray.t - _distanceFade) / (_distanceFull - _distanceFade);

	// combine the color of the rainbow with the given color
	return opacity * color + (1-opacity) * oldColor;
    }

 private:
    static const int _wavelengthMin = 380; // violet
    static const int _wavelengthMax = 780; // red
    
    Vec3f _sun; // direction of the sun light
    
    // min and max cosinus values of the angles under which the rainbow can be perceived
    float _cosMin; 
    float _cosMax;
    
    float _distanceFull;
    float _distanceFade;
    float _transparency;
    bool _invert;

    
    /** Returns the color of the light specified by the given wavelength. */
    Vec3f getColor(int wavel) const {
	double factor = 0.0;
	int z = 0;
	if ((wavel>=380)&&(wavel<=439)) z=1; // violet - blue
	if ((wavel>=440)&&(wavel<=489)) z=2; // blue - light blue
	if ((wavel>=490)&&(wavel<=509)) z=3; // light blue - green
	if ((wavel>=510)&&(wavel<=579)) z=4; // green - yellow
	if ((wavel>=580)&&(wavel<=644)) z=5; // yellow - orange - red
	if ((wavel>=645)&&(wavel<=780)) z=6; // red

	// the factor is used to get different violet and red tones (dark - light)
	if ((wavel>=380)&&(wavel<=419)) factor=0.3+0.7*(wavel-380.0)/(420.0-380.0);
	if ((wavel>=420)&&(wavel<=700)) factor=1.0;
	if ((wavel>=701)&&(wavel<=780)) factor=0.3+0.7*(780.0-wavel)/(780.0-700.0);

	Vec3f result;
	switch (z) {
	    case 1: {
		result = Vec3f( -(wavel-440.0f)/(440.0f-380.0f), 0.0f, 1.0f );
		break;
	    }
	    case 2: {
		result = Vec3f( 0.0f, (wavel-440.0f)/(490.0f-440.0f), 1.0f);
		break;
	    }
	    case 3: {
		result = Vec3f( 0.0f, 1.0f, -(wavel-510.0f)/(510.0f-490.0f) );
		break;
	    }
	    case 4: {
		result = Vec3f( (wavel-510.0f)/(580.0f-510.0f), 1.0f, 0.0f );
		break;
	    }
	    case 5: {
		result = Vec3f( 1.0f, -(wavel-645.0f)/(645.0f-580.0f), 0.0f );
		break;
	    }
	    case 6: {
		result = Vec3f( 1.0f, 0.0f, 0.0f );
		break;
	    }
	    default: {
		result = Vec3f( 0.0f, 0.0f, 0.0f );
		break;
	    }
	}

	return result * factor;
    }
};


#endif // RAINBOW_HXX
