/**
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is part of The iWear Framework.
 *
 * The iWear Framework is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation as in version 2 of the License.

 * 
 * The iWear Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#ifndef __IWEAR_CONSTANTS_H
#define __IWEAR_CONSTANTS_H

#ifndef __IWEAR_EXCEPTIONS_H
#include <iwear/exceptions.h>
#endif

namespace iwear
{

/**
 * This must be kept in sync with the human-readable name list for this !
 */
enum units
{
    unit_mV,                ///< milli Volts
    unit_mW,                ///< milli Watts
    unit_mA,                ///< milli Ampere
    unit_mWh,               ///< milli Watts per Hour
    unit_mAh,               ///< milli Ampere per Hour

    unit_celsius,            ///< Temperature in degree Celsius
    unit_fahrenheit,            ///< Temperature in degree Fahrenheit
    unit_kelvin,
    unit_rankine,
    unit_reaumur,
    unit_romer,
    unit_newtontemp,
    unit_delisle,
// Angle variants    
    unit_adeg_G,            ///< Angle in degree Grad (360 full circle)
    unit_adeg_N,            ///< Angle in degree NeuGrad (400 full circle)
    unit_adeg_gon = unit_adeg_N,
    unit_adeg_gradian = unit_adeg_N,
    unit_adeg_R,            ///< Angle in degree Radians ( 2 \f$ \pi \f$ full circle)
    unit_adeg_P,	    ///< Angle Percentage (100% full circle)
// Length Units
    unit_meter,             ///< Meter
    unit_miles,		    ///< USA miles
    unit_nmiles,	    ///< Nautical Mile
    unit_nmilesadm,	    ///< Nautical Mile (Admirality)
    unit_inch,		    ///< Inch
    unit_zoll = unit_inch,  ///< Zoll == Inch
    unit_feet,		    ///< Feet = 12 Inches
    unit_yard,		    ///< Yard = 3 Feet
    unit_lyear,		    ///< Light Year
    unit_plancklength,	    ///< in Planck Lengths
    unit_astrou,	    ///< Astronomical Unit
// Weight units
    unit_g,
    unit_pound,
    unit_unze,
// SI Prefixes
    unit_plain,		///< plain
    unit_yocto,		///< yocto
    unit_zepto,		///< zepto
    unit_atto,		///< atto
    unit_femto,		///< femto
    unit_pico,		///< pico
    unit_nano,		///< nano
    unit_micro,		///< micro
    unit_milli,		///< milli
    unit_centi,		///< centi
    unit_deci,		///< deci
    unit_deca,		///< deca
    unit_hecto,		///< hecto
    unit_kilo,		///< kilo
    unit_mega,		///< mega
    unit_giga,		///< giga
    unit_tera,		///< tera
    unit_peta,		///< peta
    unit_exa,		///< exa
    unit_zetta,		///< zetta
    unit_yotta,		///< yotta

// CS Prefixes (similar to SI, but in 2^x)    
    unit_Byte,
    unit_Kilo,
    unit_Mega,
    unit_Giga,
    unit_Tera,
    unit_Peta,
    unit_Exa,
// Brightness & Light units - not directly convertible !
    unit_lux,		    ///< Lux. 1 Lux == 1 Lumen \f$m^{-2}\f$
    unit_lumen,		    ///< @see http://en.wikipedia.org/wiki/Lumen
    unit_candela,	    ///< @see http://en.wikipedia.org/wiki/Candela
    unit_footcandle,	    ///< @see http://en.wikipedia.org/wiki/Footcandle

    unit_kmh,               ///< km per Hour
    unit_ms,                ///< meter per second
    unit_mss,               ///< meter per second per second (\f$ms^{-2}\f$)
    unit_none,		    ///< Unit free (e.g. Pi)
    unit_free = unit_none,  ///< Unit free
    unit_Nmqkgq,	    ///< Newton metersquareseconds per kg^2 (\f$N m^2 kg^{-2} \f$)
    unit_pa,		    ///< Pascal
// Time units
    unit_second,
    unit_minute,
    unit_hour,
    unit_planck_time,

    num_units,              ///< Number of known units (e.g. for array size). 
};

/// we need to keep the .cpp in sync with the above enum list !
extern const char * unit_strings[];

namespace constants/*{{{*/
{

template<class T>
class Constant
{
public:
    const T value;
    const units unit;

    inline Constant( const T& v, const units& u ) : value(v), unit(u) { }
};

const Constant<double> G(6.6742e-11,unit_Nmqkgq);
const Constant<double> au(149597870691.0,unit_meter);
const Constant<double> c(299792458,unit_ms);
const Constant<double> g(9.80665,unit_mss);
const Constant<double> lightyear(9460730472580800.0,unit_meter);
const Constant<double> parsec(3.08568025e+16,unit_meter);
const Constant<double> planck_length(1.61624e-35,unit_meter);
const Constant<double> planck_temp(1.41679e+32,unit_kelvin);
const Constant<double> planck_time(5.39121e-44,unit_second);
const Constant<double> std_pressure(101325,unit_pa);
const Constant<double> zero_kelvin(-273.15,unit_celsius);
const Constant<long double> e(2.7182818284590452353602874713526624977572470937000,unit_none);
const Constant<long double> pi(3.1415926535897932384626433832795028841971693993751,unit_none);

}/*}}}*/

/**
 * You need to multiplicate the value with one of these values to get the
 * correct amount of meters.
 * Must be kept in sync with the enum definitions !
 */
const double conversion_to_meter[] = {  /*{{{*/
	1,			// meter
	(5280.0*1200.0/3937.0),	// mile
	1852,			// nautical mile
	(6080.0*1200.0/3937.0),	// nautical mile (Admirality)
	0.0254,			// inch/zoll
	(1200.0/3937.0),	// foot
	3.0*(1200.0/3937.0),	// yard
	3.08568025e+16, 	// parsec
	9460730472580800.0, 	// Lightyear
	1.61624e-35,		// planck length
	149597870691.0,	// AU
};/*}}}*/

/**
 * Must be kept in sync with the enum definitions !
 */
const double conversion_to_plain[] = {/*{{{*/
    1,		// plain
    1e-24,	// yocto
    1e-21,	// zepto
    1e-18,	// atto
    1e-15,	// femto
    1e-12,	// pico
    1e-9, 	// nano
    1e-6,	// micro
    1e-3,	// milli
    1e-2,	// centi
    1e1,	// deci
    1e+1,	// deca
    1e+2,	// hecto
    1e+3,	// kilo
    1e+6,	// mega
    1e+9,	// giga
    1e+12,	// tera
    1e+15,	// peta
    1e+18,	// exa
    1e+21,	// zetta
    1e+24,	// yotta
};/*}}}*/

/**
 * Must be kept in sync with the enum definitions !
 */
const long double conversion_to_byte[] = {/*{{{*/
    1024.0,		//Kilo
    1048576.0,		//Mega
    1073741824.0,	//Giga
    1099511627776.0,	//Tera
    1125899906842624.0,	//Peta
    1152921504606846976.0	//Exa
};/*}}}*/
/* t cases where this will be called is probably when the units
 * parameters are constant. In this case, the compiler can totally optimize
 * it away, so only the single conversion calculation will remain.
 */

// http://www.chemie.fu-berlin.de/chemistry/general/units.html
inline long double convert_unit( long double value, units u_from, units u_to )/*{{{*/
{
    if ( u_from == u_to ) return value;

    ///@todo Create a enum to string conversion constructor to create strings out of conversion units
    switch(u_from)
    {
	/* Length Unit Conversions {{{*/
	case unit_meter:
	case unit_miles:
	case unit_nmiles:
	case unit_nmilesadm:
	case unit_inch:
	case unit_feet:
	case unit_yard:
	case unit_lyear:
	case unit_plancklength:
	    switch(u_to)
	    {
		case unit_meter:
		case unit_miles:
		case unit_nmiles:
		case unit_nmilesadm:
		case unit_inch:
		case unit_feet:
		case unit_yard:
		case unit_lyear:
		case unit_plancklength:
		    return value*( conversion_to_meter[u_from-unit_meter] / conversion_to_meter[u_to-unit_meter] );
		default:
		    break;
	    }/*}}}*/
	    break;
	/* SI Prefix Conversions {{{*/
	case unit_yocto:
	case unit_zepto:
	case unit_atto:
	case unit_femto:
	case unit_pico:
	case unit_nano:
	case unit_micro:
	case unit_milli:
	case unit_centi:
	case unit_deci:
	case unit_plain:
	case unit_deca:
	case unit_hecto:
	case unit_kilo:
	case unit_mega:
	case unit_giga:
	case unit_tera:
	case unit_peta:
	case unit_exa:
	case unit_zetta:
	case unit_yotta:
	    switch(u_to)
	    {
		case unit_yocto:
		case unit_zepto:
		case unit_atto:
		case unit_femto:
		case unit_pico:
		case unit_nano:
		case unit_micro:
		case unit_milli:
		case unit_centi:
		case unit_deci:
		case unit_plain:
		case unit_deca:
		case unit_hecto:
		case unit_kilo:
		case unit_mega:
		case unit_giga:
		case unit_tera:
		case unit_peta:
		case unit_exa:
		case unit_zetta:
		    return value*( conversion_to_plain[u_from-unit_plain] / conversion_to_plain[u_to-unit_plain] );
		default:
		    break;
	    }/*}}}*/
	    break;
	/* Byte Prefix Conversions {{{*/
	case unit_Byte:
	case unit_Kilo:
	case unit_Mega:
	case unit_Giga:
	case unit_Tera:
	case unit_Peta:
	case unit_Exa:
	    switch(u_to)
	    {
		case unit_Byte:
		case unit_Kilo:
		case unit_Mega:
		case unit_Giga:
		case unit_Tera:
		case unit_Peta:
		case unit_Exa:
		    return value*( conversion_to_byte[u_from-unit_Byte] / conversion_to_byte[u_to-unit_Byte] );
		default:
		    break;
	    }/*}}}*/
	    break;
	/* Temperature Conversions {{{*/
	case unit_fahrenheit:
	    {
		long double cel = (value - 32.0) / 1.8;
		return convert_unit(cel,unit_celsius,u_to);
	    }
	case unit_kelvin:
	    {
		long double cel = value + constants::zero_kelvin.value;
		return convert_unit(cel,unit_celsius,u_to);
	    }
	case unit_rankine:
	    {
		long double cel = (value/1.8)-constants::zero_kelvin.value;
		return convert_unit(cel,unit_celsius,u_to);
	    }
	case unit_reaumur:
	    {
		long double cel= value/0.8;
		return convert_unit(cel,unit_celsius,u_to);
	    }
	case unit_romer:
	    {
		long double cel=(value-7.5)*(40.0/21.0);
		return convert_unit(cel,unit_celsius,u_to);
	    }
	case unit_newtontemp:
	    {
		long double cel=value * (100.0/33.0);
		return convert_unit(cel,unit_celsius,u_to);
	    }
	case unit_delisle:
	    {
		long double cel = 100 - (value*(2.0/3.0));
		return convert_unit(cel,unit_celsius,u_to);
	    }

	case unit_celsius:
	    switch(u_to)
	    {
		case unit_fahrenheit:
		    return (value*1.8)+32.0;
		case unit_kelvin:
		    return (value - constants::zero_kelvin.value);
		case unit_rankine:
		    return (value * 1.8) + 491.67;
		case unit_reaumur:
		    return (value * 1.8);
		case unit_romer:
		    return ( value * (21.0/40.0)) + 7.5;
		case unit_newtontemp:
		    return (value * (33.0/100.0));
		case unit_delisle:
		    return (100.0-value) * 1.5;
	    default:
		    break;
	    }/*}}}*/
	    break;
	/* Light stuff conversions */
	case unit_footcandle:
	    switch(u_to)
	    {
		case unit_lux:
		    return value*10.76;
		default:
		    break;
	    }
	    break;
	case unit_lux:
	    switch(u_to)
	    {
		case unit_footcandle:
		    return value/10.76;
		default:
		    break;
	    }
	    break;
	case unit_adeg_P:
	    {
		long double deg = value * 3.6;
		return convert_unit(deg,unit_adeg_G,u_to);
	    }
	case unit_adeg_N:
	    {
		long double deg = value * (3.6/4.0);
		return convert_unit(deg,unit_adeg_G,u_to);
	    }
	case unit_adeg_R:
	    {
		long double deg = value * ( 180.0 / constants::pi.value ); 
		return convert_unit(deg,unit_adeg_G,u_to);
	    }
	case unit_adeg_G:
	    switch(u_to)
	    {
		case unit_adeg_R:
		    return ( value * ( constants::pi.value / 180.0 ));
		case unit_adeg_N:
		    return ( value * (4.0/3.6) );
		case unit_adeg_P:
		    return ( value / 3.6 );
		default:
		    break;
	    }
	    break;
	case unit_kmh:
	    switch(u_to)
	    {
		case unit_ms:
		    return ( value / 3.6 );
		default:
		    break;
	    }
	    break;
	case unit_ms:
	    switch(u_to)
	    {
		case unit_kmh:
		    return ( value * 3.6 );
		default:
		    break;
	    }
	    break;
	default:
	    break;
    }
    THROW( conversion_error,("Cannot convert from ?? to ??"));
}/*}}}*/

}
#endif
