#include "StdAfx.h"
#include "rpcserver.h"
#include "clientcontext.h"
#include "bridgeserver_h.h"
#include "ATLComTime.h"
#include "HouseKeeper.h"

/// RPC Server Implementation
//extern "C" {

double GetServerTime()
{
	SYSTEMTIME st;
	GetLocalTime(&st);
	COleDateTime now(st);
	return now.m_dt;
}


int LogOn(/*[out]*/ CLIENTCONTEXT_PTR context_ptr, /*[in,string]*/ wchar_t * username, /*[in,string]*/ wchar_t * password, /*[in,string]*/ wchar_t * machinename, wchar_t * port)
{
	print(L"%s: LogOn\n",username);

	int userId;
	db.GetUserId(username,userId);

	// authenticate
	if(userId==NullUserId){
		print(L"	Error: %s doesn't exist in the user database.\n",username);
		return bsUsernameMismatch;
	}

	int authenticated = 0;
	db.Authenticate(userId,password,authenticated);

	if(authenticated){
		// detect if user is already logged on
		ClientContext * context = ClientContext::Find(userId);
		if(context){
			// someone with this username is already logged on
			print(L"  Warning: %s is already logged on, re-logging on.\n",username);
			ServerNotify(context,bcUser,userId,bcLogOff,0);
			Message msg;
			msg.idType = bcSystem;
			msg.reason = bcLoggedOnElsewhere;
			msg.type = Message::Notify;
			context->PostMessageAndWait(msg,10000);
			ClientContext::Delete(context);
			db.SetUserLogin(userId,0);
		}
	} else {
		print(L"   Error: password does not match.\n");
		*context_ptr = NULL;
		return bsPasswordMismatch;
	} 

	{
		// create a context (which will also spawn a thread dedicated to this client)
		ClientContext * context = ClientContext::Create(userId, username, machinename, port);
		// place it in the handle
		*context_ptr = context;
		db.SetUserLogin(userId,1);
		db.SetUserAway(userId,0,L"none");
		db.SetUserIdle(userId,0,0);
		// notify interested clients that this person has logged on
		ServerNotify(context,bcUser,userId,bcLogOn,0);
		print(L"	log on was successful\n");
		return bsOk;
	} 
}

int LogOff(/*[in, out]*/ CLIENTCONTEXT_PTR context_ptr)
{
	ClientContext * context = ClientContext::Find((ClientContext *)*context_ptr);
	if(context){
		print(L"%s: LogOff()\n",context->username.data());
		
		// clean up after the client
		// was he in a group meeting?
		int i;
		Vector<int> groupIds;
		db.GetSubscribedGroups(context->userId,groupIds);
		for(i=0;i<groupIds.length;i++){
			Vector<int> userIds;
			db.GetAttendingGroupMembers(groupIds[i],userIds);
			if(userIds.find(context->userId)!=-1){
				// yes, so remove him from the meeting
				print(L"LogOff: Removing %s from meeting (group=%d)\n",context->username.data(),groupIds[i]);
				db.LeaveGroupMeeting(context->userId,groupIds[i]);
				ServerNotify(context,bcGroup,groupIds[i],bcIdle,userIds.length-1);
			}
		}

		// set him as logged off in the db 
		db.SetUserLogin(context->userId,0);
		db.SetUserLocation(context->userId,NullLocationId);
		// notify interested clients that this person has logged off
		ServerNotify(context,bcUser,context->userId,bcLogOff,0);

		// now remove his context
		ClientContext::Delete(context);
	}
	*context_ptr = NULL;
	return bsOk;
}

// chat routines
int SendInstantMessage(/*[in]*/ CLIENTCONTEXT ptr, /*[in]*/ int recipientId, /*[in, string]*/ wchar_t * text)
{
	ClientContext * context = ClientContext::Find((ClientContext *)ptr);
	if(context){
		print(L"%s: SendInstantMessage(recipient=%d)\n",context->username.data(),recipientId);
		ClientContext * context2 = ClientContext::Find(recipientId);
		if(context2){
			Message msg;
			msg.type = Message::InstantMessageNotify;
			msg.str.allocate(3);
			msg.str[0] = context->username;
			msg.str[1] = context2->username;
			msg.str[2] = text;
			msg.param.allocate(2);
			msg.param[0] = context->userId;
			msg.param[1] = context2->userId;
			context2->PostMessage(msg);
			
			// check if the user is away
			UserRecord record;
			db.GetUserState(recipientId,record);
			if(record.Away)
				return bsRecipientIsAway;
			else
				return bsOk;
		} else {
			print(L"   Recipient %d is not logged on.\n",recipientId);
			return bsRecipientNotLoggedOn;
		}
	} else {
		print(L"SendInstantMessage: invalid client context received\n");
		return bsInvalidContext;
	}
}

// chat routines
int SendGroupMessage(/*[in]*/ CLIENTCONTEXT ptr, /*[in]*/ int groupId, wchar_t * group, /*[in, string]*/ wchar_t * text)
{
	ClientContext * context = ClientContext::Find((ClientContext *)ptr);
	if(context){
		print(L"%s: SendGroupMessage(group=%d)\n",context->username.data(),groupId);
		Vector<int> recipientIds;
		switch(db.GetGroupMembers(groupId,recipientIds)){
			case dbInvalidParam:
				print(L"	Invalid group id: %d\n", groupId);
				return bsFailed;
			case dbException:
				print(L"	DB Exception (GetGroupMembers)\n");
				return bsFailed;
		}
		int i;
		for(i=0;i<recipientIds.length;i++){
			ClientContext * context2 = ClientContext::Find(recipientIds[i]);
			if(context2){ // recipient is logged on
				if(context2!=context){ // skip the sender
					Message msg;
					msg.type = Message::GroupMessageNotify;
					msg.str.allocate(3);
					msg.str[0] = context->username;
					msg.str[1] = group;
					msg.str[2] = text;
					msg.param.allocate(2);
					msg.param[0] = context->userId;
					msg.param[1] = groupId;
					context2->PostMessage(msg);
				}
			} else {
				print(L"   Recipient %d is not logged on. Skipping.\n",recipientIds[i]);
			}
		}
		return bsOk;
	} else {
		print(L"SendGroupMessage: invalid client context received\n");
		return bsInvalidContext;
	}
}

int ServerNotify(CLIENTCONTEXT ptr, int type, int theId, int reason, int param)
{
	ClientContext * context = ClientContext::Find((ClientContext *)ptr);
	if(context){
		// determine what subset of users this notification will go to
		print(L"Received Notify from %s: type=%s id=%d reason=%s param=%d\n",
			context->username.data(),GetTypeString(type),theId,GetReasonString(reason),param);
		db.LogNotification(context->userId,theId,GetTypeString(type),GetReasonString(reason),param);
		switch(type){
		case bcUser:
			switch(reason){
			case bcLogOn:
			case bcLogOff:
				ClientContext::NotifyOnlineUsers(type,theId,reason,theId,param);
				break;
			case bcMove:
				ClientContext::NotifyOnlineUsers(type,theId,reason,param);
				break;
			case bcPrivacy:
				ClientContext::NotifyOnlineUsers(type,theId,reason,param);
				break;
			default:
				ClientContext::NotifyWatchers(context->userId,type,theId,reason,param);
				break;
			}
			break;
		case bcGroup:
			switch(reason){
			case bcActive:
			case bcIdle:
				{
					Vector<int> attendants;
					db.GetAttendingGroupMembers(theId,attendants);
					if(attendants.length==0){
						print(L"Setting group %d to be idle (0 attendants).\n",theId);
						db.SetGroupIdle(theId,1);
					} else {
						print(L"Setting group %d to be active (%d attendants).\n",theId,attendants.length);
						db.SetGroupIdle(theId,0);
					}
				}
				ClientContext::NotifyOnlineUsers(type,theId,reason,param);
				break;
			case bcAdd:
			case bcRemove:
			case bcModified:
			case bcSubscribe:
			case bcUnsubscribe:
				ClientContext::NotifyOnlineUsers(type,theId,reason,param);
				break;
			}
			
			break;
		case bcEvent:
			switch(reason){
			case bcAdd:
			case bcRemove:
			case bcModified:
			case bcSubscribe:
			case bcUnsubscribe:
			case bcPermission:
				ClientContext::NotifyOnlineUsers(type,theId,reason,param);
				break;
			}
			switch(reason){
			case bcAdd:
			case bcRemove:
			case bcModified:
				{
					Message msg;
					msg.type = Message::KeepHouse;
					hk.PostMessage(msg);
				}
				break;
			}
			break;
		case bcLocation:
			break;
		case bcSystem:
			break;
		}
		return bsOk;
	} else
		return bsInvalidContext;
}

//The rundown routine is associated with the context handle type.  
void __RPC_USER CLIENTCONTEXT_rundown(CLIENTCONTEXT ptr)
{
	ClientContext * context = ClientContext::Find((ClientContext *)ptr);
	if(context){
		print(L"%s: lost connection\n",context->username.data());
		LogOff(&ptr);
	}
}

/*********************************************************************/
/*                 MIDL allocate and free                            */
/*********************************************************************/

void __RPC_FAR * __RPC_USER midl_user_allocate(size_t len)
{
    return(malloc(len));
}

void __RPC_USER midl_user_free(void __RPC_FAR * ptr)
{
    free(ptr);
}

//} // extern "C"

void RPCServer::ShutDown()
{
	ClientContext::NotifyOnlineUsers(bcSystem,0,bcServerShutdown,0,0);
}

RPCServer::RPCServer(void)
{
}

RPCServer::~RPCServer(void)
{
}

DWORD WINAPI StartService(LPVOID lpParameter)
{
    RPC_STATUS status;
    TCHAR * pszProtocolSequence = _T("ncacn_ip_tcp");//L"ncacn_np";
    TCHAR * pszSecurity     = NULL; 
    TCHAR pszEndpoint[100];
    unsigned int    cMinCalls      = 1;
    unsigned int    cMaxCalls      = 5000;
    unsigned int    fDontWait      = FALSE;
	_tcscpy(pszEndpoint, env.GetWideString(NULL,"port",L"6969"));
 
    status = RpcServerUseProtseqEp(pszProtocolSequence,
                                   cMaxCalls,
                                   pszEndpoint,
                                   pszSecurity); 

	print(L"Protocol: %s\n",pszProtocolSequence);
	print(L"Server Name: %s (%s)\n", GetLocalHostName(),GetLocalIPAddress());
	print(L"Port: %s\n",pszEndpoint);
	print(L"Maximum # of simultaneous connections: %d\n",cMaxCalls);
 
    if (status) 
    {
		print(L"BridgeServer: RpcServerUseProtseqEp failed\n");
		return 0;
    }
 
    status = RpcServerRegisterIf(bridgeserver_v1_0_s_ifspec,  
                                 NULL,   
                                 NULL); 
 
    if (status) 
    {
		print(L"BridgeServer: RpcServerRegisterIf failed\n");
		return 0;
    }


 	print(L"Listening for incoming connections...\n");

    status = RpcServerListen(cMinCalls,
                             cMaxCalls,
                             fDontWait);
 
    if (status) 
    {
		print(L"BridgeServer: RpcServerListen failed\n");
		return 0;
    }

	print(L"BridgeServer: stopped listening\n");

	return bsOk;
}

int RPCServer::StartService()
{
	CreateThread(NULL,NULL,::StartService,NULL,NULL,NULL);
	Sleep(100);
	return bsOk;
}