/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2010 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
#ifndef OSGEARTH_UNITS_H
#define OSGEARTH_UNITS_H 1

#include <osgEarth/Common>

namespace osgEarth
{
    class OSGEARTH_EXPORT Units
    {
    public:
        // linear
        static const Units CENTIMETERS;
        static const Units DATA_MILES;
        static const Units FATHOMS;
        static const Units FEET;
        static const Units FEET_US_SURVEY;  // http://www.wsdot.wa.gov/reference/metrics/foottometer.htm
        static const Units INCHES;
        static const Units KILOFEET;
        static const Units KILOMETERS;
        static const Units KILOYARDS;
        static const Units METERS;
        static const Units MILES;           // statute miles
        static const Units MILLIMETERS;
        static const Units NAUTICAL_MILES;
        static const Units YARDS;

        // angular
        static const Units BAM;
        static const Units DEGREES;
        static const Units NATO_MILS; // http://www.convertworld.com/en/angle/Mil+(NATO).html
        static const Units RADIANS;

        // temporal
        static const Units DAYS;
        static const Units HOURS;
        static const Units MICROSECONDS;
        static const Units MILLISECONDS;
        static const Units MINUTES;
        static const Units SECONDS;
        static const Units WEEKS;

        // speed
        static const Units FEET_PER_SECOND;
        static const Units YARDS_PER_SECOND;
        static const Units METERS_PER_SECOND;
        static const Units KILOMETERS_PER_SECOND;
        static const Units KILOMETERS_PER_HOUR;
        static const Units MILES_PER_HOUR;
        static const Units DATA_MILES_PER_HOUR;
        static const Units KNOTS;

        enum Type { TYPE_LINEAR, TYPE_ANGULAR, TYPE_TEMPORAL, TYPE_SPEED, TYPE_INVALID };

    public:

        static bool convert( const Units& from, const Units& to, double input, double& output ) {
            if ( canConvert(from, to) ) {
                if ( from._type == TYPE_LINEAR || from._type == TYPE_ANGULAR || from._type == TYPE_TEMPORAL )
                    convertSimple( from, to, input, output );
                else if ( from._type == TYPE_SPEED )
                    convertSpeed( from, to, input, output );
                return true;
            }
            return false;
        }

        static double convert( const Units& from, const Units& to, double input ) {
            double output;
            convert( from, to, input, output );
            return output;
        }

        static bool canConvert( const Units& from, const Units& to ) {
            return from._type == to._type;
        }

        bool canConvert( const Units& to ) const {
            return _type == to._type;
        }

        bool convertTo( const Units& to, double input, double& output )  const {
            return convert( *this, to, input, output );
        }

        double convertTo( const Units& to, double input ) const {
            return convert( *this, to, input );
        }
        
        const std::string& getName() const { return _name; }

        const std::string& getAbbr() const { return _abbr; }

        const Type& getType() const { return _type; }

        bool operator == ( const Units& rhs ) const {
            return _type == rhs._type && _toBase == rhs._toBase; }
        
        bool operator != ( const Units& rhs ) const {
            return _type != rhs._type || _toBase != rhs._toBase; }

        bool isLinear() const { return _type == TYPE_LINEAR; }

        bool isAngular() const { return _type == TYPE_ANGULAR; }

        bool isTemporal() const { return _type == TYPE_TEMPORAL; }

        bool isSpeed() const { return _type == TYPE_SPEED; }

    public:

        // Make a new unit definition (LINEAR, ANGULAR, TEMPORAL)
        Units( const std::string& name, const std::string& abbr, const Type& type, double toBase );

        // Maks a new unit definition (SPEED)
        Units( const std::string& name, const std::string& abbr, const Units& distance, const Units& time );

        Units() : _type(TYPE_INVALID) { }

    private:

        static void convertSimple( const Units& from, const Units& to, double input, double& output ) {
            output = input * from._toBase / to._toBase;
        }
        static void convertSpeed( const Units& from, const Units& to, double input, double& output ) {
            double t = from._distance->convertTo( *to._distance, input );
            output = to._time->convertTo( *from._time, t );
        }


        std::string _name, _abbr;
        Type _type;
        double _toBase;
        const Units* _distance;
        const Units* _time;
    };

    struct Linear;

    template<typename T>
    class qualified_double
    {
    public:
        qualified_double<T>( double value, const Units& units ) : _value(value), _units(units) { }

        qualified_double<T>( const T& rhs ) : _value(rhs._value), _units(rhs._units) { }

        void set( double value, const Units& units ) {
            _value = value;
            _units = units;
        }

        T& operator = ( const T& rhs ) {
            if ( _units.canConvert(rhs._units) )
                _value = rhs.as(_units);
            return static_cast<T&>(*this);
        }

        T operator + ( const T& rhs ) const {
            return _units.canConvert(rhs._units) ?
                T(_value + rhs.as(_units), _units) :
                T(0, Units());
        }

        T operator - ( const T& rhs ) const {
            return _units.canConvert(rhs._units) ? 
                T(_value - rhs.as(_units), _units) :
                T(0, Units());
        }

        bool operator == ( const T& rhs ) const {
            return _units.canConvert( rhs._units ) && rhs.as(_units) == _value;
        }

        bool operator != ( const T& rhs ) const {
            return !_units.canConvert(rhs._units) || rhs.as(_units) != _value;
        }

        bool operator < ( const T& rhs ) const {
            return _units.canConvert(rhs._units) && _value < rhs.as(_units);
        }

        bool operator > ( const T& rhs ) const {
            return _units.canConvert(rhs._units) && _value > rhs.as(_units);
        }

        double as( const Units& convertTo ) const {
            return _units.convertTo( convertTo, _value );
        }

        const Units& getUnits() const { return _units; }

    private:
        double _value;
        Units  _units;
    };

    struct Linear : public qualified_double<Linear> {
        Linear() : qualified_double<Linear>(0, Units::METERS) { }
        Linear(double value, const Units& units) : qualified_double<Linear>(value, units) { }
    };

    struct Angular : public qualified_double<Angular> {
        Angular() : qualified_double<Angular>(0, Units::DEGREES) { }
        Angular(double value) : qualified_double<Angular>(value, Units::DEGREES) { }
        Angular(double value, const Units& units) : qualified_double<Angular>(value, units) { }
    };

    struct Temporal : public qualified_double<Temporal> {
        Temporal() : qualified_double<Temporal>(0, Units::SECONDS) { }
        Temporal(double value, const Units& units) : qualified_double<Temporal>(value, units) { }
    };

    struct Speed : public qualified_double<Speed> {
        Speed() : qualified_double<Speed>(0, Units::METERS_PER_SECOND) { }
        Speed(double value, const Units& units) : qualified_double<Speed>(value, units) { }
    };
}

#endif // OSGEARTH_UNITS_H
