/***************************************************************************
 *   Copyright (C) 2005 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 <klocale.h>
#include <kmessagebox.h>

#include "logListItem.h"
#include "ksystemlogConfig.h"
#include "logLine.h"
#include "view.h"
#include "parsingHelper.h"

#include "defaultReader.h"


DefaultReader::DefaultReader(TQObject *parent, const char *name) :
	Reader(parent, name),
	buffers(NULL)
	{
	

}


DefaultReader::~DefaultReader() {
	delete buffers;
}

void DefaultReader::readLog() {
	//Each file in the Log file list are watched with the following method
	watchLogFiles();

	if (buffers!=NULL)
		delete buffers;

	if (logManager->getGroupBy()==NO_GROUP_BY)
		buffers=new LogLineList();
	else {
		buffers=new LogLineTree(logManager->getColumns(), logManager->getGroupBy(), logManager->getGroupByColumn());
	}
	

	LogFiles& files=logManager->getLogFiles();
	
	//Inform connected TQObject that the reading has begun
	emit readingBegin();
	
	//Read each file of the list, and place their lines in the buffer
	int i=files.count()-1;
	LogFile* logFile;
	while(i>=0) {
		logFile=files[i];
		kdDebug() << "Reading file " << logFile->url.path() << endl;
		this->readFile(logFile);

		i--;
	}
	
	//Synchronize the buffer once we have fill it
	logManager->synchronize(buffers);

	//Says to the buffer that we have read all files, and that
	//the next adding will come from logChanged method
	if (!buffers->isEmpty())
		buffers->setFirstReadPerformed(true);

	//Inform connected TQObject that the reading is now finished
	emit readingEnd();

	//Emit a signal which informs connected slots that there are new lines
	emit logUpdated(logManager->getView()->getLogList()->childCount());
}

void DefaultReader::readFile(LogFile* logFile) {
	TQString message=i18n("Opening file '%1'...").arg(logFile->url.path());
	emit statusbarChanged(message);

	//Inform connected TQObject that we are now reading the "index" file
	emit readingFile(logManager->getLogFiles().count() - logManager->getLogFiles().findIndex(logFile));
	
	
	//We initialize these values from configuration to be used by the insert methods
	tmpDeleteDuplicate=KSystemLogConfig::deleteDuplicatedLines();
	tmpDeleteProcessId=KSystemLogConfig::deleteProcessIdentifier();
	tmpMaxLines=KSystemLogConfig::maxLines();
	tmpMaxCharacters=KSystemLogConfig::maxReadCharacters();
	
	//Open the file
	TQFile* file=this->openFile(logFile->url.path());
	
	//If an error occurs, end this method
	if (file==NULL) {
		return;
	}
	
	TQString buffer;
	TQString filePath=logFile->url.path();
		

	//Insert the content of the file in a string list
	TQStringList* rawBuffer=getRawBuffer(file);

	//If there is no line
	if (rawBuffer->size()==0) {
		TQString message=i18n("No log line in '%1'.").arg(logFile->url.path());
		emit statusbarChanged(message);
		
		delete rawBuffer;
		return;
	}

	kdDebug() << "Testing each line..." << endl;

	TQStringList::iterator it;
	it=rawBuffer->end();
	it--;

	//Calculate how many lines we will read
	int size=rawBuffer->size();
	int stop;
	if (size>tmpMaxLines)
		stop=size-tmpMaxLines;
	else
		stop=0;
	

	//Test each line of the raw buffer
	int i=size-1;
	
	int progress=0;
	
	int progressTotal=size-1 - stop;
	int each=progressTotal / 100;
	if (each==0)
		each=progressTotal;
	
	
	while(i>=stop) {
		buffer=*it;
		
		if (insertOrReplaceLine(buffer, logFile)==false)
			break;

		it--;
		i--;
		
		if (i>=0 && i%each==0) {
			emit openingProgressed(progress++);
		}
	}

	delete rawBuffer;
	
	logFile->lastFileSize=file->size();
	
	// Close the file
	this->closeFile(file);
	
	message=i18n("Log file '%1' loaded successfully.").arg(logFile->url.path());
	emit statusbarChanged(message);

}



void DefaultReader::logChanged(LogFile* logFile) {
	kdDebug() << "SortedReader : File has been modified !" << endl;

	//We initialize these values from configuration to be used by the insert methods
	tmpDeleteDuplicate=KSystemLogConfig::deleteDuplicatedLines();
	tmpDeleteProcessId=KSystemLogConfig::deleteProcessIdentifier();
	tmpMaxLines=KSystemLogConfig::maxLines();
	tmpMaxCharacters=KSystemLogConfig::maxReadCharacters();
	
	TQString buffer;

	TQString filePath=logFile->url.path();
	TQFile* file=this->openFile(filePath);
	
	if (file==NULL)
		return;


	//If there are new lines in the file, insert only them
	if (logFile->lastFileSize <= (int) file->size()) {
		//Place the cursor to the last line opened
		file->at(logFile->lastFileSize);
		
		kdDebug() << "Retrieving a part of the file..." << endl;
		
		//Get the maximum number of line read from LogManager
		int maxLines=KSystemLogConfig::maxLines();
	
		TQString buffer;
		TQStringList* rawBuffer=getRawBuffer(file);

		//If there is no line
		if (rawBuffer->size()==0) {
			delete rawBuffer;
			return;
		}
	
		kdDebug() << "Testing each line..." << endl;
	
		TQStringList::iterator it;
		it=rawBuffer->end();
		it--;
	
		//Calculate how many lines we will read
		int size=rawBuffer->size();
		int stop=0;
		if (size>maxLines)
			stop=size-maxLines;

		//Test each line of the raw buffer
		int i=size-1;
		while(i>=stop) {
			buffer=*it;
			
			if (insertOrReplaceLine(buffer, logFile)==false)
				break;

			it--;
			i--;
		}
	
		kdDebug() << "Total read lines : " << (size-1-i) << endl;

		delete rawBuffer;
		
	
	}
	//Else reread all lines, clear log list
	else {
		buffers->clear();

		file->close();
		
		this->readFile(logFile);
	}

	int newLineCount=buffers->getNewLineCount();

	logManager->synchronize(buffers);
	
	//Get the size file for the next calculation
	logFile->lastFileSize=file->size();
	
	// Close the file
	this->closeFile(file);
	
	TQString message;
	message=i18n("Log file '%1' has changed.").arg(logFile->url.path());
	emit statusbarChanged(message);
	
	//Emit a signal which informs connected slots that there are new lines
	emit logUpdated(newLineCount);
}




bool DefaultReader::insertLine(TQString& buffer, LogFile* originalFile) {
	LogLine* line=this->parseMessage(buffer, originalFile);
	
	//Do not insert the line if it NULL
	//TODO Maybe return true (because we don't want to stop the reading for one dirty line)
	if (line==NULL)
		return(false);
	
	
	//If the Delete Duplicated Line is checked, we first test that the line is really new
	if (tmpDeleteDuplicate==true) {
		if (buffers->lineAlreadyExists(line)==true)
			return(true);
	}

	//If the line is newer, it can be inserted
	if (buffers->isNewer(line)==true) {

		if (buffers->getItemCount()<tmpMaxLines) {
			buffers->insert(line);
		}
		else {
			return(false);
		}
	}
	//If the line is older, then inserts it only if there is still space in the buffer
	else if (buffers->getItemCount()<tmpMaxLines) {
		buffers->insert(line);
	}

	return(true);
}


bool DefaultReader::insertOrReplaceLine(TQString& buffer, LogFile* originalFile) {
	LogLine* line=this->parseMessage(buffer, originalFile);
	
	//Do not insert the line if it NULL
	//TODO Maybe return true (because we don't want to stop the reading for one dirty line)
	if (line==NULL)
		return(false);

	//If the Delete Duplicated Line is checked, we first test that the line is really new
	if (tmpDeleteDuplicate==true) {
		if (buffers->lineAlreadyExists(line)==true) {
			return(true);
		}
	}

	
	//If the line is newer, it can be inserted	
	if (buffers->isNewer(line)==true) {

		if (buffers->getItemCount()<tmpMaxLines) {
			buffers->insert(line);
			return(true);
		}
		else {
			buffers->removeOldestLine();
			buffers->insert(line);
			return(true);
		}
	}
	//If the line is older, then inserts it only if there is still space in the buffer
	else if (buffers->getItemCount()<tmpMaxLines) {
		buffers->insert(line);
		return(true);
	}

	return(false);
}

/**
 * TODO Improve speed of this method (with KRegExp class for example)
 */
LogLine* DefaultReader::parseMessage(TQString& logLine, LogFile* originalFile) {
	//kdDebug() << "Please don't use parseMessage() from SortedReader class" << endl;
	
	int year=TQDate::currentDate().year();
	
	//Month number
	TQString month;
	month=logLine.left(3);
	
	logLine=logLine.remove(0, 4);
	int monthNum=ParsingHelper::parseMonthNumber(month);
	
	//Day number
	TQString day;
	
	day=logLine.left(2);
	int dayNum=day.toInt();
	
	logLine=logLine.remove(0, 3);
	
	TQDate date(year, monthNum, dayNum);
	if (!date.isValid()) {
		kdDebug() << "Malformed date" << endl;
		date=TQDate::currentDate();
	}

	//Time
	TQString stringTime;
	stringTime=logLine.left(8);
	int h=stringTime.left(2).toInt();
	stringTime.remove(0, 3);
	int m=stringTime.left(2).toInt();
	stringTime.remove(0, 3);
	int s=stringTime.left(2).toInt();
	stringTime.remove(0, 3);
	
	TQTime time(h, m, s);
	if (!time.isValid()) {
		kdDebug() << "Malformed time" << endl;
		time=TQTime::currentTime();
	}
	
	//TQStringList
	logLine=logLine.remove(0, 9);
	
	int nextSpace;
	nextSpace=logLine.find(' ');
	
	//Host name
	TQString hostname;
	
	hostname=logLine.left(nextSpace);
	
	logLine=logLine.remove(0, nextSpace+1);
	
	
	TQString process;
	TQString message;
	
	//Process name	
	nextSpace=logLine.find(':');
	if (nextSpace!=-1) {
		process=logLine.left(nextSpace);
		
		//If the delete process identifier option is enabled
		if (tmpDeleteProcessId==true) {
			int squareBracket=process.find('[');
			
			//If we find a bracket, we remove the useless part
			if (squareBracket!=-1) {
				process=process.left(squareBracket);
			}
			
		}
		logLine=logLine.remove(0, nextSpace+1);
		
		message=logLine.remove(0, 1);
	}
	//If we can't find any ':' character, it means that this line is a 
	//internal message of syslogd
	else {
		process=i18n("none");
		
		message=logLine;
	}
	

	
	
	
	TQStringList list;
	list.append(hostname);
	list.append(process);
	list.append(message);
	
	/*
	 *TODO Try to avoid that 2 columns with the same timestamps (but not the
	 *same appearance order are not sorted correctly
	 */
	/*
	LogLine* lastLine=buffers->lastLineInserted();
	if (lastLine!=NULL) {
		if (lastLine->getTime().date()==date && lastLine->getTime().time()==time)
			time=time.addMSecs(1);
	}
	*/
	
	TQString filePath=originalFile->url.path();
	LogLine* logLineObject=new LogLine(date, time, list, filePath, originalFile->level, Globals::noMode->id);
	
	return(logLineObject);
}


TQStringList* DefaultReader::getRawBuffer(TQFile* file) {
	kdDebug() << "Retrieving the raw buffer..." << endl;

	TQString buffer;
	TQString tmpBuffer;
	TQStringList* rawBuffer=new TQStringList();
	
	int res;

	//Insert the content of the file in a string list
	while(!file->atEnd()) {
	
		//Read the first MaxCharactersRead characters
		res=file->readLine(buffer, tmpMaxCharacters);
		
		//Ignore the rest of the line
		while(res==tmpMaxCharacters)
			file->readLine(tmpBuffer, tmpMaxCharacters);
		
		//Push the new buffer to the list
		rawBuffer->push_back(buffer);
	}

	kdDebug() << "Raw buffer retrieved." << endl;
	
	return(rawBuffer);
}

#include "defaultReader.moc"
