#pragma once
/*******************************************************************************

Copyright (c) 2010, Perforce Software, Inc.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1.  Redistributions of source code must retain the above copyright
	notice, this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
	notice, this list of conditions and the following disclaimer in the
	documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*******************************************************************************/

/*******************************************************************************
 * Name		: P4BridgeServer.h
 *
 * Author	: dbb
 *
 * Description	:  P4BridgeServer
 *
 ******************************************************************************/

#include "P4BridgeClient.h"
#include "P4Connection.h"

#include "Lock.h"

#include <string>
#include <map>

using std::string;

class P4BridgeServer;

/*
	Parallel transfer object - the P4API manages the lifetime of this object, so it needs to live on its own
*/

class ParallelTransfer : public ClientTransfer, public p4base
{
public:
	ParallelTransfer(P4BridgeServer* pServer = NULL);
	virtual ~ParallelTransfer();

	void SetBridgeServer(P4BridgeServer* pServer);

	virtual int Transfer(ClientApi *, ClientUser *, const char *, StrArray &, StrDict &, int, Error *);

	virtual int Type(void) { return tParallelTransfer; }

protected:
	P4BridgeServer *pBridgeServer;
};

/*******************************************************************************
 *
 *  This is the function prototypes for the call backs used to log status, 
 *      error conditions, debugging messages, and the like to the client. It is
 *      up to the client to log the strings into a file if desired.
 *
 ******************************************************************************/

namespace std {
	class exception;
}

#ifndef STDCALL
#if defined (_MSC_VER)
#define STDCALL __stdcall
#else
#define STDCALL
#endif
#endif


typedef int STDCALL LogCallbackFn(int, const char*, int, const char*);


// Number returned by GetProtocol() if the server supports login, logout, etc.
#define SERVER_SECURITY_PROTOCOL	18

// Minimum server level supporting extended submit options
#define SERVER_EXTENDED_SUBMIT		22

/*******************************************************************************
 *
 *  P4BridgeServer
 *
 *  It provides the capability to connect to a P4 Server and handle callbacks and logging
 *
 ******************************************************************************/

class P4BridgeServer : public p4base
{
	friend class TestP4BridgeServer;
	friend class ParallelTransfer;

public:
 
	// logging support
	static int LogMessage(int log_level, const char * file, int line, const char * message, ...);
	static int LogMessageNoArgs(int log_level, const char * file, int line, const char * message);
	static void ReportException(std::exception& e, const char* fun);

protected:
	// Cannot create without initialization
	P4BridgeServer();

	// Get the protocol information from the server
	int GetServerProtocols(P4ClientError **err);

	// Call back function used to send text results back to the client
	//
	// The function prototype is:
	//
	// void TextCallbackFn(const char*);
	//
	// The first parameter is the text data. Multiple callbacks might be made 
	//      a single command and the text should be concatenated to obtain
	//      the entire results.

	TextCallbackFn* pTextResultsCallbackFn;

	// Call back function used to send informational messages back 
	//      to the client. This is generally the output from a command
	//      when not using tagged protocol.
	//
	// The function prototype is:
	//
	// void void IntTextCallbackFn(int, const char*);
	//
	// The first parameter is the message level. Generally, a message of a 
	//      higher level is a sub field of the preceding lower level message,
	//      i.e a level zero message may be followed by one or more level one
	//      messages containing details about the output. This is an older way
	//      of grouping output into logical objects that is superseded by
	//      tagged output.
	// The second parameter is the information

	IntIntIntTextCallbackFn* pInfoResultsCallbackFn;
		
	// Callback function used to send tagged output to the client. Tagged or
	// data is sent from the api as one or more StrDict objects representing
	// one or more data objects. Each dictionary represents one data objects.
	// I.e. for a list of file stats, each dictionary object will represent the
	// data for one file. The data is stored as Key:Value pairs, i.e 
	// Filename:"MyCode.cpp"
	//
	// The function prototype is:
	//
	//  void IntTextTextCallbackFn(int, const char*, const char*);
	//      
	// The first parameter is an object ID, a given command can return 
	//      multiple objects. ID's will start at 0 and increment for each
	//      successive object.
	// The second parameter is the text 'key'
	// The third parameter is the text value
	// 
	// The client will receive the call back multiple times, once for each
	// Key:Value pair in all of the dictionaries. For m data objects each
	// with n Key:Value pairs, the client will receive m * n call backs. The
	// object id can be used to group the data with there correct objects.
	//

	IntTextTextCallbackFn* pTaggedOutputCallbackFn;
	
	// Call back function used to send error messages back to the client
	//
	// The function prototype is:
	//
	// void IntTextCallbackFn( int, const char* );
	//
	// The first parameter is the error level, i.e Error or Warning
	// The second parameter is the error message

	IntIntIntTextCallbackFn* pErrorCallbackFn;
	
	// Call back function used to send text results back to the client
	//
	// The function prototype is:
	//
	// void BinaryCallbackFn(const void *, int);
	//
	// The first parameter is the binary data. Multiple callbacks might be made
	//      a single command and the results should be concatenated to obtain
	//      the entire results.
	// The second parameter is the size of the data in bytes.

	BinaryCallbackFn* pBinaryResultsCallbackFn;
	
	PromptCallbackFn * pPromptCallbackFn;
	ParallelTransferCallbackFn* pParallelTransferCallbackFn;

	ResolveCallbackFn * pResolveCallbackFn;
	ResolveACallbackFn * pResolveACallbackFn;

	int Resolve_int( int cmdId, P4ClientMerge *merger);
	int Resolve_int( int cmdId, P4ClientResolve *resolver, int preview, Error *e);
		
	ParallelTransfer *pTransfer;

	// pTransfer calls this
	int DoTransfer(
		ClientApi* client,
		ClientUser *ui,
		const char *cmd,
		StrArray &args,
		StrDict &pVars,
		int threads,
		Error *e);

	int DoTransferInternal(const char *cmd,
		std::vector<const char*> &argList,
		StrDictListIterator *varDictIterator,
		int threads,
		Error *e);


public:
	// Create a server object and connect to the server
	P4BridgeServer( const char *p4port, 
		const char *user, const char *pass,
		const char *ws_client );

	// Finalize the connection to the server and release resources
	virtual ~P4BridgeServer(void);
	
	// Type for p4base
	virtual int Type(void) { return tP4BridgeServer; }

	// TODO: is this even required?
	P4BridgeClient * get_ui();
	P4BridgeClient * get_ui(int id) {
		return get_ui();
	}
	P4BridgeClient * find_ui(int id) {
		return get_ui();
	}

	// server connection handling
	int connected( P4ClientError **err );
	int connected_int( P4ClientError **err );
	int connect_and_trust( P4ClientError **err, char* trust_flag, char* fingerprint );
	int connect_and_trust_int(P4ClientError **err, char* trust_flag, char* fingerprint );
	int close_connection( );
	int disconnect( void );

	// Does the server support Unicode for meta data?
	int unicodeServer( );

	// The APIlevel the connected sever supports
	int APILevel();

	// Does the connected sever require login?
	int UseLogin();
	
	//Does the connected sever support extended submit options (2006.2 higher)?
	int SupportsExtSubmit();

	// If the server supports Unicode, set the character set used to
	//  communicate with the server
	string set_charset( const char* c, const char * filec = NULL );

	// Get/Set the working directory
	void set_cwd( const char* newCwd );
	string get_cwd( void );

	void Run_int(P4Connection* client, const char *cmd, P4BridgeClient* ui);

	// The 800 pound gorilla in the room, execute a command
	int run_command( const char *cmd, int cmdId, int tagged, char const * const * args, int argc );

	int resolve( const char *file, int tagged );

	// Set the connection data used
	void set_connection(const char* newPort, const char* newUser, const char* newPassword, const char* newClient);
	void set_client( const char* newVal );
	void set_user( const char* newVal );
	void set_port( const char* newVal );
	void set_password( const char* newVal );
	void set_ticketFile( const char* newVal );
	void set_programName(const char* newVal);
	void set_programVer( const char* newVal );

	// Get the connection data used
	string get_client();
	string get_user();
	string get_port();
	string get_password();
	string get_ticketFile();
	string get_charset();
	string get_programName();
	string get_programVer();

	string get_config();
	static string get_config( const char* cwd );

	/* The BridgeServer provides a static Enviro */
	/* it is inherited by the P4Connection class */
	/* beware, the client has a default Enviro too, and it might not match! */

	static const char* Get( const char *var );
	static void Set( const char *var, const char *value );
	static void Update(const char *var, const char *value );
	static void Reload();
	static void ListEnviro();
	static void ListEnviro(Enviro* ptr);
	static Enviro *GetEnviro();

	// set things like "-vnet.maxwait=5"  here.
	// or just a number like "5"
	//  output will be on stdout
	static void SetDebugLevel(const char* lvl);

	// output like above but logged to a file
	static void SetDebugLevel(const char* lvl, const char* logFile);

	static LogCallbackFn *SetLogCallFn(LogCallbackFn *log_fn);

	void SetProtocol(const char *var, const char *value);

	void cancel_command(int cmdId);
	bool IsConnected();
		
	// If the P4 Server is Unicode enabled, the output will be in
	// UTF-8 or UTF-16 based on the char set specified by the client
	void UseUnicode(int val) { isUnicode = val; }

	// Put the calls to the callback in Structured Exception Handlers to catch
	//  any problems in the call like bad function pointers.
	void CallTextResultsCallbackFn( int cmdId, const char *data) ;
	void CallInfoResultsCallbackFn( int cmdId, int msgId, char level, const char *data );
	void CallTaggedOutputCallbackFn( int cmdId, int objId, const char *pKey, const char * pVal );
	void CallErrorCallbackFn( int cmdId, int severity, int errorId, const char * errMsg );
	void CallBinaryResultsCallbackFn( int cmdId, void * data, int length );

	// Set the call back function to receive the tagged output
	void SetTaggedOutputCallbackFn(IntTextTextCallbackFn* pNew);

	// Set the call back function to receive the error output
	void SetErrorCallbackFn(IntIntIntTextCallbackFn* pNew);

	void Prompt( int cmdId, const StrPtr &msg, StrBuf &rsp, 
				int noEcho, Error *e );

	void SetPromptCallbackFn( PromptCallbackFn * pNew);

	// Set the call back function to handle parallel transfer (see ClientTransfer)
	// TODO: it's not going to be a simple text callback
	void SetParallelTransferCallbackFn(ParallelTransferCallbackFn* pNew);

	// Set the call back function to receive the information output
	void SetInfoResultsCallbackFn(IntIntIntTextCallbackFn* pNew);

	// Set the call back function to receive the text output
	void SetTextResultsCallbackFn(TextCallbackFn* pNew);

	// Set the call back function to receive the binary output
	void SetBinaryResultsCallbackFn(BinaryCallbackFn* pNew);

	// Callbacks for handling progress
	void SetProgressCallbacks(const ProgressCallbackSet& callbacks);

	void SetProgressCallbacks(ProgressInitCallback* init,
                         ProgressDescriptionCallback* desc,
                         ProgressTotalCallback* total,
                         ProgressUpdateCallback* update,
                         ProgressDoneCallback* done);
	// Callbacks for handling interactive resolve
	int	Resolve( int cmdId, ClientMerge *m, Error *e );
	int	Resolve( int cmdId, ClientResolveA *r, int preview, Error *e );

	void SetResolveCallbackFn(ResolveCallbackFn * pNew);
	void SetResolveACallbackFn(ResolveACallbackFn * pNew);
	
	static int IsIgnored( const StrPtr &path );

	static string GetTicketFile( );
	static string GetTicket( char* port, char* user );

	friend class TestP4BridgeServer;
	friend class TestP4BridgeClient;

protected:
	// the one and only connection
	P4Connection* pConnection;
	unsigned long long runThreadId;

	string user;
	string password;
	string ticketFile;
	string client;
	string p4port;

	string pProgramName;
	string pProgramVer;
	CharSetApi::CharSet charset;
	CharSetApi::CharSet fileCharset;

	string pCwd;	// cache it for reconnects

	// UI support

	// If the P4 Server is Unicode enabled, the output will be in
	// UTF-8 or UTF-16 based on the char set specified by the client
	int isUnicode;

	bool initialized;

	void setInitialized(bool initialized);
	bool isInitialized() const;

	
	P4Connection* getConnection(int id = 99999999);


	static int IsIgnored_Int( const StrPtr &path );
	string get_config_Int( );
	static string get_config_Int( const char* cwd );
	static const char* Get_Int( const char *var );
	static void Set_Int( const char *var, const char *value );
	static void Update_Int( const char *var, const char *value );
	static void Reload_Int();
	// protocol key/values need to be set at the start of a connection, not after one has been established
	// store the values and use in our getConnection() handler
	std::map<string, string> extraProtocols;

	void SetProtocol_Int(const char *var, const char *value);

   // This is where the pointer to the log callback is stored if set by the user.
	static LogCallbackFn * pLogFn;

	// The APIlevel the connected sever supports
	int apiLevel;

	// Does the connected sever require login?
	int useLogin;

	//Does the connected sever support extended submit options (2006.2 higher)?
	int supportsExtSubmit;

	int connecting;

	int disposed;
};


/*******************************************************************************
 *
 * Macros for logging
 *
 ******************************************************************************/
#define LOG_FATAL(msg) P4BridgeServer::LogMessageNoArgs(0, __FILE__ , __LINE__, msg)
#define LOG_ERROR(msg) P4BridgeServer::LogMessageNoArgs(1, __FILE__ , __LINE__, msg)
#define LOG_WARNING(msg) P4BridgeServer::LogMessageNoArgs(2, __FILE__ , __LINE__, msg)
#define LOG_INFO(msg) P4BridgeServer::LogMessageNoArgs(3, __FILE__ , __LINE__, msg)
#define LOG_DEBUG(lvl,msg) P4BridgeServer::LogMessageNoArgs(lvl, __FILE__ , __LINE__, msg)

#define LOG_FATAL1(msg, a1) P4BridgeServer::LogMessage(0, __FILE__ , __LINE__, msg, a1)
#define LOG_ERROR1(msg, a1) P4BridgeServer::LogMessage(1, __FILE__ , __LINE__, msg, a1)
#define LOG_WARNING1(msg, a1) P4BridgeServer::LogMessage(2, __FILE__ , __LINE__, msg, a1)
#define LOG_INFO1(msg, a1) P4BridgeServer::LogMessage(3, __FILE__ , __LINE__, msg, a1)
#define LOG_DEBUG1(lvl,msg, a1) P4BridgeServer::LogMessage(lvl, __FILE__ , __LINE__, msg, a1)

#define LOG_FATAL2(msg, a1, a2) P4BridgeServer::LogMessage(0, __FILE__ , __LINE__, msg, a1, a2)
#define LOG_ERROR2(msg, a1, a2) P4BridgeServer::LogMessage(1, __FILE__ , __LINE__, msg, a1, a2)
#define LOG_WARNING2(msg, a1, a2) P4BridgeServer::LogMessage(2, __FILE__ , __LINE__, msg, a1, a2)
#define LOG_INFO2(msg, a1, a2) P4BridgeServer::LogMessage(3, __FILE__ , __LINE__, msg, a1, a2)
#define LOG_DEBUG2(lvl,msg, a1, a2) P4BridgeServer::LogMessage(lvl, __FILE__ , __LINE__, msg, a1, a2)
 
#define LOG_FATAL3(msg, a1, a2, a3) P4BridgeServer::LogMessage(0, __FILE__ , __LINE__, msg, a1, a2, a3)
#define LOG_ERROR3(msg, a1, a2, a3) P4BridgeServer::LogMessage(1, __FILE__ , __LINE__, msg, a1, a2, a3)
#define LOG_WARNING3(msg, a1, a2, a3) P4BridgeServer::LogMessage(2, __FILE__ , __LINE__, msg, a1, a2, a3)
#define LOG_INFO3(msg, a1, a2, a3) P4BridgeServer::LogMessage(3, __FILE__ , __LINE__, msg, a1, a2, a3)
#define LOG_DEBUG3(lvl,msg, a1, a2, a3) P4BridgeServer::LogMessage(lvl, __FILE__ , __LINE__, msg, a1, a2, a3)
#define LOG_DEBUG4(lvl,msg, a1, a2, a3, a4) P4BridgeServer::LogMessage(lvl, __FILE__ , __LINE__, msg, a1, a2, a3, a4)

#define LOG_ENTRY() P4BridgeServer::LogMessage(4, __FILE__, __LINE__, "Entry: %s", __FUNCTION__)
#define LOG_LOC()	P4BridgeServer::LogMessage(4, __FILE__, __LINE__, "Location: %s", __FUNCTION__)
