#pragma once

#include "plug.h"
#include "chain.h"
#include "time.h"
#include "config.h"

#if defined(TRACE_ON)

struct TraceSample
{
	Scalar sampleFraction;
	Word sampleFrame;
	Byte traceNumber;
	Byte sampleType;

	enum Type
	{
		GoingUp = 0,
		GoingDown,
		Marker,
		IntegerSnapshot,
		ScalarSnapshot,
		SuspendSampling,
		ResumeSampling
	};
};

template <class T> struct SnapshotOf : public TraceSample
{
	T snapShot;
};

class Trace : public Plug
{
	friend class TraceManager;

public:
	enum Type
	{
		BitType,
		IntegerType,
		ScalarType
	};

protected:
	static Byte NextTraceID;

	const char *traceName;
	long lastFrameActivity;
	Scalar lastFractionActivity;
	Byte traceNumber;
	Byte traceType;

	Trace(const char* name, Type type);

	MemoryStream *GetTraceLog();
	void IncrementSampleCount();

	virtual void DumpTraceStatus()=0;
	virtual void ResetTrace()=0;

#if defined(USE_TIME_ANALYSIS)
	virtual void StartTiming()=0;
	virtual Scalar CalcuateUsage(long frame, Scalar fraction, double sample_time)=0;
	virtual void PrintUsage(Scalar usage);
#endif
};

class BitTrace : public Trace
{
protected:
	static Byte NextActiveLine;

	Logical traceUp;
	double lastUpTime, totalUpTime;
	Byte activeLine;

	#if defined(USE_ACTIVE_PROFILE)
		static void SetLineImplementation(Byte line);
		static void ClearLineImplementation(Byte line);
		static Logical IsLineValidImplementation(Byte line);
	#endif

	void DumpTraceStatus();
	void ResetTrace();
	#if defined(USE_TIME_ANALYSIS)
		void StartTiming();
		Scalar CalculateUsage(long frame, Scalar fraction, double sample_time);
		void PrintUsage(Scalar usage);
	#endif

public:
	BitTrace(const char *name);

	void Set();
	void Clear();

	double GetLastUpTime()
	{
		Check(this);
		return lastUpTime;
	}

	double GetTotalUpTime()
	{
		Check(this);
		return totalUpTime;
	}
};

template <class T> class TraceOf : public Trace
{
protected:
	double weightedSum;
	T currentValue;

	TraceSample::Type sampleType;

	void DumpTraceStatus();
	void ResetTrace();
	#if defined(USE_TIME_ANALYSIS)
		void StartTiming();
		Scalar CalculateUsage(long frame, Scalar fraction, double sample_time);
		void PrintUsage(Scalar usage);
	#endif

public:
	TraceOf(const char* name, const T& initial_value, Type trace_type, TraceSample::Type sample_type);

	void TakeSnapshot(const T& value);
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
template <class T> TraceOf<T>::TraceOf(const char* name, const T& initial_value, Type trace_type, TraceSample::Type sample_type) : Trace(name, trace_type)
{
	currentValue = initial_value;
	sampleType = sample_type;
	TraceOf<T>::ResetTrace();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
template <class T> void TraceOf<T>::DumpTraceStatus()
{
	Check(this);
	DEBUG_STREAM << currentValue << std::flush;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
template <class T> void TraceOf<T>::ResetTrace()
{
	#if defined(USE_TIME_ANALYSIS)
		weightedSum = 0.0;
	#endif
}

#if defined(USE_TIME_ANALYSIS)
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	template <class T> void TraceOf<T>::StartTiming()
	{
		Check(this);
		weightedSum = 0.0;
	}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	template <class T> Scalar TraceOf<T>::CalculateUsage(long frame, Scalar fraction, double sample_time)
	{
		double last_part = frame - lastFrameActivity;
		last_part += fraction - lastFractionActivity;
		weightedSum += last_part * currentValue;
		Scalar result = weightedSum / sample_time;
		Check_Fpu();
		weightedSum = 0.0;
		return result;
	}

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	template <class T> void TraceOf<T>::PrintUsage(Scalar usage)
	{
		Check(this);

		DEBUG_STREAM << usage << std::flush;
	}

#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
template <class T> void TraceOf<T>::TakeSnapshot(const T& value)
{
	Check(this);

	#if defined(USE_TIME_ANALYSIS) || defined(USE_TRACE_LOG)
		long frame = Now().ticks;
		Scalar fraction = Get_Frame_Percent_Used();
	#endif

	#if defined(USE_TIME_ANALYSIS)
		double last_part = frame - lastFrameActivity;
		last_part += fraction - lastFractionActivity;
		weightedSum += last_part * currentValue;
		lastFrameActivity = frame;
		lastFractionActivity = fraction;
	#endif

	currentValue = value;

	#if defined(USE_TRACE_LOG)
		IncrementSampleCount();
		MemoryStream *log = GetTraceLog();
		if (log)
		{
			Check(log);
			SnapshotOf<T> *sample = (SnapshotOf<T>*)log->GetPointer();
			sample->sampleType = (Byte)sampleType;
			sample->traceNumber = traceNumber;
			sample->sampleFrame = (Word)frame;
			sample->sampleFraction = fraction;
			sample->snapShot = currentValue;
			log->AdvancePointer(sizeof(*sample));
		}
	#endif
}

//#######################################################################
//#####################    TraceManager    ##############################
//#######################################################################

class TraceManager SIGNATURED
{
	friend class Trace;

protected:
	ChainOf<Trace*> traceChain;
	long sampleStartFrame;
	Scalar sampleStartFraction;
	int actualSampleCount, ignoredSampleCount;
	MemoryStream *allocatedTraceLog, *activeTraceLog;
	Byte traceCount;

	void Add(Trace *trace);

public:
	TraceManager();
	~TraceManager();

	Byte GetTraceCount()
	{
		Check(this);
		return traceCount;
	}

	void DumpTracesStatus();
	void ResetTraces();

	#if defined(USE_TIME_ANALYSIS)
		void StartTimingAnalysis();
		int SnapshotTimingAnalysis(Logical print = false);
	#endif

	#if defined(USE_TRACE_LOG)
		void CreateTraceLog(size_t max_trace_samples, Logical start_sampling);
		void SaveTraceLog(const char* filename);
		void MarkTraceLog();
		void SuspendTraceLogging();
		void ResumeTraceLogging();
	#endif

	Logical TestInstance() const;
};

extern TraceManager trace_manager;

inline MemoryStream* Trace::GetTraceLog()
{
	Check(this);
	Check(&trace_manager);
	return trace_manager.activeTraceLog;
}

inline void Trace::IncrementSampleCount()
{
	Check(this);
	Check(&trace_manager);
	if (!trace_manager.activeTraceLog)
		++trace_manager.ignoredSampleCount;
	else
		++trace_manager.actualSampleCount;
}

#	endif
