/***************************************************************************
 *   KSystemLog, a system log viewer tool                                  *
 *   Copyright (C) 2007 by Nicolas Ternisien                               *
 *   nicolas.ternisien@gmail.com                                           *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 ***************************************************************************/

#include "localLogFileReader.h"

#include <QMutex>
#include <QBuffer>
#include <QFile>
#include <QTextStream>

#include <kdirwatch.h>
#include <klocale.h>
#include <kdecompressor.h>
#include <kmimetype.h>
#include <kurl.h>

#include "logFileReader.h"
#include "logFileReaderPrivate.h"
#include "logging.h"


class LocalLogFileReaderPrivate : public LogFileReaderPrivate {

public:

	KDirWatch* watch;

	long previousFilePosition;

	/**
	 * Mutex avoiding multiple logFileModified() calls
	 */
	QMutex insertionLocking;

};


LocalLogFileReader::LocalLogFileReader(const LogFile& logFile) :
	LogFileReader(*new LocalLogFileReaderPrivate(), logFile) {

	init();
}


LocalLogFileReader::LocalLogFileReader(LocalLogFileReaderPrivate& dd, const LogFile& logFile) :
	LogFileReader(dd, logFile) {

	init();
}

LocalLogFileReader::~LocalLogFileReader() {
	Q_D(LocalLogFileReader);

	//Delete the watching object
	delete d->watch;

	//d pointer is deleted by the parent class
}

void LocalLogFileReader::init() {
	Q_D(LocalLogFileReader);

	d->watch=new KDirWatch();
	connect(d->watch, SIGNAL(dirty(QString)), this, SLOT(logFileModified()));

	//Init current file position
	d->previousFilePosition = 0;

	logDebug() << "Reading local file " << d->logFile.url().path();

}

void LocalLogFileReader::watchFile(bool enable) {
	Q_D(LocalLogFileReader);

	if (enable == true) {
		logDebug() << "Monitoring file : " << d->logFile.url().path();

		if (d->watch->contains(d->logFile.url().path()) == false) {
			d->watch->addFile(d->logFile.url().path());
		}

		//Reinit current file position
		d->previousFilePosition = 0;

		//If we enable the watching, then we first try to see if new lines have appeared
		logFileModified();
	}
	else {
		d->watch->removeFile(d->logFile.url().path());
	}
}

QIODevice* LocalLogFileReader::open() {
	Q_D(LocalLogFileReader);

	if (d->logFile.url().isValid()==false) {
		QString message(i18n("This file is not valid. Please adjust it in the settings of KSystemLog."));
		emit errorOccured(i18n("File Does Not Exist"), message);
		emit statusBarChanged(message);
	}

	QString mimeType = KMimeType::findByFileContent( d->logFile.url().path() )->name();

	logDebug() << d->logFile.url().path() << " : " << mimeType;
	QIODevice* inputDevice;

	//Try to see if this file exists
	QFile* file = new QFile(d->logFile.url().path());
	//If the file does not exist
	if (! file->exists()) {
		QString message(i18n("The file '%1' does not exist.", d->logFile.url().path()));
		emit errorOccured(i18n("File Does Not Exist"), message);
		emit statusBarChanged(message);
		delete file;
		return NULL;
	}

	//Plain text file : we use a QFile object
	if (mimeType == QLatin1String( "text/plain" ) || mimeType == QLatin1String( "application/octet-stream" )) {
		logDebug() << "Using QFile input device";

		inputDevice = file;
	}
	//Compressed file : we use the KFilterDev helper
	else {
		logDebug() << "Using KFilterDev input device";

		const QString logFilePath = d->logFile.url().path();
		KDecompressor kdecompressor;
		if (!kdecompressor.setType(KDecompressor::typeForMime(mimeType))
		    && !kdecompressor.setType(KDecompressor::typeForFile(logFilePath))) {
			QString message(i18n("Compressed file format is not supported '%1'.", logFilePath));
			emit errorOccured(i18n("Unable to open Compressed File"), message);
			emit statusBarChanged(message);
			return NULL;
		}

		QFile logFileDevice(logFilePath);
		if (!logFileDevice.open(QFile::ReadOnly)) {
			QString message(i18n("Unable to open compressed file '%1': %2.", logFilePath, logFileDevice.errorString()));
			emit errorOccured(i18n("Unable to open Compressed File"), message);
			emit statusBarChanged(message);
			return NULL;
		}

		if (kdecompressor.process(logFileDevice.readAll())) {
			QString message(i18n("Unable to uncompress '%1': '%2'.", logFilePath, kdecompressor.errorString()));
			emit errorOccured(i18n("Unable to Uncompress File"), message);
			emit statusBarChanged(message);
			return NULL;
		}

		inputDevice = new QBuffer();
		qobject_cast<QBuffer*>(inputDevice)->setData(kdecompressor.result());
	}

	if ( ! inputDevice->open( QIODevice::ReadOnly ) ) {
		QString message(i18n("You do not have sufficient permissions to read '%1'.", d->logFile.url().path()));
		emit errorOccured(i18n("Insufficient Permissions"), message);
		emit statusBarChanged(message);
		delete inputDevice;
		return NULL;
	}

	return inputDevice;
}

void LocalLogFileReader::close(QIODevice* inputDevice) {
	inputDevice->close();
	delete inputDevice;
}

QStringList LocalLogFileReader::readContent(QIODevice* inputDevice) {
	logDebug() << "Retrieving raw buffer...";

	Q_D(LocalLogFileReader);

	QStringList rawBuffer;

	QTextStream inputStream(inputDevice);
	while (inputStream.atEnd() == false) {
		rawBuffer.append(inputStream.readLine());
	}

	logDebug() << "Raw buffer retrieved.";

	//Get the size file for the next calculation
	d->previousFilePosition = inputDevice->size();
	logDebug() << "New file position : " << d->previousFilePosition << " (" << d->logFile.url().path() << ")";

	return rawBuffer;
}

void LocalLogFileReader::logFileModified() {
	Q_D(LocalLogFileReader);

	logDebug() << "Locking log file modification...";
	if (d->insertionLocking.tryLock() == false) {
		logDebug() << "Log file modification already detected.";
		return;
	}

	QIODevice* inputDevice = open();
	if (inputDevice == NULL) {
		logError() << "Could not open file " << d->logFile.url().path();
		return;
	}

	//If there are new lines in the file, insert only them or this is the first time we read this file
	if (d->previousFilePosition!=0 && d->previousFilePosition <= inputDevice->size()) {
		logDebug() << "Reading from position " << d->previousFilePosition << " (" << d->logFile.url().path() << ")";

		if (inputDevice->isSequential()) {
			logError() << "The file current position could not be modified";
		}
		else {
			//Place the cursor to the last line opened
			inputDevice->seek(d->previousFilePosition);
		}

		logDebug() << "Retrieving a part of the file...";

		emit contentChanged(this, Analyzer::UpdatingRead, readContent(inputDevice));

	}
	//Else reread all lines, clear log list
	else {
		logDebug() << "New file or file truncated. (Re-)Loading log file";

		emit contentChanged(this, Analyzer::FullRead, readContent(inputDevice));

	}

	close(inputDevice);

	logDebug() << "Unlocking log file modification...";
	d->insertionLocking.unlock();
}

#include "moc_localLogFileReader.cpp"
