/*
===========================
 Boring Introductory Stuff
===========================
*/
#define _DIMENSIONS_H_

#include <vector>
#include <string>
#include <sstream>
#include <cmath>
#include <algorithm>

#ifndef tstring
#ifdef _UNICODE
typedef std::wstring tstring;
typedef std::wistringstream tistringstream;
typedef std::wostringstream tostringstream;
typedef std::wostream tostream;
typedef std::wistream tistream;
#else
typedef std::string tstring;
typedef std::istringstream tistringstream;
typedef std::ostringstream tostringstream;
typedef std::ostream tostream;
typedef std::istream tistream;
#endif // _UNICODE
#endif // tstring

//Some useful typedefs from the Windows SDK.
//Since these are defined in WinNT.h, I'll take the chance of using this condition:
#if !(defined(_WINNT_) || defined(_TSTR_DEFINED_))
#define _TSTR_DEFINED_

#ifdef _UNICODE

typedef wchar_t TCHAR;

#else

typedef char THCAR;

#endif // _UNICODE

typedef TCHAR *LPTSTR;
typedef TCHAR const *LPCTSTR;

#endif // !(defined(_WINNT_) || defined(_TSTR_DEFINED_))

//If tchar.h has been included, the following is defined already.
#ifndef _T
#ifdef _UNICODE

#define __T(x) L##x

#else

#define __T(x) x

#endif // _UNICODE

#define _T(x) __T(x)

#endif // _T

#ifndef NULL
#define NULL 0
#endif // NULL
/*
===============================
 END Boring Introductory Stuff
===============================
*/

#define LIBNS Units

namespace LIBNS
{
	/*
	===============
	 Basic Classes
	===============
	*/

	class FactorBase
	{
		//Constructors, Destructor
	protected:
		FactorBase()
		{ }
		virtual ~FactorBase()
		{ }

		//Public Interface
	public:
		virtual double GetFactor() const { return 1; }
		virtual LPCTSTR GetName() const { return NULL; }
		virtual LPCTSTR GetSymbol() const { return NULL; }
	};

	class Prefix : public FactorBase
	{
		//Member Data
	private:
		double m_convFactor;
		tstring m_name;
		tstring m_symbol;

		//Constructors
	private:
		Prefix(Prefix const&); //No copying.
		Prefix(double convFactor = 1.0, LPCTSTR name = _T(""), LPCTSTR symbol = _T(""))
			: m_convFactor(convFactor), m_name(name), m_symbol(symbol), FactorBase()
		{ }

		//Public Interface
	public:
		virtual double GetFactor() const { return m_convFactor; }
		virtual LPCTSTR GetName() const { return m_name.c_str(); }
		virtual LPCTSTR GetSymbol() const { return m_symbol.c_str(); }

		//Operators
	private:
		const Prefix& operator=(Prefix const&); //No assignment.

		//Static Members
	public:
		static Prefix const NoPrefix;

		static Prefix const Deca;
		static Prefix const Hecto;
		static Prefix const Kilo;
		static Prefix const Mega;
		static Prefix const Giga;

		static Prefix const deci;
		static Prefix const centi;
		static Prefix const milli;
		static Prefix const micro;
		static Prefix const nano;
	};

	Prefix const Prefix::NoPrefix = Prefix();
	Prefix const Prefix::Deca(10, _T("Deca"), _T("D"));
	Prefix const Prefix::Hecto(100, _T("Hecto"), _T("H"));
	Prefix const Prefix::Kilo(1000, _T("Kilo"), _T("k"));
	Prefix const Prefix::Mega(1000000, _T("Mega"), _T("M"));
	Prefix const Prefix::Giga(1e9, _T("Giga"), _T("G"));

	Prefix const Prefix::deci(0.1, _T("deci"), _T("d"));
	Prefix const Prefix::centi(0.01, _T("centi"), _T("c"));
	Prefix const Prefix::milli(0.001, _T("milli"), _T("m"));
	Prefix const Prefix::micro(0.000001, _T("micro"), _T("u"));
	Prefix const Prefix::nano(1e-9, _T("nano"), _T("n"));

	class Unit : public FactorBase
	{
	protected:
		Prefix const *m_prefix;
		//Buffer to hold the symbol so the return value of GetSymbol() can be LPCTSTR.
		mutable tstring _symbol;

		//Constructors
	protected:
		Unit(Prefix const &prefix = Prefix::NoPrefix) : m_prefix(&prefix)
		{ }
		Unit(Unit const &src) : m_prefix(src.m_prefix)
		{ }

		//Helpers
	protected:
		virtual LPCTSTR GetSymbolNoPrefix() const { return _T(""); }

		//Public Interface
	public:
		virtual Unit* Clone() const { return NULL; }
		virtual double GetFactor() const { return m_prefix->GetFactor(); }
		virtual bool IsStandardUnit() const { return false; }
		virtual bool HasPrefixAffinity() const { return true; }
		virtual double GetFactorNoPrefix() const { return GetFactor() / m_prefix->GetFactor(); }
		virtual LPCTSTR GetSymbol() const
		{
			if (!_symbol.size())
			{
				_symbol = tstring(m_prefix->GetSymbol()) + GetSymbolNoPrefix();
			}
			return _symbol.c_str();
		}

		//Operators
	public:
		Unit& operator=(Unit const &op2)
		{
			m_prefix = op2.m_prefix;
			_symbol.clear();
			return *this;
		}

		//Dimensionless singleton
	public:
		static const Unit Dimensionless;
	};

	const Unit Unit::Dimensionless = Unit();

	class UnitContext
	{
		//Member data.
	private:
		double m_exponent;
		Unit *m_unit;

		//Constructors
	public:
		UnitContext(UnitContext const &src) : m_exponent(src.m_exponent), m_unit(src.m_unit)
		{ }
		UnitContext(Unit const &unit, double exponent) : m_exponent(exponent), m_unit(const_cast<Unit*>(&unit))
		{ }

		//Public Interface
	public:
		double GetExponent() const { return m_exponent; }
		void SetExponent(double newExponent) { m_exponent = newExponent; }
		Unit const& GetUnit() const { return *m_unit; }

		UnitContext& operator=(UnitContext const &op2)
		{
			m_exponent = op2.m_exponent;
			m_unit = op2.m_unit;
			return *this;
		}
	};

	class CompoundUnit : public Unit
	{
		//Internal data.
	protected:
		std::vector<UnitContext> _units;

		//Constructors
	public:
		CompoundUnit() : Unit()
		{ }

		//Private functor to calculate the accumulated conversion factor.
	private:
		class _ConvFactorF
		{
			//Internal Data
		private:
			double _accumFactor;

			//Constructor
		public:
			_ConvFactorF(double initialValue = 1.0) : _accumFactor(initialValue)
			{ }

			//Public Interface
		public:
			double GetAccumulatedConversionFactor() { return _accumFactor; }

			//Operator overloads
			void operator()(UnitContext const &uc)
			{
				_accumFactor *= pow(uc.GetUnit().GetFactor(), uc.GetExponent());
			}
		};

		//Private functor to derivate the symbol of the compound unit.
	private:
		class _SymbolAssemblerF
		{
			//Internal Data
		private:
			tostringstream _num_os;
			tostringstream _den_os;
			bool _useExponentChar;

			//Constructors
		public:
			_SymbolAssemblerF(_SymbolAssemblerF const &src) //Copy constructor needed because the stupid for_each() will clone this.
				: _num_os(src._num_os.str())
				, _den_os(src._den_os.str())
				, _useExponentChar(src._useExponentChar)
			{ }
			_SymbolAssemblerF(bool useExponentChar = false) : _useExponentChar(useExponentChar)
			{ }

			//Public Interface
		public:
			tstring GetAccummulatedSymbol()
			{
				tstring symbol = _num_os.str();
				tstring den = _den_os.str();
				if (den.size())
				{
					symbol += _T('/');
					symbol += den;
				}
				return symbol;
				//return tstring(_T("Nothing"));
			}

			//Operator Overloads
			void operator()(UnitContext const &uc)
			{
				tostringstream &s = uc.GetExponent() < 0 ? _den_os : _num_os;
				s << uc.GetUnit().GetSymbol();
				if (abs(uc.GetExponent()) != 1)
				{
					if (_useExponentChar) s << _T('^');
					s << abs(uc.GetExponent());
				}
			}
		};

		//Helpers
	protected:
		virtual Unit* Clone() const { return new CompoundUnit(); }

		//Public Interface
	public:
		double GetFactor() const
		{
			_ConvFactorF f = std::for_each(_units.begin(), _units.end(), _ConvFactorF());
			return f.GetAccumulatedConversionFactor();
		}
		LPCTSTR GetSymbol() const
		{
			_SymbolAssemblerF f = std::for_each(_units.begin(), _units.end(), _SymbolAssemblerF());
			return f.GetAccummulatedSymbol().c_str();
		}
	};

	/*
	===================
	 END Basic Classes
	===================
	*/

	/*
	==============
	 Length Units
	==============
	*/

	//Forward declaration of Length for friend purposes.
	class Length;

	class LengthUnit : public Unit
	{
		friend class Length;
		//Constructors
	protected:
		LengthUnit() : Unit()
		{ }

		//Static Members
	public:
		static const LengthUnit &StandardUnit;
	};

	class MeterUnit : public LengthUnit
	{
		//Constructors
	public:
		MeterUnit() : LengthUnit()
		{ }

		//Helpers
	protected:
		virtual LPCTSTR GetSymbolNoPrefix() const { return _T("m"); }
		virtual Unit* Clone() const { return new MeterUnit(); }
		//Public Interface.
	public:
		virtual bool IsStandardUnit() const { return true; }
		virtual LPCTSTR GetName() const { return _T("meter"); }

		static const MeterUnit Meter;
	};

	const MeterUnit MeterUnit::Meter = MeterUnit();
	const LengthUnit &LengthUnit::StandardUnit = MeterUnit::Meter;

	class FootUnit : public LengthUnit
	{
		//Constructors
	public:
		FootUnit() : LengthUnit()
		{ }

		//Helpers
	protected:
		virtual LPCTSTR GetSymbolNoPrefix() const { return _T("ft"); }
		virtual Unit* Clone() const { return new FootUnit(); }

		//Public Interface
	public:
		virtual double GetFactor() const { return 0.3048; }
		virtual LPCTSTR GetName() const { return _T("foot"); }

		//Singleton
	public:
		static const FootUnit Foot;
	};

	const FootUnit FootUnit::Foot = FootUnit();

	/*
	====================
	 END Length Units
	====================
	*/

	/*
	============
	 Time Units
	============
	*/

	//Forward declaration of Time for friends purposes.
	class Time;

	class TimeUnit : public Unit
	{
		friend class Time;
		//Constructors
	protected:
		TimeUnit() : Unit()
		{ }

		//Static Members
	public:
		static TimeUnit const &StandardUnit;
	};

	class SecondUnit : public TimeUnit
	{
		//Constructors
	private:
		SecondUnit() : TimeUnit()
		{ }

		//Helpers
	protected:
		virtual LPCTSTR GetSymbolNoPrefix() const { return _T("s"); }
		virtual Unit* Clone() const { return new SecondUnit(); }

		//Public Interface
	public:
		virtual bool IsStandardUnit() const { return true; }
		virtual LPCTSTR GetName() const { return _T("second"); }

		static const SecondUnit Second;
	};

	const SecondUnit SecondUnit::Second = SecondUnit();
	const TimeUnit &TimeUnit::StandardUnit = SecondUnit::Second;

	class HourUnit : public TimeUnit
	{
		//Constructors
	private:
		HourUnit() : TimeUnit()
		{ }

		//Helpers
		virtual LPCTSTR GetSymbolNoPrefix() const { return _T("h"); }
		virtual Unit* Clone() const { return new HourUnit(); }

		//Public Interface
	public:
		virtual double GetFactor() const { return 3600; }
		virtual LPCTSTR GetName() const { return _T("hour"); }

		//Singleton
	public:
		static HourUnit const Hour;
	};

	const HourUnit HourUnit::Hour = HourUnit();

	/*
	================
	 END Time Units
	================
	*/

	/*
	=============
	 Speed Units
	=============
	*/

	class SpeedUnit : public CompoundUnit
	{
		//Constructors
	public:
		SpeedUnit() : CompoundUnit()
		{ }

	public:
		static SpeedUnit const &StandardUnit;
	};

	class MetersPerSecondUnit : public SpeedUnit
	{
		//Constructors
	private:
		MetersPerSecondUnit() : SpeedUnit()
		{
			_units.push_back(UnitContext(MeterUnit::Meter, 1));
			_units.push_back(UnitContext(SecondUnit::Second, -1));
		}

		//Singleton
	public:
		static MetersPerSecondUnit const MetersPerSecond;
	};

	const MetersPerSecondUnit MetersPerSecondUnit::MetersPerSecond = MetersPerSecondUnit();
	const SpeedUnit &SpeedUnit::StandardUnit = MetersPerSecondUnit::MetersPerSecond;

	class FeetPerSecondUnit : public SpeedUnit
	{
		//Constructors
	public:
		FeetPerSecondUnit() : SpeedUnit()
		{
			_units.push_back(UnitContext(FootUnit::Foot, 1));
			_units.push_back(UnitContext(SecondUnit::Second, -1));
		}

		//Singleton
	public:
		static FeetPerSecondUnit const FeetPerSecond;
	};

	const FeetPerSecondUnit FeetPerSecondUnit::FeetPerSecond = FeetPerSecondUnit();

	/*
	=================
	 END Speed Units
	=================
	*/

	/*
	============
	 Dimensions
	============
	*/
	template<class U>
	class Dimension
	{
	public:
		typedef U UnitType;

		//Member data
	protected:
		double m_value;
		//Since I can't have by reference because I cannot convert to another unit
		//a pointer must be used.  The actual object has to be maintained in the 
		//subclass.
		//ConvertTo() calls the pure virtual function SaveUnit()
		UnitType *m_unit;

		//Constructors
	protected:
		Dimension(double value, UnitType *unit)
			: m_value(value), m_unit(unit)
		{ }

		//Destructor
	public:
		~Dimension()
		{
			DestroyUnit();
		}

		//Helpers
		void DestroyUnit() { if (m_unit) delete m_unit; m_unit = NULL; }
		void AssignUnit(UnitType *newUnit) { DestroyUnit(); m_unit = newUnit; }
		double GetConversionFactor(UnitType const &newUnit) const
		{
			return m_unit->GetFactor() / newUnit.GetFactor();
		}

		//Public Interface
	public:
		double GetValue() const { return m_value; }
		void SetValue(double newValue) { m_value = newValue; }
		UnitType const& GetUnit() const { return m_unit; }
		virtual void ConvertTo(UnitType const &newUnit)
		{
			m_value *= GetConversionFactor(newUnit);
			AssignUnit(dynamic_cast<UnitType*>(newUnit.Clone()));
		}
		//Á la .Net:
		virtual tstring ToString() const
		{
			tostringstream os;
			os << m_value << _T(" ") << m_unit->GetSymbol();
			return os.str();
		}
		virtual void ConvertToStandard()
		{
			ConvertTo(UnitType::StandardUnit);
		}

		//Operators
	public:
		virtual bool operator<(Dimension const &op2) const
		{
			return (m_value * GetConversionFactor(UnitType::StandardUnit))
				< (op2.m_value * op2.GetConversionFactor(UnitType::StandardUnit));
		}
		virtual bool operator>(Dimension const &op2) const
		{
			return op2.operator <(*this);
		}
		virtual bool operator==(Dimension const &op2) const
		{
			return !(this->operator <(op2) || op2.operator <(*this));
		}
	};

	template<class U>
	tostream& operator<<(tostream &op1, Dimension<U> const &op2)
	{
		return (op1 << op2.ToString());
	}

	class Length : public Dimension<LengthUnit>
	{
		//Constructors
	public:
		Length() : Dimension<Length::UnitType>(0, dynamic_cast<Length::UnitType*>(Length::UnitType::StandardUnit.Clone()))
		{ }
		Length(double value, Length::UnitType const &unit) : Dimension<Length::UnitType>(value, dynamic_cast<Length::UnitType*>(unit.Clone()))
		{ }
	};

	class Time : public Dimension<TimeUnit>
	{
		//Constructors
	public:
		Time() : Dimension<Time::UnitType>(0, dynamic_cast<Time::UnitType*>(Time::UnitType::StandardUnit.Clone()))
		{ }
		Time(double value, Time::UnitType const &unit) : Dimension<Time::UnitType>(value, dynamic_cast<Time::UnitType*>(unit.Clone()))
		{ }
	};

	//class Speed : public Dimension<
	/*
	================
	 END Dimensions
	================
	*/
}

/*
===========
 Shortcuts
===========
*/

#ifndef NO_UNIT_SHORTCUTS

#define Meter LIBNS::MeterUnit::Meter
#define Foot LIBNS::FootUnit::Foot

#endif // NO_UNIT_SHORTCUTS

#ifndef NO_PREFIX_SHORTCUTS

#define NoPrefix Prefix::NoPrefix

#define Deca LIBNS::Prefix::Deca
#define Hecto LIBNS::Prefix::Hecto
#define Kilo LIBNS::Prefix::Kilo
#define Mega LIBNS::Prefix::Mega
#define Giga LIBNS::Prefix::Giga

#define deci LIBNS::Prefix::deci
#define centi LIBNS::Prefix::centi
#define milli LIBNS::Prefix::milli
#define micro LIBNS::Prefix::micro
#define nano LIBNS::Prefix::nano

#endif // NO_PREFIX_SHORTCUTS

/*
===============
 END Shortcuts
===============
*/

#include <iostream>

#ifdef _UNICODE
#define tcout std::wcout
#define tcin std::wcin
#else
#define tcout std::cout
#define tcin std::cin
#endif // _UNICODE

using std::endl;

int main()
{
	Length myHeight(30.48, Meter);
	Length doorHeight(100, Foot);
	tcout << _T("I am ") << myHeight << _T(" tall.") << endl;
	tcout << _T("The doorway is ") << doorHeight << _T(" high.") << endl;
	myHeight.ConvertTo(Foot);
	doorHeight.ConvertTo(Meter);
	tcout << _T("I am ") << myHeight.ToString() << _T(" tall.") << endl;
	tcout << _T("The doorway is ") << doorHeight.ToString() << _T(" high.") << endl;
	if (myHeight > doorHeight)
		tcout << _T("You cannot enter the room!  You are too tall. :-(") << endl;
	else if (myHeight < doorHeight)
		tcout << _T("You may enter the room.  You are not that tall.") << endl;
	else
		tcout << _T("Hmmmm... try to enter the room at your own risk!  Your height and the door's height are the same.") << endl;
	system("pause");
}