/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "settingsthread.h"

#ifndef Q_OS_WIN32
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#include <QMutexLocker>
#include <QFile>
#include <QTimer>
#include <QtDebug>
#include <util/sll/qtutil.h>
#include <util/sll/util.h>
#include <util/sys/fdguard.h>
#include "basesettingsmanager.h"

namespace LeechCraft
{
	SettingsThread::~SettingsThread ()
	{
		QMutexLocker l { &Mutex_ };
		if (!Pendings_.isEmpty ())
			qWarning () << Q_FUNC_INFO
					<< "there are pending settings to be saved, unfortunately they will be lost :(";
	}

	void SettingsThread::Save (Util::BaseSettingsManager *bsm, QString name, QVariant value)
	{
		QMutexLocker l { &Mutex_ };

		if (Pendings_.isEmpty ())
			QTimer::singleShot (0, this, SLOT (saveScheduled ()));
		Pendings_ [bsm].push_back ({ name, value });
	}

	namespace
	{
		class FlushRAII
		{
			Util::DefaultScopeGuard SyncGuard_;
		public:
			FlushRAII (const QString& name)
			{
#ifndef Q_OS_WIN32
				const Util::FDGuard fd { name.toUtf8 ().constData (), O_WRONLY | O_APPEND };

				struct flock fl;
				fl.l_whence = SEEK_SET;
				fl.l_start = 0;
				fl.l_len = 0;
				fl.l_type = F_WRLCK;
				if (fcntl (fd, F_SETLKW, &fl))
					throw std::runtime_error
					{
						QString { "cannot lock the settings file %1: %2 (%3)" }
								.arg (name)
								.arg (strerror (errno))
								.arg (errno)
								.toStdString ()
					};

				const auto& backup = name + ".bak";
#if 0
				if (rename (name.toUtf8 ().constData (), backup.toUtf8 ().constData ()))
					throw std::runtime_error
					{
						QString { "cannot rename %1 to %2: %3 (%4)" }
								.arg (name)
								.arg (backup)
								.arg (strerror (errno))
								.arg (errno)
								.toStdString ()
					};
#else
				QFile nameFile { name };
				if (!nameFile.copy (backup))
					throw std::runtime_error
					{
						QString { "cannot copy %1 to %2: %3 (%4)" }
								.arg (name)
								.arg (backup)
								.arg (nameFile.errorString ())
								.arg (nameFile.error ())
								.toStdString ()
					};
#endif

				SyncGuard_ = Util::MakeScopeGuard ([name, backup]
						{
							const Util::FDGuard fd { name.toUtf8 ().constData (), O_WRONLY | O_APPEND };
							if (!fd)
							{
								qWarning () << Q_FUNC_INFO
										<< "cannot open(2) settings file"
										<< name
										<< "; error:"
										<< strerror (errno);
								return;
							}

							if (fsync (fd))
							{
								qWarning () << Q_FUNC_INFO
										<< "cannot fsync(2) backup file"
										<< name
										<< "; error:"
										<< strerror (errno);
								return;
							}

							if (!QFile::remove (backup))
								qWarning () << Q_FUNC_INFO
										<< "oops! cannot remove backup";
						});
#endif
			}
		};
	}

	void SettingsThread::saveScheduled ()
	{
		decltype (Pendings_) pendings;

		{
			QMutexLocker l { &Mutex_ };
			using std::swap;
			swap (pendings, Pendings_);
		}

		for (const auto& pair : Util::Stlize (pendings))
		{
			const auto& s = pair.first->GetSettings ();
			for (const auto& p : pair.second)
				s->setValue (p.first, p.second);
		}
	}
}
