#ifndef TDE_WIDGETS_DISPLAYUNIT_H
#define TDE_WIDGETS_DISPLAYUNIT_H

#include "common.h"

// -- TDE NAMESPACE ----------------------------------------------------

namespace TDE {


class DisplayUnitManager;
class DisplayUnit;

typedef SHARED_PTR<DisplayUnitManager> DisplayUnitManagerPtr;
typedef SHARED_PTR<DisplayUnit> DisplayUnitPtr;

// http://stackoverflow.com/a/18581693
inline long du_pow10(int n)
{
	static long t_pow10[10] = {
		1, 10, 100, 1000, 10000,
		100000, 1000000, 10000000, 100000000, 1000000000
	};

	if (n < 10)
		return t_pow10[n];

	long r = 1;
	const int x = 10;

	while (n)
	{
		if (n & 1)
		{
			r *= x;
		} else
		{
			r *= x * x;
			n -= 2;
		}
	}

	return r;
}

inline plc_float_t du_i2d(int32_t value, int decimals)
{
	double f_value = static_cast<double>(value);
	if (decimals > 0)
		f_value /= pow10(decimals);
	return static_cast<plc_float_t>(f_value);
}

inline int32_t du_d2i(double f_value, int decimals)
{
	if (decimals > 0)
		f_value *= pow10(decimals);
	return static_cast<int32_t>(f_value);
}

// -- DisplayUnitKind -------------------------------------------------

namespace DisplayUnitKind
{
	enum Enum { Unknown = 0, Length, Area, Volume, Time, Speed, Force, Pressure, Temperature, Acceleration };

	std::string name(Enum value);
}

// -- DisplayUnit -----------------------------------------------------

class DisplayUnit : public ENABLE_SHARED_FROM_THIS<DisplayUnit>
{
public:
	// static DisplayUnitPtr Make(const std::string& name);

	virtual ~DisplayUnit();

	bool manage();

	const std::string& name() const { return m_name; }
	DisplayUnitKind::Enum kind() const { return m_kind; }

	bool operator== (const DisplayUnit& other) { return (name() == other.name()) && (kind() == other.kind()); }

	virtual int32_t     internalToDisplay(int32_t value) = 0;
	virtual plc_float_t internalToDisplay(plc_float_t value) = 0;

	virtual int32_t     displayToInternal(int32_t value) = 0;
	virtual plc_float_t displayToInternal(plc_float_t value) = 0;

protected:
	DisplayUnit(const std::string& name, DisplayUnitKind::Enum kind);

private:
	DisplayUnit();
	DisplayUnit(const DisplayUnit& other);

	friend class DisplayUnitManager;

	bool _deregister();

	std::string m_name;
	DisplayUnitKind::Enum m_kind;
};

// -- DisplayUnitManager ----------------------------------------------

class DisplayUnitManager
{
public:
    static SHARED_PTR<DisplayUnitManager> Instance();

    static bool           Has(const std::string& name) { return Instance()->has(name); }
    static DisplayUnitPtr Get(const std::string& name) { return Instance()->get(name); }
	static std::vector<DisplayUnitPtr> List()          { return Instance()->list(); }
    static int32_t        InternalToDisplay(const std::string& name, int32_t value)     { return Instance()->internalToDisplay(name, value); }
    static plc_float_t    InternalToDisplay(const std::string& name, plc_float_t value) { return Instance()->internalToDisplay(name, value); }
    static int32_t        DisplayToInternal(const std::string& name, int32_t value)     { return Instance()->displayToInternal(name, value); }
    static plc_float_t    DisplayToInternal(const std::string& name, plc_float_t value) { return Instance()->displayToInternal(name, value); }

    static int32_t        Convert(const std::string& dstName, const std::string& srcName, int32_t srcValue)
      { return Instance()->convert(dstName, srcName, srcValue); }
    static int32_t        Convert(const std::string& dstName, const std::string& srcName, int32_t srcValue, int decimals)
      { return Instance()->convert(dstName, srcName, srcValue, decimals); }
    static plc_float_t    Convert(const std::string& dstName, const std::string& srcName, plc_float_t srcValue)
      { return Instance()->convert(dstName, srcName, srcValue); }

    static int32_t        Convert(DisplayUnitPtr dst, DisplayUnitPtr src, int32_t srcValue)
      { return Instance()->convert(dst, src, srcValue); }
    static int32_t        Convert(DisplayUnitPtr dst, DisplayUnitPtr src, int32_t srcValue, int decimals)
      { return Instance()->convert(dst, src, srcValue, decimals); }
    static plc_float_t    Convert(DisplayUnitPtr dst, DisplayUnitPtr src, plc_float_t srcValue)
      { return Instance()->convert(dst, src, srcValue); }

    // default display units
    static bool           HasDefaultDisplayUnit(DisplayUnitKind::Enum kind) { return Instance()->hasDefaultDisplayUnit(kind); }
    static DisplayUnitPtr GetDefaultDisplayUnit(DisplayUnitKind::Enum kind) { return Instance()->getDefaultDisplayUnit(kind); }
    static void           SetDefaultDisplayUnit(DisplayUnitKind::Enum kind, DisplayUnitPtr unitPtr) { Instance()->setDefaultDisplayUnit(kind, unitPtr); }
    static void           SetDefaultDisplayUnit(DisplayUnitKind::Enum kind, const std::string& unitName) { Instance()->setDefaultDisplayUnit(kind, unitName); }


	bool           has(const std::string& name) { return m_name_to_unit.count(name) ? true : false; }
	DisplayUnitPtr get(const std::string& name);
	std::vector<DisplayUnitPtr> list();

	int32_t     internalToDisplay(const std::string& name, int32_t value)      { return get(name)->internalToDisplay(value); }
	plc_float_t internalToDisplay(const std::string& name, plc_float_t value)  { return get(name)->internalToDisplay(value); }
	int32_t     displayToInternal(const std::string& name, int32_t value)      { return get(name)->displayToInternal(value); }
	plc_float_t displayToInternal(const std::string& name, plc_float_t value)  { return get(name)->displayToInternal(value); }

	int32_t convert(const std::string& dstName, const std::string& srcName, int32_t srcValue)
	{
		if (dstName == srcName)
			return srcValue;
		if (srcName.empty())
			return get(dstName)->internalToDisplay(srcValue);
		if (dstName.empty())
			return get(srcName)->displayToInternal(srcValue);
		double internalValue = get(srcName)->displayToInternal((plc_float_t)srcValue);
		return (int32_t)get(dstName)->internalToDisplay((plc_float_t)internalValue);
	}
	int32_t convert(const std::string& dstName, const std::string& srcName, int32_t srcValue, int decimals)
	{
		if (dstName == srcName)
			return srcValue;
		if (srcName.empty())
			return du_d2i(get(dstName)->internalToDisplay( du_i2d(srcValue, decimals) ), decimals);
		if (dstName.empty())
			return du_d2i(get(srcName)->displayToInternal( du_i2d(srcValue, decimals) ), decimals);
		double internalValue = get(srcName)->displayToInternal( du_i2d(srcValue, decimals) );
		return du_d2i(get(dstName)->internalToDisplay((plc_float_t)internalValue), decimals);
	}
	plc_float_t convert(const std::string& dstName, const std::string& srcName, plc_float_t srcValue)
	{
		if (dstName == srcName)
			return srcValue;
		if (srcName.empty())
			return get(dstName)->internalToDisplay(srcValue);
		if (dstName.empty())
			return get(srcName)->displayToInternal(srcValue);
		plc_float_t internalValue = get(srcName)->displayToInternal(srcValue);
		return get(dstName)->internalToDisplay(internalValue);
	}

	int32_t convert(DisplayUnitPtr dst, DisplayUnitPtr src, int32_t srcValue)
	{
		if (dst == src)
			return srcValue;
		if (!src)
			return dst->internalToDisplay(srcValue);
		if (!dst)
			return src->displayToInternal(srcValue);
		return (int32_t)dst->internalToDisplay((plc_float_t)src->displayToInternal((plc_float_t)srcValue));
	}
	int32_t convert(DisplayUnitPtr dst, DisplayUnitPtr src, int32_t srcValue, int decimals)
	{
		if (dst == src)
			return srcValue;
		if (!src)
			return du_d2i(dst->internalToDisplay( du_i2d(srcValue, decimals) ), decimals);
		if (!dst)
			return du_d2i(src->displayToInternal( du_i2d(srcValue, decimals) ), decimals);
		return du_d2i( dst->internalToDisplay((plc_float_t)src->displayToInternal( du_i2d(srcValue, decimals) )), decimals );
	}
	plc_float_t convert(DisplayUnitPtr dst, DisplayUnitPtr src, plc_float_t srcValue)
	{
		if (dst == src)
			return srcValue;
		if (!src)
			return dst->internalToDisplay(srcValue);
		if (!dst)
			return src->displayToInternal(srcValue);
		return dst->internalToDisplay(src->displayToInternal(srcValue));
	}

    // default display units
    bool           hasDefaultDisplayUnit(DisplayUnitKind::Enum kind) { return m_default_unit.count(kind) ? true : false; }
    DisplayUnitPtr getDefaultDisplayUnit(DisplayUnitKind::Enum kind) { return m_default_unit[kind]; }
    void           setDefaultDisplayUnit(DisplayUnitKind::Enum kind, DisplayUnitPtr unitPtr);
    void           setDefaultDisplayUnit(DisplayUnitKind::Enum kind, const std::string& unitName);

private:
	DisplayUnitManager();
	DisplayUnitManager(const DisplayUnitManager& other);

	static void CleanUp();

	bool _register(DisplayUnitPtr unitPtr);
	bool _deregister(DisplayUnitPtr unitPtr);

	friend bool DisplayUnit::manage();
	friend bool DisplayUnit::_deregister();

	std::map<std::string, DisplayUnitPtr> m_name_to_unit;
	std::string    m_cache_last_name;
	DisplayUnitPtr m_cache_last_object;

	// default display unit for kind
	std::map<DisplayUnitKind::Enum, DisplayUnitPtr> m_default_unit;

	static SHARED_PTR<DisplayUnitManager> m_instance;
};

class DisplayUnitIdentity : public DisplayUnit
{
public:
	static DisplayUnitPtr Make(const std::string& name, DisplayUnitKind::Enum kind)
	{
		DisplayUnitPtr ptr = DisplayUnitPtr( new DisplayUnitIdentity(name, kind) );
		ptr->manage();
		return ptr;
	}

	virtual int32_t     internalToDisplay(int32_t value)     { return value; }
	virtual plc_float_t internalToDisplay(plc_float_t value) { return value; }

	virtual int32_t     displayToInternal(int32_t value)     { return value; }
	virtual plc_float_t displayToInternal(plc_float_t value) { return value; }

protected:
	DisplayUnitIdentity(const std::string& name, DisplayUnitKind::Enum kind) : DisplayUnit(name, kind) {}
};

class DisplayUnitLinear : public DisplayUnit
{
public:
	static DisplayUnitPtr Make(const std::string& name, DisplayUnitKind::Enum kind, double fScale = 1.0, double fOffset = 0.0)
	{
		DisplayUnitPtr ptr = DisplayUnitPtr( new DisplayUnitLinear(name, kind, fScale, fOffset) );
		ptr->manage();
		return ptr;
	}

	virtual int32_t     internalToDisplay(int32_t value)     { return (int32_t)(((double)value * m_fScale) + m_fOffset); }
	virtual plc_float_t internalToDisplay(plc_float_t value) { return (value * m_fScale) + m_fOffset; }

	virtual int32_t     displayToInternal(int32_t value)     { return (int32_t)(((double)value - m_fOffset) / m_fScale); }
	virtual plc_float_t displayToInternal(plc_float_t value) { return (value - m_fOffset) / m_fScale; }

protected:
	DisplayUnitLinear(const std::string& name, DisplayUnitKind::Enum kind, double fScale = 1.0, double fOffset = 0.0) :
		DisplayUnit(name, kind), m_fScale(fScale), m_fOffset(fOffset) {}

private:
	double m_fScale;
	double m_fOffset;
};


} /* end of namespace TDE */

#endif /* TDE_WIDGETS_DISPLAYUNIT_H */
