//presume bug linked as multple paralelle inode to resume after "overwrite"
//then do overwrite node function to not re-set the file name

#include "TransferThread.h"

#ifdef Q_CC_GNU
//this next header is needed to change file time/date under gcc
#include <utime.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#endif

#ifndef Q_OS_UNIX
#include <windows.h>
#endif

TransferThread::TransferThread()
{
	start();
	moveToThread(this);
	needSkip		= false;
	transfer_stat			= TransferStat_Idle;
	stopIt			= false;
	fileExistsAction	= FileExists_NotSet;
	alwaysDoFileExistsAction= FileExists_NotSet;
	readError		= false;
	writeError		= false;
	rsync			= false;
	this->mkpathTransfer	= mkpathTransfer;
	readThread.setWriteThread(&writeThread);

	connect(&clockForTheCopySpeed,	SIGNAL(timeout()),			this,	SLOT(timeOfTheBlockCopyFinished()));
	maxTime=QDateTime(QDate(ULTRACOPIER_PLUGIN_MINIMALYEAR,1,1));
}

TransferThread::~TransferThread()
{
	exit();
	disconnect(&readThread);
	disconnect(&writeThread);
	disconnect(this);
	wait();
}

void TransferThread::run()
{
	//ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start: "+QString::number((qint64)QThread::currentThreadId()));
	transfer_stat			= TransferStat_Idle;
	stopIt			= false;
	fileExistsAction	= FileExists_NotSet;
	alwaysDoFileExistsAction= FileExists_NotSet;
	//the error push
	connect(&readThread,SIGNAL(error()),			this,					SLOT(getReadError()),		Qt::QueuedConnection);
	connect(&writeThread,SIGNAL(error()),			this,					SLOT(getWriteError()),		Qt::QueuedConnection);
	//the thread change operation
	connect(this,SIGNAL(internalStartPreOperation()),	this,					SLOT(preOperation()),		Qt::QueuedConnection);
	connect(this,SIGNAL(internalStartPostOperation()),	this,					SLOT(postOperation()),		Qt::QueuedConnection);
        //the state change operation
	//connect(&readThread,SIGNAL(readIsStopped()),		&readThread,				SLOT(postOperation()),		Qt::QueuedConnection);//commented to do the checksum
	connect(&readThread,SIGNAL(opened()),			this,					SLOT(readIsReady()),		Qt::QueuedConnection);
	connect(&writeThread,SIGNAL(opened()),			this,					SLOT(writeIsReady()),		Qt::QueuedConnection);
	connect(&readThread,SIGNAL(readIsStopped()),		this,					SLOT(readIsStopped()),		Qt::QueuedConnection);
	connect(&writeThread,SIGNAL(writeIsStopped()),		this,					SLOT(writeIsStopped()),		Qt::QueuedConnection);
	connect(&readThread,SIGNAL(readIsStopped()),		&writeThread,				SLOT(endIsDetected()),		Qt::QueuedConnection);
	//connect(&writeThread,SIGNAL(writeIsStopped()),		&writeThread,				SLOT(postOperation()),		Qt::QueuedConnection);//commented to do the checksum
	connect(&readThread,SIGNAL(closed()),			this,					SLOT(readIsClosed()),		Qt::QueuedConnection);
	connect(&writeThread,SIGNAL(closed()),			this,					SLOT(writeIsClosed()),		Qt::QueuedConnection);
	connect(&writeThread,SIGNAL(reopened()),		this,					SLOT(writeThreadIsReopened()),	Qt::QueuedConnection);
	connect(&readThread,SIGNAL(checksumFinish(QByteArray)),	this,					SLOT(readChecksumFinish(QByteArray)),	Qt::QueuedConnection);
	connect(&writeThread,SIGNAL(checksumFinish(QByteArray)),this,					SLOT(writeChecksumFinish(QByteArray)),	Qt::QueuedConnection);
        //error management
	connect(&readThread,SIGNAL(isSeekToZeroAndWait()),	this,					SLOT(readThreadIsSeekToZeroAndWait()),	Qt::QueuedConnection);
	connect(&readThread,SIGNAL(resumeAfterErrorByRestartAtTheLastPosition()),	this,		SLOT(readThreadResumeAfterError()),	Qt::QueuedConnection);
	connect(&readThread,SIGNAL(resumeAfterErrorByRestartAll()),                     &writeThread,	SLOT(flushAndSeekToZero()),		Qt::QueuedConnection);
	connect(&writeThread,SIGNAL(flushedAndSeekedToZero()),                          this,           SLOT(readThreadResumeAfterError()),	Qt::QueuedConnection);
	connect(this,SIGNAL(internalTryStartTheTransfer()),	this,					SLOT(internalStartTheTransfer()),	Qt::QueuedConnection);

	#ifdef ULTRACOPIER_PLUGIN_DEBUG
	connect(&readThread,SIGNAL(debugInformation(DebugLevel,QString,QString,QString,int)),this,SIGNAL(debugInformation(DebugLevel,QString,QString,QString,int)),Qt::QueuedConnection);
	connect(&writeThread,SIGNAL(debugInformation(DebugLevel,QString,QString,QString,int)),this,SIGNAL(debugInformation(DebugLevel,QString,QString,QString,int)),Qt::QueuedConnection);
	#endif

	exec();
}

TransferStat TransferThread::getStat()
{
	return transfer_stat;
}

void TransferThread::startTheTransfer()
{
	emit internalTryStartTheTransfer();
}

void TransferThread::internalStartTheTransfer()
{
	if(transfer_stat==TransferStat_Idle)
	{
		if(mode!=Move)
		{
			/// \bug can pass here because in case of direct move on same media, it return to idle stat directly
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] can't start transfert at idle");
		}
		return;
	}
	if(transfer_stat==TransferStat_PostOperation)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] can't start transfert at PostOperation");
		return;
	}
	if(transfer_stat==TransferStat_Transfer)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] can't start transfert at Transfer");
		return;
	}
	if(canStartTransfer)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] canStartTransfer is already set to true");
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] check how start the transfer");
	canStartTransfer=true;
	if(readIsReadyVariable && writeIsReadyVariable)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start directly the transfer");
		ifCanStartTransfer();
	}
	else
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start the transfer as delayed");
}

void TransferThread::setFiles(const QString &source,const qint64 &size,const QString &destination,const CopyMode &mode)
{
	if(transfer_stat!=TransferStat_Idle)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] already used, source: "+source+", destination: "+destination);
		return;
	}
	//to prevent multiple file alocation into ListThread::doNewActions_inode_manipulation()
	transfer_stat			= TransferStat_PreOperation;
	//emit pushStat(stat,transferId);
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start, source: "+source+", destination: "+destination);
	this->source			= source;
	this->destination		= destination;
	this->mode			= mode;
	this->size			= size;
	fileExistsAction		= FileExists_NotSet;
	canStartTransfer		= false;
	sended_state_preOperationStopped= false;
	canBeMovedDirectlyVariable	= false;
	fileContentError		= false;
	resetExtraVariable();
	emit internalStartPreOperation();
}

void TransferThread::setFileExistsAction(const FileExistsAction &action)
{
	if(transfer_stat!=TransferStat_PreOperation)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] already used, source: "+source+", destination: "+destination);
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] action: "+QString::number(action));
	if(action!=FileExists_Rename)
		fileExistsAction	= action;
	else
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] rename at the wrong part, source: "+source+", destination: "+destination);
	if(action==FileExists_Skip)
	{
		skip();
		return;
	}
	resetExtraVariable();
	emit internalStartPreOperation();
}

void TransferThread::setFileRename(const QString &nameForRename)
{
	if(transfer_stat!=TransferStat_PreOperation)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] already used, source: "+source+", destination: "+destination);
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] nameForRename: "+nameForRename);
	destinationInfo.setFile(destination);
	destination=destinationInfo.absolutePath();
	destination+=QDir::separator()+nameForRename;
	destinationInfo.setFile(destination);
	fileExistsAction	= FileExists_NotSet;
	resetExtraVariable();
	emit internalStartPreOperation();
}

void TransferThread::setAlwaysFileExistsAction(const FileExistsAction &action)
{
	//ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] action to do always: "+QString::number(action));
	alwaysDoFileExistsAction=action;
}

void TransferThread::resetExtraVariable()
{
	sended_state_readStopped	= false;
	sended_state_writeStopped	= false;
	writeError			= false;
	readError			= false;
	readIsReadyVariable		= false;
	writeIsReadyVariable		= false;
	readIsFinishVariable		= false;
	writeIsFinishVariable		= false;
	readIsClosedVariable		= false;
	writeIsClosedVariable		= false;
	needSkip			= false;
	retry				= false;
	readIsOpenVariable		= false;
	writeIsOpenVariable		= false;
}

void TransferThread::preOperation()
{
	if(transfer_stat!=TransferStat_PreOperation)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] already used, source: "+source+", destination: "+destination);
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	needRemove=false;
	sourceInfo.setFile(source);
	destinationInfo.setFile(destination);
	if(isSame())
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] is same"+source);
		return;
	}
	if(destinationExists())
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] destination exists: "+source);
		return;
	}
	if(canBeMovedDirectly())
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] need moved directly: "+source);
		canBeMovedDirectlyVariable=true;
		readThread.fakeOpen();
		writeThread.fakeOpen();
		return;
	}
	tryOpen();
}

void TransferThread::tryOpen()
{
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start source and destination: "+source+" and "+destination);
	if(!readIsOpenVariable)
	{
		readError=false;
		readThread.open(source,mode);
	}
	if(!writeIsOpenVariable)
	{
		writeError=false;
		writeThread.open(destination,size,osBuffer && (!osBufferLimited || (osBufferLimited && size<osBufferLimit)));
	}
}

bool TransferThread::isSame()
{
	//check if source and destination is not the same
	if(sourceInfo==destinationInfo)
	{
		emit fileAlreadyExists(sourceInfo,destinationInfo,true);
		return true;
	}
	return false;
}

bool TransferThread::destinationExists()
{
	//check if destination exists
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] overwrite: "+QString::number(fileExistsAction)+", always action: "+QString::number(alwaysDoFileExistsAction));
	if(alwaysDoFileExistsAction==FileExists_Overwrite || rsync || readError || writeError)
		return false;
	if(destinationInfo.exists())
	{
		if(fileExistsAction==FileExists_NotSet && alwaysDoFileExistsAction==FileExists_Skip)
		{
			transfer_stat=TransferStat_Idle;
			emit postOperationStopped();
			return true;
		}
		if(alwaysDoFileExistsAction==FileExists_Rename)
		{
			QString absolutePath=destinationInfo.absolutePath();
			QString fileName=destinationInfo.fileName();
			QString suffix="";
			QString newFileName;
			//resolv the suffix
			if(fileName.contains(QRegExp("^(.*)(\\.[a-z0-9]+)$")))
			{
				suffix=fileName;
				suffix.replace(QRegExp("^(.*)(\\.[a-z0-9]+)$"),"\\2");
				fileName.replace(QRegExp("^(.*)(\\.[a-z0-9]+)$"),"\\1");
			}
			//resolv the new name
			int num=1;
			do
			{
				if(num==1)
				{
					if(firstRenamingRule=="")
						newFileName=tr("%1 - copy").arg(fileName);
					else
					{
						newFileName=firstRenamingRule;
						newFileName.replace("%name%",fileName);
					}
				}
				else
				{
					if(otherRenamingRule=="")
						newFileName=tr("%1 - copy (%2)").arg(fileName).arg(num);
					else
					{
						newFileName=otherRenamingRule;
						newFileName.replace("%name%",fileName);
						newFileName.replace("%number%",QString::number(num));
					}
				}
				destination=absolutePath+QDir::separator()+newFileName+suffix;
				destinationInfo.setFile(destination);
				num++;
			}
			while(destinationInfo.exists());
			return false;
		}
		if(fileExistsAction==FileExists_OverwriteIfNewer || (fileExistsAction==FileExists_NotSet && alwaysDoFileExistsAction==FileExists_OverwriteIfNewer))
		{
			if(destinationInfo.lastModified()<sourceInfo.lastModified())
				return false;
			else
			{
				transfer_stat=TransferStat_Idle;
				emit postOperationStopped();
				return true;
			}
		}
		if(fileExistsAction==FileExists_OverwriteIfNotSameModificationDate || (fileExistsAction==FileExists_NotSet && alwaysDoFileExistsAction==FileExists_OverwriteIfNotSameModificationDate))
		{
			if(destinationInfo.lastModified()!=sourceInfo.lastModified())
				return false;
			else
			{
				transfer_stat=TransferStat_Idle;
				emit postOperationStopped();
				return true;
			}
		}
		if(fileExistsAction==FileExists_NotSet)
		{
			emit fileAlreadyExists(sourceInfo,destinationInfo,false);
			return true;
		}
	}
	return false;
}

void TransferThread::tryMoveDirectly()
{
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start the system move");

	//move if on same mount point
	QFile sourceFile(sourceInfo.absoluteFilePath());
	QFile destinationFile(destinationInfo.absoluteFilePath());
	if(destinationFile.exists() && !destinationFile.remove())
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] "+destinationFile.fileName()+", error: "+destinationFile.errorString());
		emit errorOnFile(destinationInfo,destinationFile.errorString());
		return;
	}
	QDir dir(destinationInfo.absolutePath());
	{
		mkpathTransfer->acquire();
		if(!dir.exists())
			dir.mkpath(destinationInfo.absolutePath());
		mkpathTransfer->release();
	}
	if(!sourceFile.rename(destinationFile.fileName()))
	{
		if(sourceFile.exists())
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] "+QString("file not not exists %1: %2, error: %3").arg(sourceFile.fileName()).arg(destinationFile.fileName()).arg(sourceFile.errorString()));
		else if(!dir.exists())
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] "+QString("destination folder not exists %1: %2, error: %3").arg(sourceFile.fileName()).arg(destinationFile.fileName()).arg(sourceFile.errorString()));
		else
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] "+QString("unable to do real move %1: %2, error: %3").arg(sourceFile.fileName()).arg(destinationFile.fileName()).arg(sourceFile.errorString()));
		emit errorOnFile(sourceFile,sourceFile.errorString());
		return;
	}
	readThread.fakeReadIsStarted();
	writeThread.fakeWriteIsStarted();
	readThread.fakeReadIsStopped();
	writeThread.fakeWriteIsStopped();
}

bool TransferThread::canBeMovedDirectly()
{
	//move if on same mount point
	#if defined (Q_OS_LINUX) || defined (Q_OS_WIN32)
	if(mode!=Move)
		return false;
	if(mountSysPoint.size()==0)
		return false;
	if(getDrive(destinationInfo.fileName())==getDrive(sourceInfo.fileName()))
		return true;
	#endif
	return false;
}

void TransferThread::readIsReady()
{
	if(readIsReadyVariable)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] double event dropped");
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	readIsReadyVariable=true;
	readIsOpenVariable=true;
	readIsClosedVariable=false;
	ifCanStartTransfer();
}

void TransferThread::ifCanStartTransfer()
{
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] readIsReadyVariable: "+QString::number(readIsReadyVariable)+", writeIsReadyVariable: "+QString::number(writeIsReadyVariable));
	if(readIsReadyVariable && writeIsReadyVariable)
	{
		transfer_stat=TransferStat_WaitForTheTransfer;
		sended_state_readStopped	= false;
		sended_state_writeStopped	= false;
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] stat=WaitForTheTransfer");
		if(!sended_state_preOperationStopped)
		{
			sended_state_preOperationStopped=true;
			emit preOperationStopped();
		}
		if(canStartTransfer)
		{
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] stat=Transfer");
			transfer_stat=TransferStat_Transfer;
			if(!canBeMovedDirectlyVariable)
			{
				needRemove=true;
				readThread.startRead();
			}
			else
				tryMoveDirectly();
			emit pushStat(transfer_stat,transferId);
		}
		//else
			//emit pushStat(stat,transferId);
	}
}

void TransferThread::writeIsReady()
{
	if(writeIsReadyVariable)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] double event dropped");
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	writeIsReadyVariable=true;
	writeIsOpenVariable=true;
	writeIsClosedVariable=false;
	ifCanStartTransfer();
}


//set the copy info and options before runing
void TransferThread::setRightTransfer(const bool doRightTransfer)
{
	this->doRightTransfer=doRightTransfer;
}

//set keep date
void TransferThread::setKeepDate(const bool keepDate)
{
	this->keepDate=keepDate;
}

//set the current max speed in KB/s
void TransferThread::setMaxSpeed(int maxSpeed)
{
	int interval=readThread.setMaxSpeed(maxSpeed);
	writeThread.setMaxSpeed(maxSpeed);
	if(maxSpeed>0)
	{
		clockForTheCopySpeed.setInterval(interval);
		if(!clockForTheCopySpeed.isActive())//seam useless !this->isFinished()
			clockForTheCopySpeed.start();
	}
	else
	{
		if(clockForTheCopySpeed.isActive())
			clockForTheCopySpeed.stop();
	}
}

//set block size in KB
bool TransferThread::setBlockSize(const unsigned int blockSize)
{
	this->blockSize=blockSize;
	return readThread.setBlockSize(blockSize) && writeThread.setBlockSize(blockSize);
}

//pause the copy
void TransferThread::pause()
{
	if(transfer_stat==TransferStat_Idle)
		return;
	readThread.pause();
}

//resume the copy
void TransferThread::resume()
{
	if(transfer_stat==TransferStat_Idle)
		return;
	readThread.resume();
}

//stop the current copy
void TransferThread::stop()
{
	stopIt=true;
	if(transfer_stat==TransferStat_Idle)
		return;
	readThread.stop();
	writeThread.stop();
}

void TransferThread::readIsFinish()
{
	if(readIsFinishVariable)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] double event dropped");
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	readIsFinishVariable=true;
	canStartTransfer=false;
	//check here if need start checksuming or not
	real_doChecksum=doChecksum && (!checksumOnlyOnError || fileContentError);
	if(real_doChecksum)
	{
		readIsFinishVariable=false;
		transfer_stat=TransferStat_Checksum;
		sourceChecksum=QByteArray();
		destinationChecksum=QByteArray();
		readThread.startCheckSum();
	}
	else
	{
		transfer_stat=TransferStat_PostTransfer;
		readThread.postOperation();
	}
	emit pushStat(transfer_stat,transferId);
}

void TransferThread::writeIsFinish()
{
	if(writeIsFinishVariable)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] double event dropped");
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	writeIsFinishVariable=true;
	//check here if need start checksuming or not
	if(real_doChecksum)
	{
		writeIsFinishVariable=false;
		transfer_stat=TransferStat_Checksum;
		writeThread.startCheckSum();
	}
	else
		writeThread.postOperation();
}

void TransferThread::readChecksumFinish(const QByteArray& checksum)
{
	sourceChecksum=checksum;
	compareChecksum();
}

void TransferThread::writeChecksumFinish(const QByteArray& checksum)
{
	destinationChecksum=checksum;
	compareChecksum();
}

void TransferThread::compareChecksum()
{
	if(sourceChecksum.size()==0)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] the checksum of source is missing");
		return;
	}
	if(destinationChecksum.size()==0)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] the checksum of destination is missing");
		return;
	}
	if(sourceChecksum==destinationChecksum)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] the checksum match");
		readThread.postOperation();
		writeThread.postOperation();
		transfer_stat=TransferStat_PostTransfer;
		emit pushStat(transfer_stat,transferId);
	}
	else
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] the checksum not match");
		//emit error here, and wait to resume
		emit errorOnFile(destinationInfo,tr("The checksums not match"));
	}
}

void TransferThread::readIsClosed()
{
	if(readIsClosedVariable)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] double event dropped");
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	readIsClosedVariable=true;
	checkIfAllIsClosed();
}

void TransferThread::writeIsClosed()
{
	if(writeIsClosedVariable)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] double event dropped");
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	writeIsClosedVariable=true;
	checkIfAllIsClosed();
}

bool TransferThread::checkIfAllIsClosed()
{
	if((readError || writeError) && !needSkip)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] resolve error before progress");
		return false;
	}
	if((!readIsReadyVariable || readIsClosedVariable) && (!writeIsReadyVariable || writeIsClosedVariable))
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] emit internalStartPostOperation() to do the real post operation");
		transfer_stat=TransferStat_PostOperation;
		//emit pushStat(stat,transferId);
		emit internalStartPostOperation();
		return true;
	}
	else
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] "+QString("wait self close: readIsReadyVariable: %1, readIsClosedVariable: %2, writeIsReadyVariable: %3, writeIsClosedVariable: %4")
					 .arg(readIsReadyVariable)
					 .arg(readIsClosedVariable)
					 .arg(writeIsReadyVariable)
					 .arg(writeIsClosedVariable)
					 );
		return false;
	}
}

/// \todo found way to retry that's
/// \todo the rights copy
void TransferThread::postOperation()
{
	if(transfer_stat!=TransferStat_PostOperation)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] need be in transfer, source: "+source+", destination: "+destination+", stat:"+QString::number(transfer_stat));
		return;
	}
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	//all except closing
	if((readError || writeError) && !needSkip)//normally useless by checkIfAllIsFinish()
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] resume after error");
		return;
	}

	if(!needSkip)
	{
		if(!doFilePostOperation())
			return;

		//remove source in moving mode
		if(mode==Move && !canBeMovedDirectlyVariable)
		{
			if(QFile::exists(destination))
			{
				QFile sourceFile(source);
				if(!sourceFile.remove())
				{
					emit errorOnFile(sourceInfo,sourceFile.errorString());
					return;
				}
			}
			else
				ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] try remove source but destination not exists!");
		}
	}
	else//do difference skip a file and skip this error case
	{
		if(needRemove && QFile::exists(destination))
		{
			QFile destinationFile(destination);
			if(!destinationFile.remove())
			{
				//emit errorOnFile(sourceInfo,destinationFile.errorString());
				//return;
			}
		}
		else
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] try remove destination but not exists!");
	}
	transfer_stat=TransferStat_Idle;
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] emit postOperationStopped()");
	emit postOperationStopped();
}

bool TransferThread::doFilePostOperation()
{
	//do operation needed by copy
	if(!canBeMovedDirectlyVariable)
	{
		//set the time if no write thread used
		if(keepDate)
			changeFileDateTime(destination,source);//can't do that's after move because after move the source not exist
			/*
			  ignore it, because need correct management, mainly with move
			if(!)
			{
				emit errorOnFile(destinationInfo,tr("Unable to change the date"));//destination.errorString()
				return false;
			}*/
	}

	if(stopIt)
		return false;

	return true;
}

//////////////////////////////////////////////////////////////////
/////////////////////// Error management /////////////////////////
//////////////////////////////////////////////////////////////////

void TransferThread::getWriteError()
{
        if(writeError)
        {
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] already in write error!");
                return;
        }
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	fileContentError		= true;
	writeError			= true;
	writeIsReadyVariable		= false;
	writeError_source_seeked	= false;
	writeError_destination_reopened	= false;
	emit errorOnFile(destinationInfo,writeThread.errorString());
}

void TransferThread::getReadError()
{
        if(readError)
        {
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] already in read error!");
                return;
        }
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	fileContentError	= true;
	readError		= true;
	writeIsReadyVariable	= false;
	readIsReadyVariable	= false;
	emit errorOnFile(sourceInfo,readThread.errorString());
}

//retry after error
void TransferThread::retryAfterError()
{
	//opening error
	if(transfer_stat==TransferStat_PreOperation)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] is not idle, source: "+source+", destination: "+destination+", stat: "+QString::number(transfer_stat));
		tryOpen();
		return;
	}
	//data streaming error
	if(transfer_stat!=TransferStat_PostOperation && transfer_stat!=TransferStat_Transfer && transfer_stat!=TransferStat_Checksum)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Critical,"["+QString::number(id)+"] is not idle, source: "+source+", destination: "+destination+", stat: "+QString::number(transfer_stat));
		return;
	}
	if(canBeMovedDirectlyVariable)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] retry the system move");
		tryMoveDirectly();
		return;
	}
	if(transfer_stat==TransferStat_Checksum)
	{
		if(writeError)
		{
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start and resume the write error");
			writeThread.reopen();
		}
		else if(readError)
		{
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start and resume the read error");
			readThread.reopen();
		}
		else //only checksum difference
		{
			ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] retry all the transfer");
			canStartTransfer=true;
			ifCanStartTransfer();
		}
		return;
	}
	if(writeError)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start and resume the write error");
		readThread.seekToZeroAndWait();
		writeThread.reopen();
	}
	else if(readError)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start and resume the read error");
		readThread.reopen();
	}
	else
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] unknow error resume");
}

void TransferThread::writeThreadIsReopened()
{
	if(writeError_destination_reopened)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] double event dropped");
		return;
	}
	writeError_destination_reopened=true;
	if(transfer_stat==TransferStat_Checksum)
	{
		writeThread.startCheckSum();
		return;
	}
	if(writeError_source_seeked && writeError_destination_reopened)
		resumeTransferAfterWriteError();
}

void TransferThread::readThreadIsSeekToZeroAndWait()
{
	if(writeError_source_seeked)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] double event dropped");
		return;
	}
	writeError_source_seeked=true;
	if(writeError_source_seeked && writeError_destination_reopened)
		resumeTransferAfterWriteError();
}

void TransferThread::resumeTransferAfterWriteError()
{
	writeError=false;
/********************************
 if(canStartTransfer)
	 readThread.startRead();
useless, because the open destination event
will restart the transfer as normal
*********************************/
/*********************************
if(!canStartTransfer)
	stat=WaitForTheTransfer;
useless because already do at open event
**********************************/
	//if is in wait
	if(!canStartTransfer)
		emit checkIfItCanBeResumed();
}

void TransferThread::readThreadResumeAfterError()
{
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
        readError=false;
        writeIsReady();
        readIsReady();
}

//////////////////////////////////////////////////////////////////
///////////////////////// Normal event ///////////////////////////
//////////////////////////////////////////////////////////////////

void TransferThread::readIsStopped()
{
	if(!sended_state_readStopped)
	{
		sended_state_readStopped=true;
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] emit readIsStopped()");
		emit readStopped();
	}
	else
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] drop dual read stopped");
	readIsFinish();
}

void TransferThread::writeIsStopped()
{
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start");
	if(!sended_state_writeStopped)
	{
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] emit writeStopped()");
		sended_state_writeStopped=true;
		emit writeStopped();
	}
	else
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] double event dropped");
	writeIsFinish();
}

void TransferThread::timeOfTheBlockCopyFinished()
{
	readThread.timeOfTheBlockCopyFinished();
}

//get drive of an file or folder
QString TransferThread::getDrive(QString fileOrFolder)
{
	for (int i = 0; i < mountSysPoint.size(); ++i) {
		if(fileOrFolder.startsWith(mountSysPoint.at(i)))
			return mountSysPoint.at(i);
	}
	//if unable to locate the right mount point
	return "";
}

//set drive list, used in getDrive()
void TransferThread::setDrive(QStringList drives)
{
	mountSysPoint=drives;
}

//fonction to edit the file date time
bool TransferThread::changeFileDateTime(const QString &source,const QString &destination)
{
	/*
	  if(maxTime>=sourceInfo.lastModified())
		return;
	  */
	/** Why not do it with Qt? Because it not support setModificationTime(), and get the time with Qt, that's mean use local time where in C is UTC time */
	#ifdef Q_OS_UNIX
		#ifdef Q_OS_LINUX
			struct stat info;
			stat(source.toLatin1().data(),&info);
			time_t ctime=info.st_ctim.tv_sec;
			time_t actime=info.st_atim.tv_sec;
			time_t modtime=info.st_mtim.tv_sec;
			//this function avalaible on unix and mingw
			utimbuf butime;
			butime.actime=actime;
			butime.modtime=modtime;
			//creation time not exists into unix world
			Q_UNUSED(ctime)
			return utime(destination.toLatin1().data(),&butime)==0;
		#else //mainly for mac
			QFileInfo fileInfo(destination);
			time_t ctime=fileInfo.created().toTime_t();
			time_t actime=fileInfo.lastRead().toTime_t();
			time_t modtime=fileInfo.lastModified().toTime_t();
			//this function avalaible on unix and mingw
			utimbuf butime;
			butime.actime=actime;
			butime.modtime=modtime;
			//creation time not exists into unix world
			Q_UNUSED(ctime)
			return utime(destination.toLatin1().data(),&butime)==0;
		#endif
	#else
		#ifdef Q_OS_WIN32
			#ifdef ULTRACOPIER_PLUGIN_SET_TIME_UNIX_WAY
				struct __stat64 info;
				_stat64(source.toLatin1().data(),&info);
				time_t ctime=info.st_ctime;
				time_t actime=info.st_atime;
				time_t modtime=info.st_mtime;
				//this function avalaible on unix and mingw
				utimbuf butime;
				butime.actime=actime;
				butime.modtime=modtime;
				//creation time not exists into unix world
				Q_UNUSED(ctime)
				return utime(destination.toLatin1().data(),&butime)==0;
			#else
				wchar_t filePath[65535];
				source.toWCharArray(filePath);
				HANDLE hFileSouce = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
				destination.toWCharArray(filePath);
				HANDLE hFileDestination = CreateFile(filePath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
				if(hFileSouce == INVALID_HANDLE_VALUE)
				{
					ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] open failed to read");
					return false;
				}
				if(hFileDestination == INVALID_HANDLE_VALUE)
				{
					CloseHandle(hFileSouce);
					ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] open failed to write");
					return false;
				}
				FILETIME ftCreate, ftAccess, ftWrite;
				if(!GetFileTime(hFileSouce, &ftCreate, &ftAccess, &ftWrite))
				{
					CloseHandle(hFileSouce);
					CloseHandle(hFileDestination);
					ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] unable to get the file time");
					return false;
				}
				if(!SetFileTime(hFileDestination, &ftCreate, &ftAccess, &ftWrite))
				{
					CloseHandle(hFileSouce);
					CloseHandle(hFileDestination);
					ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] unable to set the file time");
					return false;
				}
				CloseHandle(hFileSouce);
				CloseHandle(hFileDestination);
				return true;
			#endif
		#else
			return false;
		#endif
	#endif
	return false;
}

//skip the copy
void TransferThread::skip()
{
	ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] start with stat: "+QString::number(transfer_stat));
	switch(transfer_stat)
	{
	case TransferStat_PreOperation:
	case TransferStat_WaitForTheTransfer:
		needSkip=true;
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] case WaitForTheTransfer or PreOperation, readIsReadyVariable: "+QString::number(readIsReadyVariable)+", readIsClosedVariable: "+QString::number(readIsClosedVariable)+", writeIsReadyVariable: "+QString::number(writeIsReadyVariable)+", writeIsClosedVariable: "+QString::number(writeIsClosedVariable));
		//check if all is source and destination is closed
		if(!checkIfAllIsClosed())
		{
			if(readIsReadyVariable && !readIsClosedVariable)
				readThread.stop();
			if(writeIsReadyVariable && !writeIsClosedVariable)
				writeThread.stop();
		}
		break;
	case TransferStat_Transfer:
		needSkip=true;
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] case Transfer, readIsReadyVariable: "+QString::number(readIsReadyVariable)+", readIsClosedVariable: "+QString::number(readIsClosedVariable)+", writeIsReadyVariable: "+QString::number(writeIsReadyVariable)+", writeIsClosedVariable: "+QString::number(writeIsClosedVariable));
		if(!checkIfAllIsClosed())
		{
			if(readIsReadyVariable && !readIsClosedVariable)
				readThread.stop();
			if(writeIsReadyVariable && !writeIsClosedVariable)
				writeThread.stop();
		}
		break;
	case TransferStat_Checksum:
		needSkip=true;
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Notice,"["+QString::number(id)+"] case Transfer, readIsReadyVariable: "+QString::number(readIsReadyVariable)+", readIsClosedVariable: "+QString::number(readIsClosedVariable)+", writeIsReadyVariable: "+QString::number(writeIsReadyVariable)+", writeIsClosedVariable: "+QString::number(writeIsClosedVariable));
		if(!checkIfAllIsClosed())
		{
			if(readIsReadyVariable && !readIsClosedVariable)
				readThread.stop();
			if(writeIsReadyVariable && !writeIsClosedVariable)
				writeThread.stop();
		}
		break;
	case TransferStat_PostOperation:
		//do nothing because here is closing...
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] is already in post op");
		break;
	default:
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] can skip in this state!");
		return;
	}
}

//return info about the copied size
qint64 TransferThread::copiedSize()
{
	switch(transfer_stat)
	{
	case TransferStat_Transfer:
	case TransferStat_PostOperation:
		return readThread.getLastGoodPosition();
	case TransferStat_Checksum:
		return transferSize;
	default:
		return 0;
	}
}

//retry after error
void TransferThread::putAtBottom()
{
	emit tryPutAtBottom();
}

/// \brief set rsync
void TransferThread::setRsync(const bool rsync)
{
	this->rsync=rsync;
}

void TransferThread::set_osBufferLimit(unsigned int osBufferLimit)
{
	this->osBufferLimit=osBufferLimit;
}

#ifdef ULTRACOPIER_PLUGIN_DEBUG
//to set the id
void TransferThread::setId(int id)
{
	this->id=id;
	readThread.setId(id);
	writeThread.setId(id);
}

QChar TransferThread::readingLetter()
{
	switch(readThread.stat)
	{
	case ReadThread::Idle:
		return '_';
	break;
	case ReadThread::InodeOperation:
		return 'I';
	break;
	case ReadThread::Read:
		return 'R';
	break;
	case ReadThread::WaitWritePipe:
		return 'W';
	break;
	case ReadThread::Checksum:
		return 'S';
	break;
	default:
		return '?';
	}
}

QChar TransferThread::writingLetter()
{
	switch(writeThread.stat)
	{
	case WriteThread::Idle:
		return '_';
	break;
	case WriteThread::InodeOperation:
		return 'I';
	break;
	case WriteThread::Write:
		return 'W';
	break;
	case WriteThread::Close:
		return 'C';
	break;
	case WriteThread::Read:
		return 'R';
	break;
	case WriteThread::Checksum:
		return 'S';
	break;
	default:
		return '?';
	}
}

#endif

void TransferThread::setMkpathTransfer(QSemaphore *mkpathTransfer)
{
	this->mkpathTransfer=mkpathTransfer;
	writeThread.setMkpathTransfer(mkpathTransfer);
}

void TransferThread::set_doChecksum(bool doChecksum)
{
	this->doChecksum=doChecksum;
}

void TransferThread::set_checksumIgnoreIfImpossible(bool checksumIgnoreIfImpossible)
{
	this->checksumIgnoreIfImpossible=checksumIgnoreIfImpossible;
}

void TransferThread::set_checksumOnlyOnError(bool checksumOnlyOnError)
{
	this->checksumOnlyOnError=checksumOnlyOnError;
}

void TransferThread::set_osBuffer(bool osBuffer)
{
	this->osBuffer=osBuffer;
}

void TransferThread::set_osBufferLimited(bool osBufferLimited)
{
	this->osBufferLimited=osBufferLimited;
}

//not copied size, because that's count to the checksum, ...
quint64 TransferThread::realByteTransfered()
{
	switch(transfer_stat)
	{
	case TransferStat_Transfer:
	case TransferStat_PostOperation:
	case TransferStat_Checksum:
		return readThread.getLastGoodPosition();
		ULTRACOPIER_DEBUGCONSOLE(DebugLevel_Warning,"["+QString::number(id)+"] transferSize: "+QString::number(transferSize));
		return transferSize;
	default:
		return 0;
	}
}

void TransferThread::setRenamingRules(QString firstRenamingRule,QString otherRenamingRule)
{
	this->firstRenamingRule=firstRenamingRule;
	this->otherRenamingRule=otherRenamingRule;
}
