/****************************************************************************
** 
** 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 "qtlockedfile.h"
#include <QFileInfo>
#include <qt_windows.h>

#define MUTEX_PREFIX "QtLockedFile mutex "
// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
#define MAX_READERS MAXIMUM_WAIT_OBJECTS

#if QT_VERSION >= 0x050000
#define QT_WA(unicode, ansi) unicode
#endif

Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
{
	if (mutexname.isEmpty()) {
		QFileInfo fi(*this);
		mutexname = QString::fromLatin1(MUTEX_PREFIX)
				+ fi.absoluteFilePath().toLower();
	}
	QString mname(mutexname);
	if (idx >= 0)
		mname += QString::number(idx);

	Qt::HANDLE mutex;
	if (doCreate) {
		QT_WA({ mutex = CreateMutexW(NULL, FALSE, (WCHAR*)mname.utf16()); }, { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); });
		if (!mutex) {
			qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
			return 0;
		}
	}
	else {
		QT_WA({ mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (WCHAR*)mname.utf16()); }, { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); });
		if (!mutex) {
			if (GetLastError() != ERROR_FILE_NOT_FOUND)
				qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
			return 0;
		}
	}
	return mutex;
}

bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
{
	Q_ASSERT(mutex);
	DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
	switch (res) {
	case WAIT_OBJECT_0:
	case WAIT_ABANDONED:
		return true;
		break;
	case WAIT_TIMEOUT:
		break;
	default:
		qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
	}
	return false;
}

bool QtLockedFile::lock(LockMode mode, bool block)
{
	if (!isOpen()) {
		qWarning("QtLockedFile::lock(): file is not opened");
		return false;
	}

	if (mode == NoLock)
		return unlock();

	if (mode == m_lock_mode)
		return true;

	if (m_lock_mode != NoLock)
		unlock();

	if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
		return false;

	if (!waitMutex(wmutex, block))
		return false;

	if (mode == ReadLock) {
		int idx = 0;
		for (; idx < MAX_READERS; idx++) {
			rmutex = getMutexHandle(idx, false);
			if (!rmutex || waitMutex(rmutex, false))
				break;
			CloseHandle(rmutex);
		}
		bool ok = true;
		if (idx >= MAX_READERS) {
			qWarning("QtLockedFile::lock(): too many readers");
			rmutex = 0;
			ok = false;
		}
		else if (!rmutex) {
			rmutex = getMutexHandle(idx, true);
			if (!rmutex || !waitMutex(rmutex, false))
				ok = false;
		}
		if (!ok && rmutex) {
			CloseHandle(rmutex);
			rmutex = 0;
		}
		ReleaseMutex(wmutex);
		if (!ok)
			return false;
	}
	else {
		Q_ASSERT(rmutexes.isEmpty());
		for (int i = 0; i < MAX_READERS; i++) {
			Qt::HANDLE mutex = getMutexHandle(i, false);
			if (mutex)
				rmutexes.append(mutex);
		}
		if (rmutexes.size()) {
			DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
			                                   TRUE, block ? INFINITE : 0);
			if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
				if (res != WAIT_TIMEOUT)
					qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
				m_lock_mode = WriteLock;// trick unlock() to clean up - semiyucky
				unlock();
				return false;
			}
		}
	}

	m_lock_mode = mode;
	return true;
}

bool QtLockedFile::unlock()
{
	if (!isOpen()) {
		qWarning("QtLockedFile::unlock(): file is not opened");
		return false;
	}

	if (!isLocked())
		return true;

	if (m_lock_mode == ReadLock) {
		ReleaseMutex(rmutex);
		CloseHandle(rmutex);
		rmutex = 0;
	}
	else {
		for (Qt::HANDLE mutex : rmutexes) {
			ReleaseMutex(mutex);
			CloseHandle(mutex);
		}
		rmutexes.clear();
		ReleaseMutex(wmutex);
	}

	m_lock_mode = QtLockedFile::NoLock;
	return true;
}

QtLockedFile::~QtLockedFile()
{
	if (isOpen())
		unlock();
	if (wmutex)
		CloseHandle(wmutex);
}
