#include "StdAfx.h"
#include "clientcontext.h"

HANDLE ClientContext::Mutex = CreateMutex(NULL,FALSE,NULL);
std::list<ClientContext*> ClientContext::contexts; // the global list of contexts
int ClientContext::ClientContextCount = 0; // to detect stale contexts

ClientContext::ClientContext(int id, const TCHAR * u, const TCHAR * machinename, const TCHAR * port)
{
	username = u;
	userId = id;
	client.UUID = NULL;
	client.Protocol = L"ncacn_ip_tcp";
	client.Address = new wchar_t[100];
	_tcscpy(client.Address,machinename);
	std::wstring address = client.Address;
	if(address==L"204.204.204.204")
		_tcscpy(client.Address,L"localhost");
	client.Endpoint = new wchar_t[100];
	_tcscpy(client.Endpoint,port);
	client.Options = NULL;
	print(L"%s: Connecting from\n",u);
	print(L"    uuid = %s\n",client.UUID);
	print(L"    protocol = %s\n",client.Protocol);
	print(L"    address = %s\n",client.Address);
	print(L"    endpoint = %s\n",client.Endpoint);
	print(L"    options = %s\n",client.Options);
	dead = 0;
	// create the event handles 
	MessageNotify = CreateEvent(NULL,TRUE,FALSE,NULL);
	ClientContextCount++;
    // Initialize the critical section.
    InitializeCriticalSection(&CriticalSection); 
}

ClientContext::~ClientContext(void)
{
	CloseHandle(MessageNotify);
	if(client.Address)
		delete [] client.Address;
	if(client.Endpoint)
		delete [] client.Endpoint;
	ClientContextCount--;
    // Release resources used by the critical section object.
    DeleteCriticalSection(&CriticalSection);
	print(L"Cleaning up stale context of %s (%d remaining)\n",username.data(),ClientContextCount);
}

int ClientContext::PostMessageAndWait(Message & msg, int timeout)
{
	msg.NotifyDone = CreateEvent(NULL,TRUE,NULL,NULL);
	PostMessage(msg);	
	WaitForSingleObject(msg.NotifyDone,timeout);
	CloseHandle(msg.NotifyDone);
	return msg.result;
}

void ClientContext::PostMessage(Message & msg)
{
	if(msg.NotifyDone){
		//EnterCriticalSection(&CriticalSection); 
		Q.push_front(&msg);
		//LeaveCriticalSection(&CriticalSection);
	} else {
		Message * msgcopy = msg.Copy();
		//EnterCriticalSection(&CriticalSection); 
		Q.push_front(msgcopy);
		//LeaveCriticalSection(&CriticalSection);
	}
	PulseEvent(MessageNotify);
}

int ClientContext::MessagePump()
{
	while(!dead){
		if(Q.size()==0)
			WaitForSingleObject(MessageNotify,INFINITE);
		if(dead) break;
		// the pump is being notified to take care of a message from outside this thread
		while(Q.size()>0){
			Message * msg = Q.back();
			switch(msg->type){
				case Message::Quit:
					dead = 1;
					break;
				case Message::InstantMessageNotify:
					print(L"Relaying Instant Message: %s --> %s, msg = %s\n",
						msg->str[0].data(),msg->str[1].data(),msg->str[2].data());
					InstantMessageNotify(msg->param[0],msg->str[0].data(),msg->param[1],msg->str[1].data(),msg->str[2].data());
					break;
				case Message::GroupMessageNotify:
					print(L"Relaying Group Message: %s --> %s, msg = %s\n",
						msg->str[0].data(),msg->str[1].data(),msg->str[2].data());
					GroupMessageNotify(msg->param[0],msg->str[0].data(),msg->param[1],msg->str[1].data(),msg->str[2].data());
					break;
				case Message::Notify:
					print(L"Notifying %s: type=%s, id=%d, reason=%s, param=%d\n",
						username.data(),GetTypeString(msg->idType),msg->id,GetReasonString(msg->reason),msg->param[0]);
					ClientNotify(msg->idType,msg->id,msg->reason,msg->param[0]);
					break;
				case Message::Unknown:
				default: // unknown message
					break;
			}
			Q.pop_back();
			// this means that this message is being waited on, 
			// so don't delete it (see PostMessageAndWait)
			if(msg->NotifyDone) 
				SetEvent(msg->NotifyDone);
			else
				delete msg;
		}
	}

	return dead;
}

void ClientContext::Die()
{
	dead = 1;
	PulseEvent(MessageNotify);
}

DWORD WINAPI ClientContext::StartContextPump(LPVOID p)
{
	ClientContext * context = (ClientContext*)p;
	context->MessagePump();
	ClientContext::Delete(context);
	Sleep(5000);
	delete context;
	return 0;
}

// these static routines maintain the context list and threads
// only use these to create and delete ClientContext objects
ClientContext * ClientContext::Create(int userId, const TCHAR * username, const TCHAR * machinename, const TCHAR * port)
{
	ClientContext * context = new ClientContext(userId,username,machinename,port);
	while(WaitForSingleObject(Mutex,1000)==WAIT_TIMEOUT){
		print(L"ClientContext::Create: timing out waiting for mutex.\n");
	}
	contexts.push_back(context);
	ReleaseMutex(Mutex);
	context->hThread = CreateThread(NULL,0,StartContextPump,context,NULL,NULL);
	return context;
}

int ClientContext::Delete(ClientContext * context)
{
	while(WaitForSingleObject(Mutex,1000)==WAIT_TIMEOUT){
		print(L"ClientContext::Delete: timing out waiting for mutex.\n");
	}
	std::list<ClientContext*>::iterator i;
	for(i=contexts.begin();i!=contexts.end();i++){
		if(context==*i){
			print(L"De-activating client context of %s.\n",context->username.data());
			context->Die();
			contexts.erase(i);
			ReleaseMutex(Mutex);
			return 1;
		}
	}
	ReleaseMutex(Mutex);
	return 0;
}

ClientContext * ClientContext::Find(int userId)
{
	std::list<ClientContext*>::iterator i;
	for(i=contexts.begin();i!=contexts.end();i++){
		ClientContext * context = *i;
		if(context->userId==userId){
			if(context->dead){
				ClientContext::Delete(context);
				return NULL;
			}
			return context;
		}
	}
	return NULL;	
}

ClientContext * ClientContext::Find(ClientContext * ptr)
{
	std::list<ClientContext*>::iterator i;
	for(i=contexts.begin();i!=contexts.end();i++){
		ClientContext * context = *i;
		if(context==ptr){
			if(context->dead){
				ClientContext::Delete(context);
				return NULL;
			}
			return context;
		}
	}
	return NULL;	
}

void ClientContext::Notify(int userId, int type, int theId, int reason, int param)
{
	ClientContext * recipient_context = ClientContext::Find(userId);
	if(recipient_context && !recipient_context->dead){
		Message msg;
		msg.type = Message::Notify;
		msg.idType = type;
		msg.id = theId;
		msg.reason = reason;
		msg.param.allocate(1);
		msg.param[0] = param;
		recipient_context->PostMessageAndWait(msg,1000);
	}
}

void ClientContext::Notify(Vector<int> recipients, int type, int theId, int reason, int param, int except)
{
	int i;
	for(i=0;i<recipients.length;i++){
		if(recipients[i]!=except){
			ClientContext * recipient_context = ClientContext::Find(recipients[i]);
			if(recipient_context && !recipient_context->dead){
				Message msg;
				msg.type = Message::Notify;
				msg.idType = type;
				msg.id = theId;
				msg.reason = reason;
				msg.param.allocate(1);
				msg.param[0] = param;
				recipient_context->PostMessage(msg);
			}
		}
	}
}

void ClientContext::NotifyWatchers(int userId, int type, int theId, int reason, int param, int except)
{
	Vector<int> everyone;
	db.GetUsersThatWatchMe(userId,everyone);
	Notify(everyone,type,theId,reason,param,except);
}

void ClientContext::NotifyOnlineUsers(int type, int theId, int reason, int param, int except)
{
	Vector<int> everyone;
	db.GetOnlineUsers(everyone);
	Notify(everyone,type,theId,reason,param,except);
}

void ClientContext::NotifyGroupMembers(int groupId, int type, int theId, int reason, int param, int except)
{
	Vector<int> everyone;
	db.GetGroupMembers(groupId,everyone);
	Notify(everyone,type,theId,reason,param,except);
}

void ClientContext::NotifyEventMembers(int eventId, int type, int theId, int reason, int param, int except)
{
	Vector<int> everyone;
	db.GetEventMembers(eventId,everyone);
	Notify(everyone,type,theId,reason,param,except);
}

void ClientContext::NotifyEventCanViews(int eventId, int type, int theId, int reason, int param, int except)
{
	Vector<int> everyone;
	db.GetEventCanViews(eventId,everyone);
	Notify(everyone,type,theId,reason,param,except);
}

//// RPC calls to the client service that is associated with this context

// the BridgeServer calls this to notify the BridgeClient of a chat message
void ClientContext::InstantMessageNotify(int senderId, /*[in,string]*/ const TCHAR * sender, int recipientId, /*[in,string]*/ const TCHAR * recipient, /*[in,string]*/ const TCHAR * msg)
{
    RpcTryExcept  
    {
		::InstantMessageNotify(&client,senderId,(TCHAR*)sender,recipientId,(TCHAR*)recipient,(TCHAR*)msg);
	}
    RpcExcept(1) 
    {
        unsigned long ulCode = RpcExceptionCode();
        print(L"ClientContext::InstantMessageNotify: Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);
    }
    RpcEndExcept
}

// the BridgeServer calls this to notify the BridgeClient of a group message
void ClientContext::GroupMessageNotify(int senderId, /*[in,string]*/ const TCHAR * sender, int recipientId, /*[in,string]*/ const TCHAR * recipient, /*[in,string]*/ const TCHAR * msg)
{
    RpcTryExcept  
    {
		::GroupMessageNotify(&client,senderId,(TCHAR*)sender,recipientId,(TCHAR*)recipient,(TCHAR*)msg);
	}
    RpcExcept(1) 
    {
        unsigned long ulCode = RpcExceptionCode();
        print(L"ClientContext::GroupMessageNotify: Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);
    }
    RpcEndExcept
}

// the BridgeServer calls this to notify the BridgeClient of changes
void ClientContext::ClientNotify(/*[in]*/ int type, /*[in]*/ int id, /*[in]*/ int reason, int param)
{
    RpcTryExcept  
    {
		::ClientNotify(&client,type,id,reason,param);
	}
    RpcExcept(1) 
    {
        unsigned long ulCode = RpcExceptionCode();
        print(L"ClientContext::Notify: Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);
    }
    RpcEndExcept
}

// the bind and unbind routines for the connection to the client service
extern "C" {

/* This _bind routine is called by the client stub immediately */
/* before each remote procedure call.                          */
RPC_BINDING_HANDLE __RPC_USER HBC_bind(HBC h)
{
    RPC_STATUS status;    // returned by RPC API functions
    RPC_BINDING_HANDLE hBinding;
    wchar_t * pszStringBinding;

    status = RpcStringBindingCompose(h->UUID,
                                     h->Protocol,
                                     h->Address,
                                     h->Endpoint,
                                     h->Options,
                                     &pszStringBinding);
    if (status) {
		print(L"HBC_bind: RpcStringBindingCompose failed\n");
		return NULL;
    }

    status = RpcBindingFromStringBinding(pszStringBinding,
                                         &hBinding);
    if (status) {
		print(L"HBC_bind: RpcBindingFromStringBinding failed\n");
		return NULL;
    }

    status = RpcStringFree(&pszStringBinding);  // free storage

	status = RpcMgmtSetComTimeout(hBinding,env.Timeout);
	if (status) 
    {
		print(L"HBC_bind: RpcMgmtSetComTimeout failed\n");
		return 0;
    }

    return(hBinding);
}

/* This _unbind routine is called by the client stub immediately */
/* after each remote procedure call.                             */
void __RPC_USER HBC_unbind(HBC h, RPC_BINDING_HANDLE hBinding)
{
     RPC_STATUS status;    // returned by RPC API functions
     status = RpcBindingFree(&hBinding);  // unbind
}

} // extern "C"