//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================

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

#include "tier0/memdbgon.h"
#include "tier1/functors.h"
#include "workthreadpool.h"

namespace GCSDK
{
class CJobMgr;
class CLock;
class CJob;
class IMsgNetPacket;

//-----------------------------------------------------------------------------
// Purpose: Use these macros to declare blocks where it is unsafe to yield.
//	The job will assert if it pauses within the block
//-----------------------------------------------------------------------------
#define DO_NOT_YIELD_THIS_SCOPE()	GCSDK::CDoNotYieldScope doNotYieldScope_##line( FILE_AND_LINE )
#define BEGIN_DO_NOT_YIELD()		GJobCur().PushDoNotYield( FILE_AND_LINE )
#define END_DO_NOT_YIELD()			GJobCur().PopDoNotYield()

class CDoNotYieldScope
{
public:
	CDoNotYieldScope( const char *pchLocation );
	~CDoNotYieldScope();
private:
	// Disallow these constructors and operators
	CDoNotYieldScope();
	CDoNotYieldScope( const CDoNotYieldScope &that );
	CDoNotYieldScope &operator=( const CDoNotYieldScope &that );
};

//-----------------------------------------------------------------------------

// job creation function
typedef CJob *(*JobCreationFunc_t)( void *pvServerParent, void * pvStartParam );


//-----------------------------------------------------------------------------
// Purpose: static job information
//                      contains information relevant to one type of CJob
//-----------------------------------------------------------------------------
struct JobType_t
{
        const char *m_pchName;               // name of this type of job
        MsgType_t m_eCreationMsg;                     // network message that creates this job
		EServerType m_eServerType;			// the server type that responds to this message
        JobCreationFunc_t m_pJobFactory; // virtual constructor
};


//-----------------------------------------------------------------------------
// Purpose: reason as to why the current job has yielded to the main thread (paused)
//			if this is updated, k_prgchJobPauseReason[] in job.cpp also needs to be updated
//-----------------------------------------------------------------------------
enum EJobPauseReason
{
	k_EJobPauseReasonNone,
	k_EJobPauseReasonNotStarted,
	k_EJobPauseReasonNetworkMsg,
	k_EJobPauseReasonSleepForTime,
	k_EJobPauseReasonWaitingForLock,
	k_EJobPauseReasonYield,
	k_EJobPauseReasonSQL,
	k_EJobPauseReasonWorkItem,

	k_EJobPauseReasonCount
};


//-----------------------------------------------------------------------------
// Purpose: contains information used to route a message to a job, or to
//			create a new job from that message type
//-----------------------------------------------------------------------------
struct JobMsgInfo_t
{
	MsgType_t m_eMsg;
	JobID_t m_JobIDSource;
	JobID_t m_JobIDTarget;
	EServerType m_eServerType; 

	JobMsgInfo_t()
	{
		m_eMsg = (MsgType_t)0;
		m_JobIDSource = k_GIDNil;
		m_JobIDTarget = k_GIDNil;
		m_eServerType = k_EServerTypeInvalid;
	}

	JobMsgInfo_t( MsgType_t eMsg, JobID_t jobIDSource, JobID_t jobIDTarget, EServerType eServerType )
	{
		m_eMsg = eMsg;
		m_JobIDSource = jobIDSource;
		m_JobIDTarget = jobIDTarget;
		m_eServerType = eServerType;
	}
};

typedef void (CJob::*JobThreadFunc_t)();

#define BYieldingAcquireLock( lock ) _BYieldingAcquireLock( lock, __FILE__, __LINE__ ) 
#define BAcquireLockImmediate( lock ) _BAcquireLockImmediate( lock, __FILE__, __LINE__ )
#define ReleaseLock( lock ) _ReleaseLock( lock, false, __FILE__, __LINE__ )

//-----------------------------------------------------------------------------
// Purpose: A job is any server operation that requires state.  Typically, we use jobs for
//			operations that need to pause waiting for responses from other servers.  The
//			job object persists the state of the operation while it waits, and the reply
//			from the remote server re-activates the job.
//-----------------------------------------------------------------------------
class CJob
{
public:
	// Constructors & destructors, when overriding job name a static string pointer must be used
	explicit CJob( CJobMgr &jobMgr, char const *pchJobName = NULL );
	virtual ~CJob();

	// starts the job, storing off the network msg and calling it's Run() function
	void StartJobFromNetworkMsg( IMsgNetPacket *pNetPacket, const JobID_t &gidJobIDSrc );

	// accessors
	JobID_t GetJobID() const { return m_JobID; }

	// start job immediately
	// mostly for CMD jobs, which should immediately Yield() once
	// so that they don't do their work until the enclosing JobMbr.Run() 
	// is called
	void StartJob( void * pvStartParam );
	// schedules the job for execution, but does not interrup the currently running job. Effectively starts the job on the yielding list as if it had immediately yielded
	// although is more efficient than actually doing so
	void StartJobDelayed( void * pvStartParam );

	// string name of the job
	const char *GetName() const;
	// return reason why we're paused
	EJobPauseReason GetPauseReason() const		{ return m_ePauseReason; }
	// string description of why we're paused
	const char *GetPauseReasonDescription() const;
	// return time at which this job was last paused or continued
	const CJobTime& GetTimeSwitched() const		{ return m_STimeSwitched; }
	// return microseconds run since we were last continued
	uint64 GetMicrosecondsRun() const			{ return m_FastTimerDelta.GetDurationInProgress().GetUlMicroseconds(); }
	bool BJobNeedsToHeartbeat() const			{ return ( m_STimeNextHeartbeat.CServerMicroSecsPassed() >= 0 ); }

	// --- locking pointers
	bool _BYieldingAcquireLock( CLock *pLock, const char *filename = "unknown", int line = 0 );
	bool _BAcquireLockImmediate( CLock *pLock, const char *filename = "unknown", int line = 0 );
	void _ReleaseLock( CLock *pLock, bool bForce = false, const char *filename = "unknown", int line = 0 );
	bool BHoldsAnyLocks() const { return m_vecLocks.Count() > 0; }
	void ReleaseLocks();

	/// If we hold any locks, spew about them and release them.
	/// This is useful for long running jobs, to make sure they don't leak
	/// locks that never get cleaned up
	void ShouldNotHoldAnyLocks();

	// --- general methods for waiting for events
	// Simple yield to other jobs until Run() called again
	bool BYield();
	// Yield IF JobMgr thinks we need to based on how long we've run and our priority
	bool BYieldIfNeeded( bool *pbYielded = NULL );
	// waits for a set amount of time
	bool BYieldingWaitTime( uint32 m_cMicrosecondsToSleep );
	bool BYieldingWaitOneFrame();
	// waits for another network msg, returning false if none returns
	bool BYieldingWaitForMsg( IMsgNetPacket **ppNetPacket );
	bool BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg );
	bool BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg );

#ifdef GC
	bool BYieldingWaitForMsg( CGCMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID );
	bool BYieldingWaitForMsg( CProtoBufMsgBase *pMsg, MsgType_t eMsg, const CSteamID &expectedID );
	bool BYieldingRunQuery( CGCSQLQueryGroup *pQueryGroup, ESchemaCatalog eSchemaCatalog );
#endif

	bool BYieldingWaitTimeWithLimit( uint32 cMicrosecondsToSleep, CJobTime &stimeStarted, int64 nMicroSecLimit );
	bool BYieldingWaitTimeWithLimitRealTime( uint32 cMicrosecondsToSleep, int nSecLimit );

	void RecordWaitTimeout() { m_flags.m_bits.m_bWaitTimeout = true; }

	// wait for pending work items before deleting job
	void WaitForThreadFuncWorkItemBlocking();

	// waits for a work item completion callback
	// You can pass a string that describes what sort of work item you are waiting on.
	// WARNING: This function saves the pointer to the string, it doesn't copy the string
	bool BYieldingWaitForWorkItem( const char *pszWorkItemName = NULL );

	// adds this work item to threaded work pool and waits for it
	bool BYieldingWaitForThreadFuncWorkItem( CWorkItem * );

	// calls a local function in a thread, and yields until it's done
	bool BYieldingWaitForThreadFunc( CFunctor *jobFunctor );

	// Used by the DO_NOT_YIELD() macros
	int32 GetDoNotYieldDepth() const;
	void PushDoNotYield( const char *pchFileAndLine );
	void PopDoNotYield();

#ifdef DBGFLAG_VALIDATE
	virtual void Validate( CValidator &validator, const char *pchName );		// Validate our internal structures
	static void ValidateStatics( CValidator &validator, const char *pchName );
#endif

	// creates a job
	template <typename JOB_TYPE, typename PARAM_TYPE>
	static JOB_TYPE *AllocateJob( PARAM_TYPE *pParam )
	{
		return new JOB_TYPE( pParam );
	}
	// delete a job (the job knows what allocator to use)
	static void DeleteJob( CJob *pJob );

	void SetStartParam( void * pvStartParam )		{ Assert( NULL == m_pvStartParam ); m_pvStartParam = pvStartParam; }
	void SetFromFromMsg( bool bRunFromMsg )			{ m_bRunFromMsg = true; }

	void AddPacketToList( IMsgNetPacket *pNetPacket, const JobID_t gidJobIDSrc );
	// marks a packet as being finished with, releases the packet and frees the memory
	void ReleaseNetPacket( IMsgNetPacket *pNetPacket );

	void EndPause( EJobPauseReason eExpectedState );

	// Generate an assertion in the coroutine of this job
	// (creating a minidump).  Useful for inspecting stuck jobs
	void GenerateAssert( const char *pchMsg = NULL );

	/// Return true if we tried to waited on a message of this type,
	/// and failed to receive it.  (If so, then this means we could
	/// conceivably still receive a reply of that type at any moment.)
	bool BHasFailedToReceivedMsgType( MsgType_t m ) const;

	/// Mark that we awaited a message of the specified type, but timed out
	void MarkFailedToReceivedMsgType( MsgType_t m );

	/// Clear flag that we timed out waiting on a message of the specified type.
	/// This is used to allow you to wait ont he same message again, even if
	/// you know the reply might be a late reply.  It's up to you to deal with
	/// mismatched replies!
	void ClearFailedToReceivedMsgType( MsgType_t m );

protected:
	// main job implementation, in the coroutine.  Every job must implement at least one of these methods.
	virtual bool BYieldingRunJob( void * pvStartParam )				{ Assert( false ); return true; }	// implement this if your job can be started directly
	virtual bool BYieldingRunJobFromMsg( IMsgNetPacket * pNetPacket )	{ Assert( false ); return true; }	// implement this if your job can be started by a network message

	// Can be overridden to return a different timeout per job class
	virtual uint32 CHeartbeatsBeforeTimeout();

	// Called by CJobMgr to send heartbeat message to our listeners during long operations
	void Heartbeat();


	// accessor to get access to the JobMgr from the server we belong to
	CJobMgr &GetJobMgr();
	uint32 m_bRunFromMsg:1,
			m_bWorkItemCanceled:1,			// true if the work item we were waiting on was canceled
			m_bIsTest:1,
			m_bIsLongRunning:1;

private:
	// starts the coroutine that activates the job
	void InitCoroutine();

	// continues the current job
	void Continue();

	// break into this coroutine - can only be called from OUTSIDE this coroutine
	void Debug();

	// pauses the current job - can only be called from inside a coroutine
	void Pause( EJobPauseReason eReason );

	static void BRunProxy( void *pvThis );

	JobID_t m_JobID;					// Our unique identifier.
	HCoroutine m_hCoroutine;
	void * m_pvStartParam;				// Start params for our job, if any
	// all these flags indicate some kind of failure and we will want to report them
	union  {
		struct {
			uint32
				m_bJobFailed:1,					// true if BYieldingRunJob returned false
				m_bLocksFailed:1,
				m_bLocksLongHeld:1,
				m_bLocksLongWait:1,
				m_bWaitTimeout:1,
				m_bLongInterYield:1,
				m_bTimeoutNetMsg:1,
				m_bTimeoutOther:1,
				m_uUnused:24;
			} m_bits;
		uint32 m_uFlags;
		} m_flags;
	int m_cLocksAttempted;
	int m_cLocksWaitedFor;
	EJobPauseReason m_ePauseReason;
	MsgType_t	m_unWaitMsgType;
	CJobTime m_STimeStarted;				// time (frame) at which this job started
	CJobTime m_STimeSwitched;				// time (frame) at which we were last paused or continued
	CJobTime m_STimeNextHeartbeat;		// Time at which next heartbeat should be performed
	CFastTimer m_FastTimerDelta;		// How much time we've been running for without yielding
	CCycleCount m_cyclecountTotal;		// Total runtime
	CJob *m_pJobPrev;					// the job that launched us

	// lock manipulation
	void _SetLock( CLock *pLock, const char *filename, int line );
	void UnsetLock( CLock *pLock );
	void PassLockToJob( CJob *pNewJob, CLock *pLock );
	void OnLockDeleted( CLock *pLock );
	void AddJobToNotifyOnLockRelease( CJob *pJob );
	CUtlVectorFixedGrowable< CLock *, 2 > m_vecLocks;
	CLock *m_pWaitingOnLock;			// lock we're waiting on, if any
	const char *m_pWaitingOnLockFilename;
	int m_waitingOnLockLine;
	CJob *m_pJobToNotifyOnLockRelease;	// other job that wants this lock
	CWorkItem *m_pWaitingOnWorkItem;	// set if job is waiting for this work item

	CJobMgr &m_JobMgr;					// our job manager
	CUtlVectorFixedGrowable< IMsgNetPacket *, 1 > m_vecNetPackets;			// list of tcp packets currently held by this job (ie, needing release on job exit)

	/// List of message types that we have waited for, but timed out.
	/// once this happens, we currently do not have a good mechanism
	/// to recover gracefully.  But we at least can detect the situation
	/// and avoid getting totally hosed or processing the wrong reply
	CUtlVector<MsgType_t>	m_vecMsgTypesFailedToReceive;

	// pointer to our own static job info
	struct JobType_t const *m_pJobType;

	// Name of the job for when it's not registered
	const char *m_pchJobName;

	// A stack of do not yield guards so we can print the right warning if they're nested
	CUtlLinkedList<const char *> m_stackDoNotYieldGuards;

	// setting the job info
	friend void Job_SetJobType( CJob &job, const JobType_t *pJobType );
	friend class CJobMgr;
	friend class CLock;

	// used to store the memory allocation stack
	CUtlMemory< unsigned char > m_memAllocStack;
};


// Only one job can be running at a time.  We keep a global accessor to it.
extern CJob *g_pJobCur;
inline CJob &GJobCur() { Assert( g_pJobCur != NULL ); return *g_pJobCur; }

#define AssertRunningJob() { Assert( NULL != g_pJobCur ); }
#define AssertRunningThisJob() { Assert( this == g_pJobCur ); }
#define AssertNotRunningThisJob() { Assert( this != g_pJobCur ); }
#define AssertNotRunningJob() { Assert( NULL == g_pJobCur ); }


//-----------------------------------------------------------------------------
// Purpose: simple locking class
//			add this object to any classes you want jobs to be able to lock
//-----------------------------------------------------------------------------
class CLock
{
public:
	CLock( );
	~CLock();
	
	bool BIsLocked()		{ return m_pJob != NULL; }
	CJob *GetJobLocking()	{ return m_pJob; }
	CJob *GetJobWaitingQueueHead()	{ return m_pJobToNotifyOnLockRelease; }
	CJob *GetJobWaitingQueueTail()	{ return m_pJobWaitingQueueTail; }
	void AddToWaitingQueue( CJob *pJob );
	const char *GetName() const;
	void SetName( const char *pchName );
	void SetName( const char *pchPrefix, uint64 ulID );
	void SetName( const CSteamID &steamID );
	int16 GetLockType() const { return m_nsLockType; }
	void SetLockType( int16 nsLockType ) { m_nsLockType = nsLockType; }
	uint64 GetLockSubType() const { return m_unLockSubType; }
	void SetLockSubType( uint64 unLockSubType ) { m_unLockSubType = unLockSubType; }
	int32 GetWaitingCount() const { return m_nWaitingCount; }
	int64 GetMicroSecondsSinceLock() const { return m_sTimeAcquired.CServerMicroSecsPassed(); }
	void IncrementReference();
	int DecrementReference();
	void ClearReference() { m_nRefCount = 0; }
	int32 GetReferenceCount() const { return m_nRefCount; }

	void Dump( const char *pszPrefix = "\t\t", int nPrintMax = 1, bool bPrintWaiting = true ) const;

private:
	enum ENameType
	{
		k_ENameTypeNone = 0,
		k_ENameTypeSteamID = 1,
		k_ENameTypeConstStr = 2,
		k_ENameTypeConcat = 3,
	};

	CJob *m_pJob;						// the job that's currently locking us
	CJob *m_pJobToNotifyOnLockRelease;	// Pointer to the first job waiting on us
	CJob *m_pJobWaitingQueueTail;		// Pointer to the last job waiting on us
	int16 m_nsLockType;					// Lock priority for safely waiting on multiple locks
	int16 m_nsNameType;					// Enum that controls how this lock is named

	uint64 m_ulID;						// ID part of the lock's name
	const char *m_pchConstStr;			// Prefix part of the lock's name
	mutable CUtlString m_strName;		// Cached name

	int32 m_nRefCount;					// # of times locked
	int32 m_nWaitingCount;				// Count of jobs waiting on the lock
	CJobTime m_sTimeAcquired;			// Time the lock was last locked
	uint64 m_unLockSubType;

	const char *m_pFilename;			// Filename of the source file who aquired this lock
	int m_line;							// Line number of the filename

	friend class CJob;
};

//-----------------------------------------------------------------------------
// Purpose: automatic locking class
//-----------------------------------------------------------------------------

class CAutoCLock
{
public:
	CAutoCLock( CLock &refLock )
		: m_pLock ( &refLock)
	{
		DbgVerify( GJobCur().BYieldingAcquireLock( m_pLock ) );
	}

	// explicitly unlock before destruction
	void Unlock()
	{
		if ( m_pLock != NULL )
			GJobCur().ReleaseLock( m_pLock );
		m_pLock = NULL;
	}

	~CAutoCLock( )
	{
		Unlock();
	}

private:
	CLock *m_pLock;
	CJob *m_pJob;
};

} // namespace GCSDK

#include "tier0/memdbgoff.h"

#endif // GC_JOB_H