///////////////////////////////////////////////////////////////////////////////
// MQ4CPP - Message queuing for C++
// Copyright (C) 2004-2007  Riccardo Pompeo (Italy)
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//

#define SILENT
#include "LockManager.h"
#include "Trace.h"
#include "GeneralHashFunctions.h"

LockManagerClient::LockManagerClient(const char* theName, const char* theHost,int thePort, const char* theTarget) 
				   :Client(theName, theHost, thePort, theTarget)
{
	TRACE("LockManagerClient::LockManagerClient - start")

	TRACE("LockManagerClient::LockManagerClient - end")
}

LockManagerClient::LockManagerClient(const char* theName, const char* theHost,int thePort, const char* theTarget,Encription* theEncr) 
				   :Client(theName, theHost, thePort, theTarget)
{
	TRACE("LockManagerClient::LockManagerClient - start")
	setEncription(theEncr);
	TRACE("LockManagerClient::LockManagerClient - end")
}

LockManagerClient::~LockManagerClient()
{
	TRACE("LockManagerClient::~LockManagerClient - start")

	TRACE("LockManagerClient::~LockManagerClient - end")
}

bool LockManagerClient::lock(const char* theResource)
{
	TRACE("LockManagerClient::lock - start")
	bool ret=false;	
	bool found=false;

	list<LockManagerSession>::iterator i;
	for (i=itsSessions.begin(); i != itsSessions.end(); i++)
	{
		if((*i).resource.compare(theResource)==0)
		{
			found=true;
			break;	
		}			
	}

	if(!found)
	{
		itsTxStructure.free();	
		StringProperty* aLockTypeProperty=new StringProperty("LT");
		aLockTypeProperty->set("LCK");			
		itsTxStructure.add(aLockTypeProperty);
	
		StringProperty* aResourceProperty=new StringProperty("RN");
		aResourceProperty->set(theResource);		
		itsTxStructure.add(aResourceProperty);
		TRACE("Resource name=" << theResource)
	
		LockManagerSession aNewSession;
		aNewSession.time=Timer::time();
		aNewSession.token=0;
		aNewSession.resource=theResource;
		aNewSession.state=LM_WAIT_ACK;
		aNewSession.client=JSHash(theResource)+threadID()+Timer::time()+rand();
	
		LongIntProperty* aClientTokenProperty=new LongIntProperty("CT");
		aClientTokenProperty->set(aNewSession.client);		
		itsTxStructure.add(aClientTokenProperty);
		TRACE("Client token=" << aNewSession.client)
	
		itsSessions.push_back(aNewSession);
	
		string aString;
		encodeProperties(itsTxStructure,aString);
		sendMessage(aString);
		ret=true;
	}

	TRACE("LockManagerClient::lock - end")
	return ret;
}

bool LockManagerClient::unlock(const char* theResource)
{
	TRACE("LockManagerClient::unlock - start")
	bool ret=false;	
	bool found=false;

	itsTxStructure.free();	
	StringProperty* aLockTypeProperty=new StringProperty("LT");
	aLockTypeProperty->set("ULK");		
	itsTxStructure.add(aLockTypeProperty);

	list<LockManagerSession>::iterator i;
	for (i=itsSessions.begin(); i != itsSessions.end(); i++)
	{
		if((*i).resource.compare(theResource)==0)
		{
			found=true;
			break;	
		}			
	}

	if(found)
	{	
		LongIntProperty* aTokenProperty=new LongIntProperty("TK");
		aTokenProperty->set((*i).token);		
		itsTxStructure.add(aTokenProperty);
		TRACE("Session token=" << (*i).token)

		LongIntProperty* aClientTokenProperty=new LongIntProperty("CT");
		aClientTokenProperty->set((*i).client);		
		itsTxStructure.add(aClientTokenProperty);
		TRACE("Client token=" << (*i).client)

		(*i).time=Timer::time();
		(*i).state=LM_WAIT_ACK;

		string aString;
		encodeProperties(itsTxStructure,aString);
		sendMessage(aString);
		ret=true;
	}

	TRACE("LockManagerClient::unlock - end")
	return ret;
}

void LockManagerClient::success(string theBuffer)
{
	TRACE("LockManagerClient::success - start")

	decodeProperties(theBuffer,itsResponse);

	LockManagerResult result=LM_VOID;

	Property* aProperty=itsResponse.get("RS");
	if(aProperty!=NULL && aProperty->is(PROPERTY_SHORTINT))
	{
		result=(LockManagerResult)((ShortIntProperty*)aProperty)->get();
		TRACE("Result=" << result)
	}

	unsigned long token=0;
	aProperty=itsResponse.get("TK");
	if(aProperty!=NULL && aProperty->is(PROPERTY_LONGINT))
	{
		token=((LongIntProperty*)aProperty)->get();
		TRACE("Session token=" << token)
	}

	unsigned long client=0;
	aProperty=itsResponse.get("CT");
	if(aProperty!=NULL && aProperty->is(PROPERTY_LONGINT))
	{
		client=((LongIntProperty*)aProperty)->get();
		TRACE("Client token=" << client)
	}

	bool found=false;
	list<LockManagerSession>::iterator i;
	for (i=itsSessions.begin(); i != itsSessions.end(); i++)
	{
		if((*i).client==client)
		{
			found=true;
			break;	
		}			
	}

	if(found)
	{
		LockManagerSession& aSession=*i;

		switch(result)
		{
				case LM_NOT_ALLOWED:
					TRACE("Lock manager exception")
					aSession.state=LM_FAILED;
					aSession.token=0;
					onCompletion(aSession);
					itsSessions.erase(i);
					break;
				
				case LM_LOCKED:
					TRACE("Lock acquired")
					aSession.token=token;
					aSession.state=LM_STOP;
					onCompletion(aSession);
					break;
				
				case LM_UNLOCKED:
					TRACE("Unlocked")
					aSession.state=LM_STOP;
					aSession.token=0;
					onCompletion(aSession);
					itsSessions.erase(i);
					break;
								
				default:
					TRACE("Return value not allowed")
					aSession.state=LM_FAILED;
					aSession.token=0;
					onCompletion(aSession);			
					itsSessions.erase(i);
		}
	}
	else
	{
		TRACE("Client session not found. Token=" << token)	
	}
		
	TRACE("LockManagerClient::success - end")
}

void LockManagerClient::onWakeup(Wakeup* theMessage)
{
	TRACE("LockManagerClient::onWakeup - start")

	for(list<LockManagerSession>::iterator i=itsSessions.begin(); i != itsSessions.end(); i++)
	{
		LockManagerSession& aSession=*i;

		if(Timer::time()-aSession.time>LM_TIMEOUT)
		{
			TRACE("Session timeout. Token=" << aSession.token << " dTime=" << (theMessage->getTime()-aSession.time))
			aSession.state=LM_FAILED;
			aSession.token=0;
			onCompletion(aSession);			
			break;
		}			
	}

	Client::onWakeup(theMessage);
	
	TRACE("LockManagerClient::onWakeup - end")
}

LockManagerServer::LockManagerServer(const char* theName)
				   :Server(theName)
{
	TRACE("LockManagerServer::LockManagerServer - start")
	SCHEDULE(this,500);
	TRACE("LockManagerServer::LockManagerServer - end")
}

LockManagerServer::LockManagerServer(const char* theName,Encription* theEncr)
				   :Server(theName)
{
	TRACE("LockManagerServer::LockManagerServer - start")
	setEncription(theEncr);	
	SCHEDULE(this,500);
	TRACE("LockManagerServer::LockManagerServer - end")
}

LockManagerServer::~LockManagerServer()
{
	TRACE("LockManagerServer::~LockManagerServer - start")
	
	TRACE("LockManagerServer::~LockManagerServer - end")
}
	
string LockManagerServer::service(string theBuffer)
{
	TRACE("LockManagerServer::service - start")
	string ret;
	ListProperty aResponse;

	decodeProperties(theBuffer,itsRxStructure);

	Property* aProperty=itsRxStructure.get("CT");
	if(aProperty!=NULL && aProperty->is(PROPERTY_LONGINT))
	{
		unsigned long aClientToken=((LongIntProperty*)aProperty)->get();

		aProperty=itsRxStructure.get("LT");
		if(aProperty!=NULL && aProperty->is(PROPERTY_STRING))
		{
			string btype=((StringProperty*)aProperty)->get();
			TRACE("Lock type=" << btype)
	
			if(btype.compare("LCK")==0)
			{			
				receiveLock(aClientToken,aResponse);
			}
			else if(btype.compare("ULK")==0)
			{
				receiveUnlock(aClientToken,aResponse);
			}
			else
			{
				setResponse(aResponse,LM_NOT_ALLOWED,aClientToken);
			}	
		}
	}

	encodeProperties(aResponse,ret);

	TRACE("LockManagerServer::service - end")
	return ret;
}

void LockManagerServer::setResponse(ListProperty& theResponse,LockManagerResult theCode,unsigned long theClientToken,unsigned long theToken)
{
	TRACE("LockManagerServer::setResponse - start")
	TRACE("Result=" << theCode)
	TRACE("Token=" << theToken)
	
	ShortIntProperty* aResultProperty=new ShortIntProperty("RS");
	aResultProperty->set(theCode);		
	theResponse.add(aResultProperty);
	
	if(theToken!=0L)
	{
		LongIntProperty* aTokenProperty=new LongIntProperty("TK");
		aTokenProperty->set(theToken);		
		theResponse.add(aTokenProperty);
	}

	LongIntProperty* aClientTokenProperty=new LongIntProperty("CT");
	aClientTokenProperty->set(theClientToken);		
	theResponse.add(aClientTokenProperty);
	TRACE("LockManagerServer::setResponse - end")
}

unsigned long LockManagerServer::lock(string theResourceName)
{
	TRACE("LockManagerServer::lock - start")
	wait();

	LockManagerSession aNewSession;
	aNewSession.time=Timer::time();
	aNewSession.token=RSHash(theResourceName)+Timer::time();
	aNewSession.resource=theResourceName;
	aNewSession.client=0;

	bool found=false;
	list<LockManagerSession>::iterator i;
	for (i=itsSessions.begin(); i != itsSessions.end(); i++)
	{
		if((*i).resource.compare(theResourceName)==0)
		{
			found=true;
			break;	
		}			
	}

	if(!found)
	{	
		onLock(aNewSession);
		itsSessions.push_back(aNewSession);
		TRACE("Resource locked. Name=" << aNewSession.resource)
	}
	else
	{
		aNewSession.token=0;
		TRACE("Resource already locked. Name=" << aNewSession.resource)
	}

	release();
	
	TRACE("LockManagerServer::lock - end")
	return aNewSession.token;
}

void LockManagerServer::receiveLock(unsigned long theClientToken,ListProperty& theResponse)
{
	TRACE("LockManagerServer::receiveLock - start")
	TRACE("Client token=" << theClientToken)

	Property* aProperty=itsRxStructure.get("RN");
	if(aProperty!=NULL && aProperty->is(PROPERTY_STRING))
	{
		LockManagerSession aNewSession;
		aNewSession.time=Timer::time();
		
		string name=((StringProperty*)aProperty)->get();
		TRACE("Resource mame= " << name)
		aNewSession.token=RSHash(name)+rand()*rand();
		aNewSession.resource=name;
		aNewSession.client=theClientToken;

		bool found=false;
		list<LockManagerSession>::iterator i;
		for (i=itsSessions.begin(); i != itsSessions.end(); i++)
		{
			if((*i).resource.compare(name)==0)
			{
				found=true;
				break;	
			}			
		}

		if(!found)
		{	
			onLock(aNewSession);
			itsSessions.push_back(aNewSession);
			setResponse(theResponse,LM_LOCKED,theClientToken,aNewSession.token);
			TRACE("Resource locked. Name=" << aNewSession.resource)
		}
		else if(found && (*i).client==theClientToken)
		{
			setResponse(theResponse,LM_LOCKED,theClientToken,aNewSession.token);
			TRACE("Resource already locked. Name=" << aNewSession.resource)
		}
		else
		{
			setResponse(theResponse,LM_NOT_ALLOWED,theClientToken);
			TRACE("Resource already locked. Name=" << aNewSession.resource)
		}
	}
	else
	{
		setResponse(theResponse,LM_NOT_ALLOWED,theClientToken);
		TRACE("Wrong parameters from peer")
	}

	TRACE("LockManagerServer::receiveLock - end")
}

void LockManagerServer::unlock(unsigned long theToken)
{
	TRACE("LockManagerServer::unlock - start")
	wait();
	
	bool found=false;
	list<LockManagerSession>::iterator i;
	for (i=itsSessions.begin(); i != itsSessions.end(); i++)
	{
		if((*i).token==theToken)
		{
			found=true;
			break;	
		}			
	}
	
	if(found)
	{
		TRACE("Session found")
		TRACE("Resource unlocked. Name=" << (*i).resource)
		onUnlock(*i);
		itsSessions.erase(i);
	}
	else
	{
		TRACE("Session not found")			
	}

	release();
	
	TRACE("LockManagerServer::unlock - end")
}

void LockManagerServer::receiveUnlock(unsigned long theClientToken,ListProperty& theResponse)
{
	TRACE("LockManagerServer::receiveUnlock - start")
	TRACE("Client token=" << theClientToken)
	
	Property* aProperty=itsRxStructure.get("TK");
	if(aProperty!=NULL && aProperty->is(PROPERTY_LONGINT))
	{
		unsigned long token=((LongIntProperty*)aProperty)->get();
		TRACE("Session token=" << token)
		
		bool found=false;
		list<LockManagerSession>::iterator i;
		for (i=itsSessions.begin(); i != itsSessions.end(); i++)
		{
			if((*i).token==token && (*i).client==theClientToken)
			{
				found=true;
				break;	
			}			
		}
		
		if(found)
		{
			TRACE("Session found")
			TRACE("Resource unlocked. Name=" << (*i).resource)
			setResponse(theResponse,LM_UNLOCKED,theClientToken);
			onUnlock(*i);
			itsSessions.erase(i);
		}
		else
		{
			TRACE("Session not found")			
			setResponse(theResponse,LM_UNLOCKED,theClientToken);
		}
	}
	else
	{
		TRACE("Wrong parameters from peer")
		setResponse(theResponse,LM_NOT_ALLOWED,theClientToken);
	}

	TRACE("LockManagerServer::receiveUnlock - end")
}

void LockManagerServer::onWakeup(Wakeup* theMessage)
{
	TRACE("LockManagerServer::onWakeup - start")
	
	for(list<LockManagerSession>::iterator i=itsSessions.begin(); i != itsSessions.end(); i++)
	{
		if(Timer::time()-(*i).time>LM_SERVERTIMEOUT)
		{
			TRACE("Session timeout. Token=" << (*i).token << " dTime=" << (theMessage->getTime()-(*i).time))
			onUnlock(*i);
			itsSessions.erase(i);
			break;
		}			
	}
	
	TRACE("LockManagerServer::onWakeup - end")
}	

void LockManagerServer::onLock(LockManagerSession& theSession)
{
//	DISPLAY("Resource '" << theSession.resource << "' locked. Client token=" << theSession.client << ". Server token=" << theSession.token)
}	

void LockManagerServer::onUnlock(LockManagerSession& theSession)	
{
//	DISPLAY("Resource '" << theSession.resource << "' unlocked. Client token=" << theSession.client << ". Server token=" << theSession.token)
}	

