/****************************************************************************
**
** Copyright (C) 2008 Krzemin <pkrzemin@o2.pl>
**
** This file may be used under the terms of the GNU General Public License
** version 2 as published by the Free Software Foundation and appearing in the
** file GPL.txt included in the packaging of this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include "qinterprocesschannel_win32.h"


/*!
	\class QInterProcessChannel
	
	\brief A generic network-based communication channel for applications.
	
	QInterProcessChannel offers communication facilities between several instances of
	the same application. It initializes itself so that the first created instance acts
	as a "server" which recieve messages from the others instances. This is particularly
	handy when creating a single-instance application which, for example, uses file
	association through a command line interface.
	
	\see QSingleApplication
*/

/*!
	\brief Constructor
	
	Check for a valid server and create one if none found.
*/
QInterProcessChannel::QInterProcessChannel(QObject *p)
 : QThread(p)
{
	uniqPrefix = QApplication::applicationName();
	pipeName = QString("\\\\.\\pipe\\%1%2").arg(uniqPrefix).arg("_uniquePipeName");
	globalMutexStr = uniqPrefix + QString("_globalMutexId");
	blockerMutexStr = uniqPrefix + QString("_blockerMutexId");
	init();
}

/*!
	\brief Destructor
*/
QInterProcessChannel::~QInterProcessChannel()
{
	//while(isRunning())
	//	quit();
	
	close();
}

/*!
	\return Wether the instance has a server role
*/
bool QInterProcessChannel::isServer() const
{
	return serverMode;
}

/*!
	\return The current buffered message
*/
QString QInterProcessChannel::messageBuffer() const
{
	return sMsg;
}

/*!
	\brief Sets a buffered message
*/
void QInterProcessChannel::setMessageBuffer(const QString& s)
{
	sMsg = s;
}

/*!
	\overload
	
	Sends the content of the current message buffer
*/
void QInterProcessChannel::sendMessage()
{
	sendMessage(sMsg);
	sMsg.clear();
}

/*!
	\overload
*/
void QInterProcessChannel::sendMessage(const QString& s)
{
	sendMessage(s.toLocal8Bit());
}
/*!
	\brief Send a message to server instance
*/
void QInterProcessChannel::sendMessage(const QByteArray& msg)
{
	if(msg.count())
	{
		// a lot of simultaneous clients cause that pipe sometimes omits
		// some of these clients despite results of writing functions are ok ;/
		// workaround: reserve mutex, wait 100ms before every write operation
		
		HANDLE mutex = CreateMutexA(NULL, FALSE, qPrintable(blockerMutexStr));
		WaitForSingleObject(mutex, INFINITE);
		Sleep(100);
		
		HANDLE hPipe;
		
		// loop to get pipe's handle
		for(;;)
		{
			hPipe = CreateFileA( 
						qPrintable(pipeName),
						GENERIC_WRITE,
						0,
						NULL,
						OPEN_EXISTING,
						0,
						NULL);
			
			if(hPipe != INVALID_HANDLE_VALUE)
				break;
			
			if(GetLastError() != ERROR_PIPE_BUSY)
			{
				ReleaseMutex(mutex);
				return;
			}
			
			// wait max 1s and retry
			WaitNamedPipeA(qPrintable(pipeName), 1000);
		}
		
		// ok, now we have pipe's handle, we can prepare buffers and write to the pipe
		
		char buffer[bufferSize];
		strncpy(buffer, msg.constData(), bufferSize);
		buffer[bufferSize-1] = 0;
		
		DWORD bytesWritten;
		bool writeSuccess = WriteFile( 
								hPipe,
								buffer,
								strlen(buffer) + 1,
								&bytesWritten,
								NULL);
		
		if (!writeSuccess)
		{
			ReleaseMutex(mutex);
			return;
		}
		
		CloseHandle(hPipe);
		ReleaseMutex(mutex);
	} else {
		//qWarning("::sendMessage(%d): Empty messages are not carried out...", GetCurrentProcessId());
	}
}

/*!
	\brief Closes the communication channel
*/
void QInterProcessChannel::close()
{
	if(serverMode)
	{
		// if we want to quit pipe server thread, we have to send --quit message
		sendMessage(QString("--quit"));
	}
}

/*!
	\internal
*/
void QInterProcessChannel::run()
{
	if(!serverMode) return;
	
	HANDLE hPipe;
	char buffer[bufferSize];
	bool connected, readSuccess;
	DWORD bytesRead;
	
	// thread loop
	for(;;)
	{
		hPipe = CreateNamedPipeA(
					qPrintable(pipeName),
					PIPE_ACCESS_INBOUND,
					PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
					1,
					0,
					bufferSize,
					0,
					NULL);
		
		if(hPipe == INVALID_HANDLE_VALUE)
			break;
		
		connected = ConnectNamedPipe(hPipe, NULL) ?
						TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
		
		if(!connected)
		{
			CloseHandle(hPipe);
			continue;
		}
		
		readSuccess = ReadFile(
						hPipe,
						buffer,
						bufferSize,
						&bytesRead,
						NULL);
		buffer[bufferSize - 1] = 0;
		
		if(!readSuccess || !bytesRead)
		{
			CloseHandle(hPipe);
			continue;
		}
		
		QStringList argv = QManagedRequest::splitArguments(QString(buffer));
		const QString cmd = argv.at(0);
		
		// if server instance is going to suspend thread...
		if ( cmd == "--quit" ) {
			CloseHandle(hPipe);
			break;
		} else if ( cmd == "--request" ) {
			argv.removeAt(0);
			emit request(argv);
		} else {
			emit message(QString(buffer));
		}
		
		CloseHandle(hPipe);
	}
}

/*!
	\internal
*/
void QInterProcessChannel::init()
{
	//while(isRunning())
	//	quit();
	
	// create global mutex to check single instance
	CreateMutexA(NULL, FALSE, qPrintable(globalMutexStr));
	serverMode = !(GetLastError() == ERROR_ALREADY_EXISTS);
	
	// start listening thread in single instance mode
	if(serverMode) start();
}
