#ifndef LOGHISTOGRAM_H
#define LOGHISTOGRAM_H

#include <map>
#include <cmath>
#include <stdexcept>
#include <vector>

#include "histogramtablesformatter.h"

#include "../util/serializable.h"

#ifndef HAVE_EXP10
#define exp10(x) exp( (2.3025850929940456840179914546844) * (x) )
#endif

class LogHistogram : public Serializable
{
	private:
		class AmplitudeBin : public Serializable
		{
		public:
			AmplitudeBin() :
				count(0)
			{
			}
			long unsigned count;
			
			long unsigned GetCount() const
			{
				return count;
			}
			
			AmplitudeBin &operator+=(const AmplitudeBin &other)
			{
				count += other.count;
				return *this;
			}
			AmplitudeBin &operator-=(const AmplitudeBin &other)
			{
				if(count >= other.count)
					count -= other.count;
				else
					count = 0;
				return *this;
			}
			
			virtual void Serialize(std::ostream &stream) const final override
			{
				SerializeToUInt64(stream, count);
			}
			virtual void Unserialize(std::istream &stream) final override
			{
				count = UnserializeUInt64(stream);
			}
		};
		
	public:
		LogHistogram()
		{
		}
		
		LogHistogram(const LogHistogram &source) : _amplitudes(source._amplitudes)
		{
		}
		
		void Add(const double amplitude)
		{
			if(std::isfinite(amplitude))
			{
				const double centralAmp = getCentralAmplitude(amplitude);
				AmplitudeBin &bin = getBin(centralAmp);
				++bin.count;
			}
		}
		
		void Add(const LogHistogram &histogram)
		{
			for(std::map<double, AmplitudeBin>::const_iterator i=histogram._amplitudes.begin(); i!=histogram._amplitudes.end();++i)
			{
				AmplitudeBin &bin = getBin(i->first);
				bin += i->second;
			}
		}
		
		void operator-=(const LogHistogram &histogram)
		{
			for(std::map<double, AmplitudeBin>::const_iterator i=histogram._amplitudes.begin(); i!=histogram._amplitudes.end();++i)
			{
				AmplitudeBin &bin = getBin(i->first);
				bin -= i->second;
			}
		}
		
		double MaxAmplitude() const
		{
			if(_amplitudes.empty())
				return 0.0;
			return _amplitudes.rbegin()->first;
		}
		
		double MinPositiveAmplitude() const
		{
			std::map<double, AmplitudeBin>::const_iterator i = _amplitudes.begin();
			if(i == _amplitudes.end())
				return 0.0;
			while(i->first <= 0.0)
			{
				++i;
				if(i == _amplitudes.end())
					return 0.0;
			}
			return i->first;
		}
		
		double NormalizedCount(double startAmplitude, double endAmplitude) const
		{
			unsigned long count = 0;
			for(std::map<double, class AmplitudeBin>::const_iterator i=_amplitudes.begin();i!=_amplitudes.end();++i)
			{
				if(i->first >= startAmplitude && i->first < endAmplitude)
					count += i->second.GetCount();
			}
			return (double) count / (endAmplitude - startAmplitude);
		}
		
		double NormalizedCount(double centreAmplitude) const
		{
			const double key = getCentralAmplitude(centreAmplitude);
			std::map<double, AmplitudeBin>::const_iterator i = _amplitudes.find(key);
			if(i == _amplitudes.end()) return 0.0;
			return (double) i->second.GetCount() / (binEnd(centreAmplitude) - binStart(centreAmplitude));
		}
		
		double MinNormalizedCount() const
		{
			const_iterator i = begin();
			if(i == end())
				return 0.0;
			double minCount = i.normalizedCount();
			do
			{
				const double c = i.normalizedCount();
				if(c < minCount) minCount = c;
				++i;
			} while(i != end());
			return minCount;
		}
		
		double MaxNormalizedCount() const
		{
			double maxCount = 0.0;
			for (LogHistogram::const_iterator i=begin(); i!=end(); ++i)
			{
				if(i.normalizedCount() > maxCount && i.value() > 0 && std::isfinite(i.value()))
					maxCount = i.normalizedCount();
			}
			return maxCount;
		}
		
		double NormalizedTotalCount() const
		{
			unsigned long count = 0;
			for (LogHistogram::const_iterator i=begin(); i!=end(); ++i)
				count += i.unnormalizedCount();
			return count;
		}
		
		double NormalizedCountAbove(double lowerLimitingAmplitude) const
		{
			unsigned long count = 0;
			LogHistogram::const_iterator i=begin();
			while(i!=end() && i.value() <= lowerLimitingAmplitude)
			{
				++i;
			}
			while(i!=end())
			{
				count += i.unnormalizedCount();
				++i;
			}
			return count;
		}
		
		double AmplitudeWithMaxNormalizedCount() const
		{
			double maxCount = 0.0, maxPosition = 0.0;
			for (LogHistogram::const_iterator i=begin(); i!=end(); ++i)
			{
				if(i.normalizedCount() > maxCount && i.value() > 0 && std::isfinite(i.value()))
				{
					maxCount = i.normalizedCount();
					maxPosition = i.value();
				}
			}
			return maxPosition;
		}
		
		double MinPosNormalizedCount() const
		{
			const_iterator i = begin();
			if(i == end())
				return 0.0;
			double minCount = std::isfinite(i.normalizedCount()) ? i.normalizedCount() + 1.0 : 1.0;
			do
			{
				const double c = i.normalizedCount();
				if(c < minCount && c > 0.0 && std::isfinite(c)) minCount = c;
				++i;
			} while(i != end());
			return minCount;
		}
		
		double NormalizedSlope(double startAmplitude, double endAmplitude) const
		{
			unsigned long n = 0;
			long double sumX = 0.0, sumXY = 0.0, sumY = 0.0, sumXSquare = 0.0;
			for(const_iterator i=begin();i!=end();++i)
			{
				if(i.value() >= startAmplitude && i.value() < endAmplitude)
				{
					double x = log10(i.value());
					double y = log10(i.normalizedCount());
					++n;
					sumX += x;
					sumXSquare += x * x;
					sumY += y;
					sumXY += x * y;
				}
			}
			return (sumXY - sumX*sumY/n)/(sumXSquare - (sumX*sumX/n));
		}
		
		
		double PowerLawExponent(double startAmplitude) const
		{
			const long double xMin = startAmplitude;
			long double termSum = 0.0;
			long double termCount = 0.0;
			for(const_iterator i=begin();i!=end();++i)
			{
				const long double x = i.value();
				if(x >= startAmplitude)
				{
					const long double count = i.unnormalizedCount();
					const long double thisTerm = logl(x / xMin);
					termCount += count;
					termSum += thisTerm * count;
				}
			}
			return (double) (-1.0L - termCount / termSum);
		}
		
		double PowerLawExponentStdError(double startAmplitude, double expEstimator) const
		{
			long double termCount = 0.0;
			for(const_iterator i=begin();i!=end();++i)
			{
				if(i.value() >= startAmplitude)
				{
					const long double count = i.unnormalizedCount();
					termCount += count;
				}
			}
			return (double) ((-expEstimator - 1.0L) / sqrtl(termCount));
		}
		
		double NormalizedSlopeOffset(double startAmplitude, double endAmplitude, double slope) const
		{
			unsigned long n = 0;
			long double sumOffset = 0.0;
			for(const_iterator i=begin();i!=end();++i)
			{
				if(i.value() >= startAmplitude && i.value() < endAmplitude)
				{
					double y = log10(i.normalizedCount());
					double x = log10(i.value());
					double ySlope = x*slope;
					++n;
					sumOffset += (y - ySlope);
				}
			}
			return (double) (sumOffset/(long double) n);
		}
		
		double NormalizedSlopeStdError(double startAmplitude, double endAmplitude, double slope) const
		{
			long double ssxx = 0.0, ssxy = 0.0, ssyy = 0.0, xSum = 0.0, ySum = 0.0;
			unsigned long n = 0;
			// determine the 'average' x and y
			for(const_iterator i=begin();i!=end();++i)
			{
				if(i.value() >= startAmplitude && i.value() < endAmplitude)
				{
					xSum += log10(i.value());
					ySum += log10(i.normalizedCount());
					++n;
				}
			}
			const long double avgX = xSum / (long double) n, avgY = ySum / (long double) n;
			for(const_iterator i=begin();i!=end();++i)
			{
				if(i.value() >= startAmplitude && i.value() < endAmplitude)
				{
					long double y = log10(i.normalizedCount());
					long double x = log10(i.value());
					ssxx += (x-avgX)*(x-avgX);
					ssxy += (x-avgX)*(y-avgY);
					ssyy += (y-avgY)*(y-avgY);
				}
			}
			return (double) sqrtl((ssyy-slope*ssxy)/(ssxx * (long double) (n-2)));
		}
		
		double NormalizedSlopeStdDevBySampling(double startAmplitude, double endAmplitude, double slope, double stepFactor) const
		{
			long double sum = 0.0;
			unsigned long n = 0;
			if(stepFactor <= 1.0001) stepFactor = 1.0001;
			while(startAmplitude < endAmplitude)
			{
				const double stepEnd = startAmplitude * stepFactor;
				double sampledSlope = NormalizedSlope(startAmplitude, stepEnd);
				double sampleError = sampledSlope - slope;
				sum += sampleError * sampleError;
				++n;
				
				startAmplitude = stepEnd;
			}
			
			return (double) sqrtl(sum / ((long double) n*n - n));
		}
		
		double PowerLawUpperLimit(double constrainingAmplitude, double exponent, double factor) const
		{
			const double count = NormalizedCountAbove(constrainingAmplitude);
			const double term = count * (exponent+1.0)/factor + pow(constrainingAmplitude, exponent+1.0);
			return pow(term, 1.0/(exponent+1.0));
		}
		
		double PowerLawLowerLimit(double constrainingAmplitude, double exponent, double factor, double rfiRatio) const
		{
			const double countPart = NormalizedCountAbove(constrainingAmplitude);
			const double countTotal = NormalizedTotalCount() * rfiRatio;
			const double term = (countPart - countTotal) * (exponent+1.0)/factor + pow(constrainingAmplitude, exponent+1.0);
			return pow(term, 1.0/(exponent+1.0));
		}
		
		double PowerLawLowerLimit2(double constrainingAmplitude, double exponent, double factor, double rfiRatio) const
		{
			const double countPart = NormalizedCountAbove(constrainingAmplitude);
			const double countTotal = NormalizedTotalCount() * rfiRatio;
			const double term = (countPart - countTotal) * (exponent+1.0)/factor + pow(constrainingAmplitude, exponent+1.0);
			return pow(term/-exponent, 1.0/(exponent+1.0));
		}
		
		void GetRFIRegion(double &start, double &end) const
		{
			double sigmaEstimate = AmplitudeWithMaxNormalizedCount();
			double maxAmplitude = MaxAmplitude();
			start = sigmaEstimate * 20.0;
			double halfWay = exp((log(start) + log(maxAmplitude)) * 0.5);
			end = halfWay;
		}

		double NormalizedSlopeInRFIRegion() const
		{
			double start, end;
			GetRFIRegion(start ,end);
			return NormalizedSlope(start, end);
		}
		
		void SetData(std::vector<HistogramTablesFormatter::HistogramItem> &histogramData)
		{
			for(std::vector<HistogramTablesFormatter::HistogramItem>::const_iterator i=histogramData.begin(); i!=histogramData.end();++i)
			{
				const double b = (i->binStart + i->binEnd) * 0.5; // TODO somewhat inefficient...
				getBin(getCentralAmplitude(b)).count = (unsigned long) i->count;
			}
		}
		
		void Rescale(double factor)
		{
			std::map<double, AmplitudeBin> newAmplitudes;
			std::map<double, AmplitudeBin>::iterator position = newAmplitudes.begin();
			for(std::map<double, AmplitudeBin>::const_iterator i=_amplitudes.begin();
					i!=_amplitudes.end();++i)
			{
				position = newAmplitudes.insert(position, std::pair<double, AmplitudeBin>(getCentralAmplitude(i->first * factor), i->second));
			}
			_amplitudes = newAmplitudes;
		}
		
		class const_iterator
		{
			public:
				const_iterator(const LogHistogram &histogram, std::map<double, AmplitudeBin>::const_iterator iter) :
					_iterator(iter)
				{ }
				const_iterator(const const_iterator &source) :
					_iterator(source._iterator)
				{ }
				const_iterator &operator=(const const_iterator &source)
				{
					_iterator = source._iterator;
					return *this;
				}
				bool operator==(const const_iterator &other) const { return other._iterator == _iterator; }
				bool operator!=(const const_iterator &other) const { return other._iterator != _iterator; }
				const_iterator &operator++() { ++_iterator; return *this; }
				const_iterator &operator--() { --_iterator; return *this; }
				double value() const { return _iterator->first; }
				double normalizedCount() const { return _iterator->second.GetCount() / (binEnd() - binStart()); }
				long unsigned unnormalizedCount() const { return _iterator->second.GetCount(); }
				double binStart() const
				{
					return _iterator->first>0.0 ?
						exp10(log10(_iterator->first)-0.005) :
						-exp10(log10(-_iterator->first)-0.005);
				}
				double binEnd() const
				{
					return _iterator->first>0.0 ?
						exp10(log10(_iterator->first)+0.005) :
						-exp10(log10(-_iterator->first)+0.005);
				}
			private:
				std::map<double, AmplitudeBin>::const_iterator _iterator;
		};
		typedef const_iterator iterator;
		
		const_iterator begin() const
		{
			return const_iterator(*this, _amplitudes.begin());
		}
		
		const_iterator end() const
		{
			return const_iterator(*this, _amplitudes.end());
		}
		
		virtual void Serialize(std::ostream &stream) const final override
		{
			SerializeToUInt64(stream, _amplitudes.size());
			for(std::map<double, AmplitudeBin>::const_iterator i=_amplitudes.begin();i!=_amplitudes.end();++i)
			{
				SerializeToDouble(stream, i->first);
				i->second.Serialize(stream);
			}
		}
		
		virtual void Unserialize(std::istream &stream) final override
		{
			_amplitudes.clear();
			size_t mapSize = UnserializeUInt64(stream);
			std::map<double, AmplitudeBin>::iterator insertPos = _amplitudes.begin();
			for(size_t i=0;i!=mapSize;++i)
			{
				std::pair<double, AmplitudeBin> p;
				p.first = UnserializeDouble(stream);
				p.second.Unserialize(stream);
				insertPos = _amplitudes.insert(insertPos, p);
			}
		}
		
		void CreateMissingBins()
		{
			double first = MinPositiveAmplitude(), last = MaxAmplitude();
			for(double i=first; i<last; i *= 1.01)
			{
				getBin(getCentralAmplitude(i));
			}
		}
	private:
		std::map<double, AmplitudeBin> _amplitudes;
		
		AmplitudeBin &getBin(double centralAmplitude)
		{
			std::map<double, class AmplitudeBin>::iterator element =
				_amplitudes.find(centralAmplitude);
			
			if(element == _amplitudes.end())
			{
				element = _amplitudes.insert(std::pair<double, AmplitudeBin>(centralAmplitude, AmplitudeBin())).first;
			}
			return element->second;
		}
		double binStart(double x) const
		{
			return x>0.0 ?
				exp10(log10(x)-0.005) :
				-exp10(log10(x)-0.005);
		}
		double binEnd(double x) const
		{
			return x>0.0 ?
				exp10(log10(x)+0.005) :
				-exp10(log10(x)+0.005);
		}
		
		static double getCentralAmplitude(const double amplitude)
		{
			if(amplitude>=0.0)
				return exp10(round(100.0*log10(amplitude))/100.0);
			else
				return -exp10(round(100.0*log10(-amplitude))/100.0);
		}
};

#endif
