/****************************************************************************
** 
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
** 
** This file is part of a Qt Solutions component.
**
** Commercial Usage  
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Solutions Commercial License Agreement provided
** with the Software or, alternatively, in accordance with the terms
** contained in a written agreement between you and Nokia.
** 
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
** 
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this
** package.
** 
** GNU General Public License Usage 
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
** 
** Please note Third Party Software included with Qt Solutions may impose
** additional restrictions and it is the user's responsibility to ensure
** that they have met the licensing requirements of the GPL, LGPL, or Qt
** Solutions Commercial license and the relevant license of the Third
** Party Software they are using.
** 
** If you are unsure which license is appropriate for your use, please
** contact Nokia at qt-info@nokia.com.
** 
****************************************************************************/

#include "qtlocalpeer.h"
#include <QCoreApplication>
#include <QDataStream>
#include <QRegularExpression>
#include <QTime>
#include <QtDebug>

#if defined(Q_OS_WIN)
#include <QLibrary>
#include <qt_windows.h>
typedef BOOL(WINAPI* PProcessIdToSessionId)(DWORD, DWORD*);
static PProcessIdToSessionId pProcessIdToSessionId = 0;
#endif
#if defined(Q_OS_UNIX)
#include <time.h>
#include <unistd.h>
#endif

const char* QtLocalPeer::ack = "ack";

static QRegularExpression prefixRegex = QRegularExpression("[^a-zA-Z]");

QtLocalPeer::QtLocalPeer(QObject* parent, const QString& appId)
	: QObject(parent), id(appId)
{
	QString prefix = id;
	if (id.isEmpty()) {
		id = QCoreApplication::applicationFilePath();
#if defined(Q_OS_WIN)
		id = id.toLower();
#endif
		prefix = id.section(QLatin1Char('/'), -1);
	}
	prefix.remove(prefixRegex);
	prefix.truncate(6);

	QByteArray idc = id.toUtf8();
	quint16 idNum = qChecksum(idc);
	socketName = QLatin1String("qtsingleapp-") + prefix
			+ QLatin1Char('-') + QString::number(idNum, 16);

#if defined(Q_OS_WIN)
	if (!pProcessIdToSessionId) {
		QLibrary lib("kernel32");
		pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
	}
	if (pProcessIdToSessionId) {
		DWORD sessionId = 0;
		pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
		socketName += QLatin1Char('-') + QString::number(sessionId, 16);
	}
#else
	socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
#endif

	server = new QLocalServer(this);
	QString lockName = QDir(QDir::tempPath()).absolutePath()
			+ QLatin1Char('/') + socketName
			+ QLatin1String("-lockfile");
	lockFile.setFileName(lockName);
	lockFile.open(QIODevice::ReadWrite);
}

bool QtLocalPeer::isClient()
{
	if (lockFile.isLocked())
		return false;

	if (!lockFile.lock(QtLockedFile::WriteLock, false))
		return true;

	bool res = server->listen(socketName);
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4, 5, 0))
	// ### Workaround
	if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
		QFile::remove(QDir::cleanPath(QDir::tempPath()) + QLatin1Char('/') + socketName);
		res = server->listen(socketName);
	}
#endif
	if (!res)
		qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
	QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
	return false;
}

bool QtLocalPeer::sendMessage(const QString& message, int timeout)
{
	return sendMessage(message.toUtf8(), timeout);
}

bool QtLocalPeer::sendMessage(const char* message, int timeout)
{
	return sendMessage(QByteArray(message), timeout);
}

bool QtLocalPeer::sendMessage(const QByteArray& message, int timeout)
{
	if (!isClient())
		return false;

	QLocalSocket socket;
	bool connOk = false;
	for (int i = 0; i < 2; i++) {
		// Try twice, in case the other instance is just starting up
		socket.connectToServer(socketName);
		connOk = socket.waitForConnected(timeout / 2);
		if (connOk || i)
			break;
		int ms = 250;
#if defined(Q_OS_WIN)
		Sleep(DWORD(ms));
#else
		struct timespec ts = {ms / 1000, (ms % 1000) * 1000 * 1000};
		nanosleep(&ts, NULL);
#endif
	}
	if (!connOk)
		return false;

	QDataStream ds(&socket);
	ds.writeBytes(message.constData(), message.size());
	bool res = socket.waitForBytesWritten(timeout);
	res &= socket.waitForReadyRead(timeout);// wait for ack
	res &= (socket.read(qstrlen(ack)) == ack);
	return res;
}

void QtLocalPeer::receiveConnection()
{
	QLocalSocket* socket = server->nextPendingConnection();
	if (!socket)
		return;

	while (socket->bytesAvailable() < (int)sizeof(quint32))
		socket->waitForReadyRead();
	QDataStream ds(socket);
	QByteArray uMsg;
	quint32 remaining;
	ds >> remaining;
	uMsg.resize(remaining);
	int got = 0;
	char* uMsgBuf = uMsg.data();
	do {
		got = ds.readRawData(uMsgBuf, remaining);
		remaining -= got;
		uMsgBuf += got;
	} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
	if (got < 0) {
		qWarning() << "QtLocalPeer: Message reception failed" << socket->errorString();
		delete socket;
		return;
	}
	socket->write(ack, qstrlen(ack));
	socket->waitForBytesWritten(1000);
	delete socket;
	emit messageReceived(uMsg);//### (might take a long time to return)
	emit messageReceived(QString::fromUtf8(uMsg));
}

#include "moc_qtlocalpeer.cpp"
