#ifndef TIMEUTILS_H
#define TIMEUTILS_H
#ifdef _WIN32
#pragma once
#endif

#include <limits.h>
#include <math.h>
#include "dbg.h"

enum RoundStyle_t
{
	ROUND_NEAREST,
	ROUND_DOWN,
	ROUND_UP,
};

class DmeTime_t;

class DmeFramerate_t
{
public:
	DmeFramerate_t(float fps);
	DmeFramerate_t(int fps = 0);
	DmeFramerate_t(const DmeFramerate_t& src) : m_num(src.m_num), m_den(src.m_den) {}

	void SetFramerate(float flFrameRate);
	void SetFramerate(int fps);
	void SetFramerateNTSC(int multiplier = 30);

	float GetFramesPerSecond() const;
	DmeTime_t GetTimePerFrame() const;

	bool operator==(DmeFramerate_t f) const { return m_num == f.m_num && m_den == f.m_den; }
	bool operator!=(DmeFramerate_t f) const { return m_num != f.m_num && m_den != f.m_den; }
	bool operator< (DmeFramerate_t f) const { return m_num * (int)f.m_den < f.m_num* (int)m_den; }
	bool operator> (DmeFramerate_t f) const { return m_num * (int)f.m_den > f.m_num * (int)m_den; }
	bool operator<=(DmeFramerate_t f) const { return m_num * (int)f.m_den <= f.m_num * (int)m_den; }
	bool operator>=(DmeFramerate_t f) const { return m_num * (int)f.m_den >= f.m_num * (int)m_den; }

	DmeFramerate_t operator*(int i) const { return DmeFramerate_t(m_num * i, m_den); }
	DmeFramerate_t operator/(int i) const { return DmeFramerate_t(m_num, m_den * i); }

	DmeFramerate_t operator*=(int i) { Assert(abs(m_num * i) <= USHRT_MAX); m_num *= (unsigned short)i; return *this; }
	DmeFramerate_t operator/=(int i) { Assert(abs(m_den * i) <= USHRT_MAX); m_den *= (unsigned short)i; return *this; }

private:
	DmeFramerate_t(int nNumerator, int nDenominator);

	unsigned short m_num;
	unsigned short m_den;

	friend class DmeTime_t;
};

#define DMETIME_ZERO		DmeTime_t(0)
#define DMETIME_MINDELTA	DmeTime_t::MinTimeDelta()
#define DMETIME_MINTIME		DmeTime_t::MinTime()
#define DMETIME_MAXTIME		DmeTime_t::MaxTime()
#define DMETIME_INVALID		DmeTime_t::InvalidTime()

class DmeTime_t
{
public:
	DmeTime_t() : m_tms(INT_MIN) {}   
	explicit DmeTime_t(int tms) : m_tms(tms) {}
	explicit DmeTime_t(float  sec) : m_tms(RoundSecondsToTMS(sec)) {}
	explicit DmeTime_t(double sec) : m_tms(RoundSecondsToTMS(sec)) {}
	DmeTime_t(int frame, DmeFramerate_t framerate);


	friend bool operator==(DmeTime_t a, DmeTime_t b) { return a.m_tms == b.m_tms; }
	friend bool operator!=(DmeTime_t a, DmeTime_t b) { return a.m_tms != b.m_tms; }
	friend bool operator< (DmeTime_t a, DmeTime_t b) { return a.m_tms < b.m_tms; }
	friend bool operator> (DmeTime_t a, DmeTime_t b) { return a.m_tms > b.m_tms; }
	friend bool operator<=(DmeTime_t a, DmeTime_t b) { return a.m_tms <= b.m_tms; }
	friend bool operator>=(DmeTime_t a, DmeTime_t b) { return a.m_tms >= b.m_tms; }

	friend DmeTime_t operator%(DmeTime_t a, DmeTime_t b) { return DmeTime_t(a.m_tms % b.m_tms); }
	friend DmeTime_t operator+(DmeTime_t a, DmeTime_t b) { return DmeTime_t(a.m_tms + b.m_tms); }
	friend DmeTime_t operator-(DmeTime_t a, DmeTime_t b) { return DmeTime_t(a.m_tms - b.m_tms); }

	DmeTime_t operator-() const { return DmeTime_t(-m_tms); }

	DmeTime_t operator+=(DmeTime_t t) { m_tms += t.m_tms; return *this; }
	DmeTime_t operator-=(DmeTime_t t) { m_tms -= t.m_tms; return *this; }

	friend DmeTime_t operator*(DmeTime_t t, float f) { t *= f; return t; }
	friend DmeTime_t operator*(float f, DmeTime_t t) { t *= f; return t; }
	friend DmeTime_t operator/(DmeTime_t t, float f) { t /= f; return t; }
	friend float     operator/(DmeTime_t n, DmeTime_t d) { return float(n.m_tms / double(d.m_tms)); }

	DmeTime_t operator*=(float f);
	DmeTime_t operator/=(float f);

	DmeTime_t operator++() { ++m_tms; return *this; }
	DmeTime_t operator--() { --m_tms; return *this; }
	DmeTime_t operator++(int) { DmeTime_t t = *this; ++m_tms; return t; }   
	DmeTime_t operator--(int) { DmeTime_t t = *this; --m_tms; return t; }   

	bool IsValid() const { return m_tms != INT_MIN; }

	static DmeTime_t InvalidTime() { return DmeTime_t(INT_MIN); }
	static DmeTime_t MinTime() { return DmeTime_t(INT_MIN + 1); }
	static DmeTime_t MaxTime() { return DmeTime_t(INT_MAX); }
	static DmeTime_t MinTimeDelta() { return DmeTime_t(1); }


	int GetTenthsOfMS() const { return m_tms; }
	float GetSeconds() const { return m_tms * 0.0001f; }
	void SetTenthsOfMS(int tms) { m_tms = tms; }
	void SetSeconds(float sec) { m_tms = RoundSecondsToTMS(sec); }


	void Clamp(DmeTime_t lo, DmeTime_t hi);
	bool IsInRange(DmeTime_t lo, DmeTime_t hi) const;


	friend float GetFractionOfTimeBetween(DmeTime_t t, DmeTime_t start, DmeTime_t end, bool bClamp);
	friend float GetFractionOfTime(DmeTime_t t, DmeTime_t duration, bool bClamp);
	friend int FrameForTime(DmeTime_t t, DmeFramerate_t framerate);


	friend DmeTime_t abs(DmeTime_t t) { return t.m_tms >= 0 ? t : -t; }


	int CurrentFrame(DmeFramerate_t framerate, RoundStyle_t roundStyle = ROUND_DOWN) const;
	DmeTime_t TimeAtCurrentFrame(DmeFramerate_t framerate, RoundStyle_t roundStyle = ROUND_DOWN) const;
	DmeTime_t TimeAtNextFrame(DmeFramerate_t framerate) const;
	DmeTime_t TimeAtPrevFrame(DmeFramerate_t framerate) const;

private:
	explicit DmeTime_t(int64 tms) : m_tms(int(tms)) {}

	static int RoundSecondsToTMS(float sec);
	static int RoundSecondsToTMS(double sec);

	int m_tms;
};

float GetFractionOfTimeBetween(DmeTime_t t, DmeTime_t start, DmeTime_t end, bool bClamp = false);
float GetFractionOfTime(DmeTime_t t, DmeTime_t duration, bool bClamp = false);

class CUtlBuffer;
bool Serialize(CUtlBuffer& buf, const DmeTime_t& src);
bool Unserialize(CUtlBuffer& buf, DmeTime_t& dest);


#endif  