/*
 *
 *
 *    fixed.h
 *
 *    Fixed point math.
 *
 *    Copyright (C) 2016 by Matt Roberts.
 *    License: GNU GPL3 (www.gnu.org)
 *
 *
 */

#ifndef __KK5JY_FIXED_H
#define __KK5JY_FIXED_H

#ifdef __linux__
#include <limits>
#include <cstdlib>
#include <stdint.h>
#include <math.h>
#include <iosfwd>
#endif
#include "nlimits.h"

#ifndef FIXED_POINT_WHOLE_BITS
#define FIXED_POINT_WHOLE_BITS (0)
#endif

namespace KK5JY {
	namespace DSP {
		//
		//  FixedPoint
		//
		template <typename T = int>
		class FixedPoint {
			public:
				const size_t DivisorShift;
				T Numerator;

			private:
				T GetNumerator(double value);
				
			public:
				FixedPoint(double value, double norm = 1.0);
				FixedPoint(const FixedPoint<T> &src);
				FixedPoint();

			public:
				double toDouble() const;
				double toFloat() const;

				FixedPoint<T> operator = (double d);
				FixedPoint<T> operator = (float d);
				FixedPoint<T> operator = (const FixedPoint<T> other);

				FixedPoint<T> operator - () const;

				//operator double () const { return toDouble(); }
		};

		///////////////////////////////////////////////////////////////////
		//
		//   Constructors
		//
		///////////////////////////////////////////////////////////////////

		template <typename T>
		inline FixedPoint<T>::FixedPoint(const FixedPoint<T> &src)
			: DivisorShift(src.DivisorShift),
			  Numerator(src.Numerator) {
			// nop
		}

		template <typename T>
		inline FixedPoint<T>::FixedPoint(double value, double norm)
			: DivisorShift(norm * (((sizeof(T) * 8) - ((norm_limits<T>::is_signed ? 2 : 1) + FIXED_POINT_WHOLE_BITS)))),
			  Numerator(GetNumerator(value)) {
			// nop
		}

		template <typename T>
		inline FixedPoint<T>::FixedPoint()
			: DivisorShift(1.0 * (((sizeof(T) * 8) - ((norm_limits<T>::is_signed ? 2 : 1) + FIXED_POINT_WHOLE_BITS)))),
			  Numerator(0) {
			// nop
		}

		template <typename T>
		inline T FixedPoint<T>::GetNumerator(double value) {
			return static_cast<T>(1LU << DivisorShift) * value;
		}

		///////////////////////////////////////////////////////////////////
		//
		//   Assignment Operators
		//
		///////////////////////////////////////////////////////////////////

		// = double
		template <typename T>
		inline FixedPoint<T> FixedPoint<T>::operator = (double d) {
			Numerator = GetNumerator(d);
			return *this;
		}

		// = float
		template <typename T>
		inline FixedPoint<T> FixedPoint<T>::operator = (float d) {
			Numerator = GetNumerator(d);
			return *this;
		}

		// = FixedPoint<T>
		template <typename T>
		inline FixedPoint<T> FixedPoint<T>::operator = (const FixedPoint<T> other) {
			if (this->DivisorShift == other.DivisorShift) {
				this->Numerator = other.Numerator;
				return *this;
			}

			// otherwise, we have to convert
			*this = other.toDouble();
			return *this;
		}

		///////////////////////////////////////////////////////////////////
		//
		//   Comparisons & Conversions
		//
		///////////////////////////////////////////////////////////////////

		// n == fp
		template <typename T, typename U>
		inline bool operator == (const U &f1, const FixedPoint<T> &f2) {
			if (f1 == 0) return f2.Numerator == 0;
			return f2.toDouble() == f1;
		}

		// n != fp
		template <typename T, typename U>
		inline bool operator != (const U &f1, const FixedPoint<T> &f2) {
			if (f1 == 0) return f2.Numerator != 0;
			return f2.toDouble() != f1;
		}

		// fp == n
		template <typename T, typename U>
		inline bool operator == (const FixedPoint<T> &f1, const U &f2) {
			if (f2 == 0) return f1.Numerator == 0;
			return f1.toDouble() == f2;
		}

		// fp != n
		template <typename T, typename U>
		inline bool operator != (const FixedPoint<T> &f1, const U &f2) {
			if (f2 == 0) return f1.Numerator != 0;
			return f1.toDouble() != f2;
		}

		// fp == fp
		template <typename T>
		inline bool operator == (const FixedPoint<T> &f1, const FixedPoint<T> &f2) {
			return f1.Numerator == f2.Numerator;
		}

		// fp != fp
		template <typename T>
		inline bool operator != (const FixedPoint<T> &f1, const FixedPoint<T> &f2) {
			return f1.Numerator != f2.Numerator;
		}

		// convert to double
		template <typename T>
		inline double FixedPoint<T>::toDouble() const {
			return static_cast<double>(Numerator) / static_cast<double>(1u << DivisorShift);
		}

		// convert to float
		template <typename T>
		inline double FixedPoint<T>::toFloat() const {
			return static_cast<float>(Numerator) / static_cast<float>(1u << DivisorShift);
		}

		///////////////////////////////////////////////////////////////////
		//
		//   Operations
		//
		///////////////////////////////////////////////////////////////////

		template <typename T>
		inline FixedPoint<T> FixedPoint<T>::operator - () const {
			FixedPoint<T> result(*this);
			result.Numerator = -Numerator;
			return result;
		}

#if ALLOW_TEMPLATE_OPERATIONS
		//
		//  T * fp - disable for now, while testing specializations
		//

		// T * fp
		template <typename T>
		inline T operator * (const T t1, const FixedPoint<T> &fp) {
			return (t1 * fp.Numerator) >> fp.DivisorShift;
		}

		// fp * T
		template <typename T>
		inline T operator * (const FixedPoint<T> &fp, const T t1) {
			return (t1 * fp.Numerator) >> fp.DivisorShift;
		}
#endif
		// fp / n
		template <typename T, typename U>
		inline T operator / (const FixedPoint<T> &f1, const U &n) {
			return FixedPoint<T>(f1.Numerator / n, f1.DivisorShift);
		}

		// fp + fp
		template <typename T>
		inline FixedPoint<T> operator + (const FixedPoint<T> &f1, const FixedPoint<T> &f2) {
			return FixedPoint<T>(f1.toDouble() + f2.toDouble());
		}

		// fp + n
		template <typename T>
		inline FixedPoint<T> operator + (const FixedPoint<T> &f1, const double &f2) {
			return FixedPoint<T>(f1.toDouble() + f2);
		}

		// n + fp
		template <typename T>
		inline FixedPoint<T> operator + (const double &f1, const FixedPoint<T> &f2) {
			return FixedPoint<T>(f1 + f2.toDouble());
		}

		// fp - fp
		template <typename T>
		inline FixedPoint<T> operator - (const FixedPoint<T> &f1, const FixedPoint<T> &f2) {
			return FixedPoint<T>(f1.toDouble() - f2.toDouble());
		}

		// fp - n
		template <typename T>
		inline FixedPoint<T> operator - (const FixedPoint<T> &f1, const double &f2) {
			return FixedPoint<T>(f1.toDouble() - f2);
		}

		// n - fp
		template <typename T>
		inline FixedPoint<T> operator - (const double &f1, const FixedPoint<T> &f2) {
			return FixedPoint<T>(f1 - f2.toDouble());
		}

		///////////////////////////////////////////////////////////////////
		//
		//   Specializations
		//
		///////////////////////////////////////////////////////////////////

		//
		//  int32
		//

		// fp * int32
		inline int32_t operator * (const FixedPoint<int32_t> &fp, const int32_t t1) {
			return (static_cast<int64_t>(t1) * static_cast<int64_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		// int32 * fp
		inline int32_t operator * (const int32_t t1, const FixedPoint<int32_t> &fp) {
			return (static_cast<int64_t>(t1) * static_cast<int64_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		//
		//  uint32
		//

		// fp * uint32
		inline uint32_t operator * (const FixedPoint<uint32_t> &fp, const uint32_t t1) {
			return (static_cast<uint64_t>(t1) * static_cast<uint64_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		// uint32 * fp
		inline uint32_t operator * (const uint32_t t1, const FixedPoint<uint32_t> &fp) {
			return (static_cast<uint64_t>(t1) * static_cast<uint64_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		//
		//  int16
		//

		// fp * int16
		inline int16_t operator * (const FixedPoint<int16_t> &fp, const int16_t t1) {
			return (t1 * static_cast<int32_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		// int16 * fp
		inline int16_t operator * (const int16_t t1, const FixedPoint<int16_t> &fp) {
			return (t1 * static_cast<int32_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		//
		//  uint16
		//

		// fp * uint16
		inline uint16_t operator * (const FixedPoint<uint16_t> &fp, const uint16_t t1) {
			return (t1 * static_cast<uint32_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		// uint16 * fp
		inline uint16_t operator * (const uint16_t t1, const FixedPoint<uint16_t> &fp) {
			return (t1 * static_cast<uint32_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		//
		//  int8
		//

		// fp * int8
		inline int8_t operator * (const FixedPoint<int8_t> &fp, const int8_t t1) {
			return (t1 * static_cast<int32_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		// int8 * fp
		inline int8_t operator * (const int8_t t1, const FixedPoint<int8_t> &fp) {
			return (t1 * static_cast<int32_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		//
		//  uint8
		//

		// fp * uint8
		inline uint8_t operator * (const FixedPoint<uint8_t> &fp, const uint8_t t1) {
			return (t1 * static_cast<uint16_t>(fp.Numerator)) >> fp.DivisorShift;
		}

		// uint8 * fp
		inline uint8_t operator * (const uint8_t t1, const FixedPoint<uint8_t> &fp) {
			return (t1 * static_cast<uint16_t>(fp.Numerator)) >> fp.DivisorShift;
		}

#ifdef __linux__
		///////////////////////////////////////////////////////////////////
		//
		//   iostream support
		//
		///////////////////////////////////////////////////////////////////

		template <typename T>
		std::ostream &operator << (std::ostream &out, const FixedPoint<T> &fp) {
			out << fp.toDouble();
			return out;
		}
#endif
	}
}

#endif // __KK5JY_FIXED_H
