//========= Copyright Valve Corporation, All rights reserved. ============//
// vp4mutex.cpp : Defines the entry point for the console application.
//

#define Error DbgError

#include "tier0/platform.h"

#include <stdio.h>
#include <conio.h>
#include <vector> // SEE NOTES BELOW ABOUT REVERTING THIS IF REQUIRED

#undef Error
#undef Verify

#include "clientapi.h"

#include <time.h>
#include <ctype.h>
#include <windows.h>

#undef SetPort

#define RemoveAll clear
#define AddToTail push_back
#define Count	  size

#define CLIENTSPEC_BUFFER_SIZE		(8 * 1024)

//-----------------------------------------------------------------------------
// internal
//-----------------------------------------------------------------------------
ClientApi client;
ClientUser user;

//
// NOTE: All of this crap is here since we don't want to have the .exe depend on tier0 or vstdlib.dll.  If we change that, this can go away and std::vector can go back
//  to CUtlVector and CUtlSymbol can be used like it's supposed to be used....
//
//
//
//

static void Q_strncpy( char *pDest, char const *pSrc, int maxLen )
{
	strncpy( pDest, pSrc, maxLen );
	if ( maxLen > 0 )
	{
		pDest[maxLen-1] = 0;
	}
}

static int	Q_strlen( const char *str )
{
	return strlen( str );
}


#if defined( _WIN32 ) || defined( WIN32 )
#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/')
#else	//_WIN32
#define PATHSEPARATOR(c) ((c) == '/')
#endif	//_WIN32

static void Q_FileBase( const char *in, char *out, int maxlen )
{
	if ( !in || !in[ 0 ] )
	{
		*out = 0;
		return;
	}

	int len, start, end;

	len = Q_strlen( in );
	
	// scan backward for '.'
	end = len - 1;
	while ( end&& in[end] != '.' && !PATHSEPARATOR( in[end] ) )
	{
		end--;
	}
	
	if ( in[end] != '.' )		// no '.', copy to end
	{
		end = len-1;
	}
	else 
	{
		end--;					// Found ',', copy to left of '.'
	}

	// Scan backward for '/'
	start = len-1;
	while ( start >= 0 && !PATHSEPARATOR( in[start] ) )
	{
		start--;
	}

	if ( start < 0 || !PATHSEPARATOR( in[start] ) )
	{
		start = 0;
	}
	else 
	{
		start++;
	}

	// Length of new sting
	len = end - start + 1;

	int maxcopy = min( len + 1, maxlen );

	// Copy partial string
	Q_strncpy( out, &in[start], maxcopy );
}

#define COPY_ALL_CHARACTERS -1
static char *Q_strncat(char *pDest, const char *pSrc, size_t destBufferSize, int max_chars_to_copy )
{
	size_t charstocopy = (size_t)0;

	size_t len = strlen(pDest);
	size_t srclen = strlen( pSrc );
	if ( max_chars_to_copy <= COPY_ALL_CHARACTERS )
	{
		charstocopy = srclen;
	}
	else
	{
		charstocopy = (size_t)min( max_chars_to_copy, (int)srclen );
	}

	if ( len + charstocopy >= destBufferSize )
	{
		charstocopy = destBufferSize - len - 1;
	}

	if ( !charstocopy )
	{
		return pDest;
	}

	char *pOut = strncat( pDest, pSrc, charstocopy );
	pOut[destBufferSize-1] = 0;
	return pOut;
}

//-----------------------------------------------------------------------------
// Finds a string in another string with a case insensitive test
//-----------------------------------------------------------------------------
static char const* Q_stristr( char const* pStr, char const* pSearch )
{
	if (!pStr || !pSearch) 
		return 0;

	char const* pLetter = pStr;

	// Check the entire string
	while (*pLetter != 0)
	{
		// Skip over non-matches
		if (tolower((unsigned char)*pLetter) == tolower((unsigned char)*pSearch))
		{
			// Check for match
			char const* pMatch = pLetter + 1;
			char const* pTest = pSearch + 1;
			while (*pTest != 0)
			{
				// We've run off the end; don't bother.
				if (*pMatch == 0)
					return 0;

				if (tolower((unsigned char)*pMatch) != tolower((unsigned char)*pTest))
					break;

				++pMatch;
				++pTest;
			}

			// Found a match!
			if (*pTest == 0)
				return pLetter;
		}

		++pLetter;
	}

	return 0;
}

static int	Q_stricmp( const char *s1, const char *s2 )
{
	return stricmp( s1, s2 );
}

//-----------------------------------------------------------------------------
// Purpose: utility function to split a typical P4 line output into var and value
//-----------------------------------------------------------------------------
static void SplitP4Output(const_char *data, char *pszCmd, char *pszInfo, int bufLen)
{
	Q_strncpy(pszCmd, data, bufLen);
	
	char *mid = (char *)Q_stristr(pszCmd, " ");
	if (mid)
	{
		*mid = 0;
		Q_strncpy(pszInfo, data + (mid - pszCmd) + 1, bufLen);
	}
	else
	{
		pszInfo[0] = 0;
	}
}

static int Q_atoi (const char *str)
{
	int             val;
	int             sign;
	int             c;
	
	if (*str == '-')
	{
		sign = -1;
		str++;
	}
	else
		sign = 1;
		
	val = 0;

//
// check for hex
//
	if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
	{
		str += 2;
		while (1)
		{
			c = *str++;
			if (c >= '0' && c <= '9')
				val = (val<<4) + c - '0';
			else if (c >= 'a' && c <= 'f')
				val = (val<<4) + c - 'a' + 10;
			else if (c >= 'A' && c <= 'F')
				val = (val<<4) + c - 'A' + 10;
			else
				return val*sign;
		}
	}
	
//
// check for character
//
	if (str[0] == '\'')
	{
		return sign * str[1];
	}
	
//
// assume decimal
//
	while (1)
	{
		c = *str++;
		if (c <'0' || c > '9')
			return val*sign;
		val = val*10 + c - '0';
	}
	
	return 0;
}

static int Q_snprintf( char *pDest, int maxLen, char const *pFormat, ... )
{
	va_list marker;

	va_start( marker, pFormat );
#ifdef _WIN32
	int len = _vsnprintf( pDest, maxLen, pFormat, marker );
#elif _LINUX
	int len = vsnprintf( pDest, maxLen, pFormat, marker );
#else
	#error "define vsnprintf type."
#endif
	va_end( marker );

	// Len < 0 represents an overflow
	if( len < 0 )
	{
		len = maxLen;
		pDest[maxLen-1] = 0;
	}

	return len;
}

//-----------------------------------------------------------------------------
// Purpose: base class for parse input from the P4 server
//-----------------------------------------------------------------------------
template< class T >
class CDataRetrievalUser : public ClientUser
{
public:
	std::vector<T> &GetData()
	{
		return m_Data;
	}

	// call this to start retrieving data
	void InitRetrievingData()
	{
		m_bAwaitingNewRecord = true;
		m_Data.RemoveAll();
	}

	// implement this to parse out input from the server into the specified object
	virtual void OutputRecord(T &obj, const char *pszVar, const char *pszInfo) = 0;


private:
	bool m_bAwaitingNewRecord;
	std::vector<T> m_Data;


	virtual void OutputInfo(char level, const_char *data)
	{
		if (Q_strlen(data) < 1)
		{
			// end of a record, await the new one
			m_bAwaitingNewRecord = true;
			return;
		}

		if (m_bAwaitingNewRecord)
		{
			// add in the new record
			T newRec;
			m_Data.AddToTail( newRec );

			T &record = m_Data[ m_Data.Count() - 1 ];
			memset(&record, 0, sizeof(record));
			m_bAwaitingNewRecord = false;
		}

		// parse
		char szVar[_MAX_PATH];
		char szInfo[_MAX_PATH];
		SplitP4Output(data, szVar, szInfo, sizeof(szVar));

		// emit
		T &record = m_Data[m_Data.Count() - 1];
		OutputRecord(record, szVar, szInfo);
	}
};

class CP4Counter
{
public:
	CP4Counter() :
	  m_nValue( 0 )
	{
		m_szName[ 0 ] = 0;
	}

	char const *GetCounterName() const
	{
		return m_szName;
	}

	int GetValue() const
	{
		return m_nValue;
	}

	void	SetName( char const *name )
	{
		Q_strncpy( m_szName, name, sizeof( m_szName ) );
	}

	void	SetValue( int value )
	{
		m_nValue = value;
	}
private:

	char		m_szName[ 128 ];
	int			m_nValue;
};

//-----------------------------------------------------------------------------
// Purpose: Retrieves a file list
//-----------------------------------------------------------------------------
class CCountersUser : public CDataRetrievalUser<CP4Counter>
{
public:
	void RetrieveCounters()
	{
		// clear the list
		InitRetrievingData();

		client.Run("counters", this);
	}

private:
	virtual void OutputRecord(CP4Counter &counter, const char *szCmd, const char *szInfo)
	{
		if ( !Q_stricmp( szCmd, "counter" ) )
		{
            counter.SetName( szInfo );
		}
		else if ( !Q_stricmp( szCmd, "value" ) )
		{
			counter.SetValue( Q_atoi( szInfo ) );
		}
	}
};

//-----------------------------------------------------------------------------
// Purpose: Retrieves a file list
//-----------------------------------------------------------------------------
class CSetCounterUser : public ClientUser
{
public:
	void SetCounter( char *countername, int value )
	{
		char valuestr[ 32 ];
		Q_snprintf( valuestr, sizeof( valuestr ), "%d", value );
		char *argv[] = { countername, valuestr, NULL };
		client.SetArgv( 2, argv );
		client.Run("counter", this);
	}

	virtual void 	HandleError( Error *err )
	{
	}
	virtual void 	Message( Error *err )
	{
	}
	virtual void 	OutputError( const_char *errBuf )
	{
	}
	virtual void	OutputInfo( char level, const_char *data )
	{
	}
	virtual void 	OutputBinary( const_char *data, int length )
	{
	}
	virtual void 	OutputText( const_char *data, int length )
	{
	}
};

static CSetCounterUser g_SetCounterUser;
static CCountersUser g_CountersUser;

typedef enum
{
	MUTEX_QUERY = 0,
	MUTEX_LOCK,
	MUTEX_RELEASE,
} MUTEXACTION;

static void printusage( char const *basefile )
{
	printf( "usage:  %s \n\
\t< query | release | lock > <branchname> <sleepseconds> <clientname> [<ip:port>]\n\
\te.g.:\n\
\t%s query src_main 3 yahn\n\
\t%s query\n\
", basefile, basefile, basefile );
}

struct CUtlSymbol
{
public:
	CUtlSymbol()
	{
		m_szValue[ 0 ] = 0;
	}

	CUtlSymbol& operator =( const char * lhs )
	{
		Q_strncpy( m_szValue, lhs, sizeof( m_szValue ) );
		return *this;
	}

	char const *String()
	{
		return m_szValue;
	}
private:
	char m_szValue[ 256 ];
};

int FindLockUsers( CCountersUser& counters, char const *branchspec, std::vector< CUtlSymbol >& users, std::vector< int >& locktimes, std::vector< CUtlSymbol >* branchnames = NULL )
{
	users.RemoveAll();

	char lockstr[ 256 ];
	if ( branchspec != NULL )
	{
		Q_snprintf( lockstr, sizeof( lockstr ), "%s_lock_", branchspec );
	}
	else
	{
		Q_snprintf( lockstr, sizeof( lockstr ), "_lock_", branchspec );
	}

	counters.RetrieveCounters();
	std::vector< CP4Counter >& list = counters.GetData();
	int count = list.Count();
	for ( int i = 0; i < count; ++i )
	{
		char const *name = list[ i ].GetCounterName(); 
		int value = list[ i ].GetValue();

		char const *p = Q_stristr( name, lockstr );
		if ( !p )
			continue;

		if ( value != 0 )
		{
			CUtlSymbol sym;
			sym = p + Q_strlen( lockstr );

			users.AddToTail( sym );
			locktimes.AddToTail( value );

			if ( branchnames )
			{
				char branchname[ 512 ];
				Q_strncpy( branchname, name, p - name + 1 );
				CUtlSymbol sym;
				sym = branchname;
				branchnames->AddToTail( sym );
			}
		}
	}

	return users.Count();
}

static void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen )
{
	int nMinutes = nInputSeconds / 60;
	int nSeconds = nInputSeconds - nMinutes * 60;
	int nHours = nMinutes / 60;
	nMinutes -= nHours * 60;

	char *extra[2] = { "", "s" };
	
	if ( nHours > 0 )
		Q_snprintf( pOut, outLen, "%d hour%s, %d minute%s, %d second%s", nHours, extra[nHours != 1], nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] );
	else if ( nMinutes > 0 )
		Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] );
	else
		Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[nSeconds != 1] );
}

static void ComputeHoldTime( int holdtime, char *buf, size_t bufsize )
{
	buf[ 0 ] = 0;

	if ( holdtime < 100 )
	{
		Q_snprintf( buf, bufsize, "UNKNOWN" );
	}
	else
	{
		// Prepend the time.
		time_t aclock = (time_t)holdtime;
		struct tm *newtime = localtime( &aclock );
		
		// Get rid of the \n.
		Q_strncpy( buf, asctime( newtime ), bufsize );
		char *pEnd = (char *)Q_stristr( buf, "\n" );
		if ( pEnd )
		{
			*pEnd = 0;
		}

		time_t curtime;
		time( &curtime );

		int holdSeconds = curtime - holdtime;
		if ( holdSeconds > 0 )
		{
			char durstring[ 256 ];
			durstring[ 0 ] = 0;
			GetHourMinuteSecondsString( holdSeconds, durstring, sizeof( durstring ) );

			Q_strncat( buf, ", held for ", bufsize, COPY_ALL_CHARACTERS );
			Q_strncat( buf, durstring, bufsize, COPY_ALL_CHARACTERS );
		}
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	char basefile[ 256 ];
	Q_FileBase( argv[ 0 ], basefile, sizeof( basefile ) );

	bool validAction = false;
	MUTEXACTION action = MUTEX_QUERY;
	bool validBranch = false;
	CUtlSymbol branchspec;
	bool validSleepSeconds = false;
	int sleepSeconds = 1;
	bool validClient = false;
	CUtlSymbol clientname;
	bool validIP = false;
	CUtlSymbol ipport;

	for ( int i = 1; i < argc; ++i )
	{
		switch ( i )
		{
		default:
			break;
		case 1:
			validAction = true;
			if ( !Q_stricmp( argv[ i ], "query" ) )
			{
				action = MUTEX_QUERY;
			}
			else if ( !Q_stricmp( argv[ i ], "release" ) )
			{
				action = MUTEX_RELEASE;
			}
			else if ( !Q_stricmp( argv[ i ], "lock" ) )
			{
				action = MUTEX_LOCK;
			}
			else 
			{
				validAction = false;
			}
			break;
		case 2:
			{
				validBranch = true;
				branchspec = argv[ i ];
			}
			break;
		case 3:
			{
				validSleepSeconds = true;
				sleepSeconds = clamp( Q_atoi( argv[ i ] ), 0, 100 );
			}
			break;
		case 4:
			{
				validClient = true;
				clientname = argv[ i ];
			}
			break;
		case 5:
			{
				validIP = true;
				ipport = argv[ i ];
			}
			break;
		}
	}

	bool describeLocksOnly = false;
	if ( !validBranch ||
		 !validSleepSeconds ||
		 !validClient ||
		 !validIP )
	{
		if ( !validAction || action != MUTEX_QUERY )
		{
			printusage( basefile );
			return -1;
		}

		describeLocksOnly = true;
		sleepSeconds = 5;
	}

	// set the protocol return all data as key/value pairs
	client.SetProtocol( "tag", "" );

	// connect to the p4 server
	Error e;
	if ( ipport.String()[ 0 ] )
	{
		client.SetPort( ipport.String() );
	}
	if ( clientname.String()[ 0 ] )
	{
		client.SetUser( clientname.String() );
	}

	client.Init( &e );
	bool connected = ( e.Test() == 0 ) ? true : false;
	if ( !connected )
	{
		printf( "Unable to connect to perforce server\n" );
		return -1;
	}

	if ( describeLocksOnly )
	{
		std::vector< CUtlSymbol > users;
		std::vector< int > locktimes;
		std::vector< CUtlSymbol > branchnames;
		int holdCount = FindLockUsers( g_CountersUser, NULL, users, locktimes, &branchnames );

		for ( int i = 0; i < holdCount; ++i )
		{
			char timestr[ 128 ];
			ComputeHoldTime( locktimes[ i ], timestr, sizeof( timestr ) );
			printf( "'%s' HELD by:  %s\n\ttime: %s\n", branchnames[ i ].String(), users[ i ].String(), timestr );
		}
	}
	else
	{

		std::vector< CUtlSymbol > users;
		std::vector< int > locktimes;
		int holdCount = FindLockUsers( g_CountersUser, branchspec.String(), users, locktimes );

		if ( holdCount >= 2 )
		{
			char userlist[ 1024 ];
			userlist[ 0 ] = 0;
			for ( int i = 0; i < (int)users.Count(); ++i )
			{
				Q_strncat( userlist, users[ i ].String(), sizeof( userlist ), COPY_ALL_CHARACTERS );
				if ( i != users.Count() - 1 )
				{
					Q_strncat( userlist, ", ", sizeof( userlist ), COPY_ALL_CHARACTERS );
				}
			}
			printf( "%s:  ERROR, multiple users (%s) holding lock on '%s'\n", basefile, userlist, branchspec.String() );
			printusage( basefile );
			return -1;
		}

		char setcountername[ 256 ];
		Q_snprintf( setcountername, sizeof( setcountername ), "%s_lock_%s", branchspec.String(), clientname.String() );

		switch ( action )
		{
		default:
			break;
		case MUTEX_QUERY:
			{
				if ( holdCount == 1 )
				{
					bool isHeldByLocal = false;

					if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) )
					{
						isHeldByLocal = true;
					}

					char timestr[ 128 ];
					ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) );
					printf( "%s: '%s' lock on %s is HELD by:  %s\nHOLD INFO:  %s\n", basefile, branchspec.String(), ipport.String(), users[ 0 ].String(), timestr );
				}
				else if ( holdCount == 0 )
				{
					printf( "%s: '%s' lock on %s is FREE\n", basefile, branchspec.String(), ipport.String() );
				}
			}
			break;
		case MUTEX_LOCK:
			{
				printf( "%s: Attempting to lock the '%s' codeline for %s on %s\n\n", basefile, branchspec.String(), clientname.String(), ipport.String() );

				// p4mutex: Attempting to lock the 'main_src' codeline for yahn on 207.173.178.12:1666.

				if ( holdCount == 1 )
				{
					bool isHeldByLocal = false;

					if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) )
					{
						isHeldByLocal = true;
					}

					char timestr[ 128 ];
					ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) );

					if ( isHeldByLocal )
					{
						// Success: You already have the 'main_src' codeline lock.
							printf( "Success: You already have the '%s' codeline lock\nInfo:  %s\n", branchspec.String(), timestr );
					}
					else
					{
						// Failed: 'main_goldsrc' lock currently owned by alfred
						printf( "Failed: '%s' lock currently owned by %s\nInfo:  %s\n", branchspec.String(), users[ 0 ].String(), timestr );
					}
				}
				else if ( holdCount == 0 )
				{
					// Success: 'main_src' codeline lock granted to yahn.
					// Set the counter
					time_t aclock;
					time( &aclock );
					g_SetCounterUser.SetCounter( setcountername, (int)aclock );

					printf( "Success: '%s' codeline lock granted to %s\n", branchspec.String(), clientname.String() );
				}
			}
			break;
		case MUTEX_RELEASE:
			{
				printf( "%s: Attempting to release the '%s' codeline for %s on %s\n\n", basefile, branchspec.String(), clientname.String(), ipport.String() );

				// p4mutex: Attempting to release the 'main_src' codeline for yahn on 207.173.178.12:1666.

				if ( holdCount == 1 )
				{
					bool isHeldByLocal = false;

					if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) )
					{
						isHeldByLocal = true;
					}

					char timestr[ 128 ];
					ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) );

					if ( isHeldByLocal )
					{
						// Success: 'main_src' codeline lock released.
						
						// Set the counter
						g_SetCounterUser.SetCounter( setcountername, 0 );

						printf( "Success: '%s' codeline lock released.\n", branchspec.String() );
					}
					else
					{
						// Failed: 'main_goldsrc' lock currently owned by alfred
						printf( "Failed: '%s' lock currently owned by %s\nInfo:  %s\n", branchspec.String(), users[ 0 ].String(), timestr );
					}
				}
				else if ( holdCount == 0 )
				{
					// Success: The 'main_src' codeline lock is already free.
					
					printf( "Success: The '%s' codeline lock is already free\n", branchspec.String() );
				}
			}
			break;
		}
	}

	if ( sleepSeconds > 0 )
	{
		time_t starttime;
		time( &starttime );

		int elapsed = 0;

		do 
		{
			time_t curtime;
			time( &curtime );
			elapsed = curtime - starttime;

			Sleep( 50 );

		} while ( elapsed < sleepSeconds && !kbhit() );
	}

	return 0;
}