/*******************************************************************************
*                                                                              *
* Société de Transports de Montréal.                                    	   *
* 2012 - 2013                                                                        *
*                                                                              *
* Projet Zones Tests                                                           *
*                                                                              *
*                                                                              *
*                                                                              *
*******************************************************************************/
/*
	Description:
	Cette classe est responsable de la création d'une liste d'objets CLogElement
	à partir des données de passage des trains contenues dans les fichiers log.
	Cette classe est principalement utilisée par l'interface graphique pour la
	visualisation des passages.
	
*/

/* ************************************************************************** */
/* Revision:
### 20121024 JFM

	
### YYYYMMDD Description du besoin ou du bug
	Description du changement.
 */

/* ************************************************************************** */

#include "LogMgr.h"

#include "TrainLogFileMgr.h"
#include "OutilZT.h"
//#include "ZTLog.h"
#include <QCoreApplication>
#include <QMessageBox>
#include <QBuffer>

CLogMgr::CLogMgr()
{
    mProgramHandle = 0;

    mDirParserThread = new QThread();
    mDirParserWorkerThread = new CDirParserThread();
    mDirParserWorkerThread->moveToThread(mDirParserThread);
    connect(mDirParserThread,SIGNAL(started()),mDirParserWorkerThread,SLOT(ParseDirectories()));
    connect(mDirParserThread,SIGNAL(finished()),this,SLOT(ThreadQuit()));
    connect(mDirParserThread,SIGNAL(terminated()),this,SLOT(ThreadTerminated()));
   // connect(mDirParserWorkerThread,SIGNAL(NewLogParsed(QString,bool)),this,SLOT(NewLogParsed(QString,bool)));
    connect(mDirParserWorkerThread,SIGNAL(NewLogParsed(CLogElement*)),this,SLOT(NewLogParsed(CLogElement*)));
    connect(mDirParserWorkerThread,SIGNAL(ParsingFinished(int)),this,SLOT(DirParsingFinished(int)));
    connect(mDirParserWorkerThread,SIGNAL(EmptyDirParsed()),this,SLOT(EmptyDirParsed()));

    mDatabaseParsingTimer = new QTimer();
    mDatabaseParsingTimer->setInterval(60000);
    connect(mDatabaseParsingTimer,SIGNAL(timeout()),this,SLOT(ParsingTimerExpired()));

}

CLogMgr::~CLogMgr()
{
	DestroyLogList();
    delete mDirParserThread;
    delete mDirParserWorkerThread;
    delete mDatabaseParsingTimer;
}

unsigned int CLogMgr::DestroyLogList()
{
	for(int i = 0; i < mPassagesList.size(); i++)
	{
        delete mPassagesList.at(i);
	}

    mPassagesList.clear();

	return RET_OK;
}

unsigned int CLogMgr::ProtectLogElementFile(bool IsProtected, CLogElement *Element)
{
    if(Element->mZTLogType == ZT1_LOG_TYPE)
    {
        CZT1LogElement *ZT1Element = (CZT1LogElement*)Element;
        if(CTrainLogFileMgr::instance()->SetTrainLogProtected(IsProtected,ZT1Element->mLogFileName) == RET_OK)
        {
            ZT1Element->mFileProtected = IsProtected;
            SaveDatabaseFile();
            return RET_OK;
        }
    }
    else if(Element->mZTLogType == ZT2_LOG_TYPE)
    {
        CZT2LogElement *ZT2Element = (CZT2LogElement*)Element;
        if(CTrainLogFileMgr::instance()->SetTrainLogProtected(IsProtected,ZT2Element->mLogFileName) == RET_OK)
        {
            ZT2Element->mFileProtected = IsProtected;
            SaveDatabaseFile();
            return RET_OK;
        }
    }

    return RET_ERROR;
}
unsigned int CLogMgr::ParseLogs(bool RebuildDatabase, bool KeepData)
{
    DestroyLogList();
    qDebug("Parsing Logs now...");

    mSaveDBFile = false;
    mParsingFinished = false;
    mProgramHandle->DatabaseFetchingBegin(this);
    mDatabaseFileCounter = 0;
    if(RebuildDatabase == true)
    {
        qDebug("Rebuilding database now...");
        mDirParserWorkerThread->SetParsingInfo(QDir(mLogDataDir),"*.bin",KeepData);
        qDebug("Starting parser thread now...");
        mDirParserThread->start();
        mSaveDBFile = true;
    }
    else
    {
        //Load database file.
        QDir BaseDir(mLogDataDir);
        QString DatabaseFilePath = BaseDir.filePath("Trains.zdb");
       // DatabaseFilePath += "Trains.zdb";
        QFile* DatabaseFile = new QFile(BaseDir.filePath("Trains.zdb")/*DatabaseFilePath*/);
        if(DatabaseFile)
        {
            if(DatabaseFile->open(QIODevice::ReadOnly | QIODevice::Unbuffered) == false)
            {
               //rebuild database...
                qDebug("Failed to open Trains.zdb, rebuilding database now...");
                mDirParserWorkerThread->SetParsingInfo(QDir(mLogDataDir),"*.bin",KeepData);
                qDebug("Starting parser thread now...");
                mDirParserThread->start();
                mSaveDBFile = true;
                delete DatabaseFile;
                return RET_OK;
            }
        }
        else
        {
            DirParsingFinished(0);
            return RET_ERROR;
        }


        QByteArray DBData = DatabaseFile->readAll();
        QDataStream *DBStrm = new QDataStream(DBData);
        DBStrm->setVersion(QDataStream::Qt_4_8);

        qint32 NBRecords;
        *DBStrm >> NBRecords;

        for(qint32 i = 0; i < NBRecords; i++)
        {
            unsigned int LogType;
            *DBStrm >> LogType;

            if(LogType == ZT1_LOG_TYPE)
            {
                CZT1LogElement *NewElement = new CZT1LogElement;


                *DBStrm >> NewElement->mPassageDateTime
                        >> NewElement->mTrainType
                        >> NewElement->mNbElements
                        >> NewElement->mThreadDataStartTime
                        >> NewElement->mThreadDataEndTime
                        >> NewElement->mLogFileName
                        >> NewElement->mMeanSpeed
                        >> NewElement->mFlags
                        >> NewElement->mStationName
                        >> NewElement->mFileProtected;

                int NBDetect;
                *DBStrm >> NBDetect;

                for(int i = 0; i < NBDetect; i++)
                {
                    CZTDetectionData *NewDetection = new CZTDetectionData;
                    *DBStrm >> NewDetection->mDetectionID
                            >> NewDetection->mRank
                            >> NewDetection->mTimeStamp;

                    NewElement->mZTDetections.append(NewDetection);
                }
                NewLogParsed(NewElement);

            }
            else if(LogType == ZT2_LOG_TYPE)
            {
                CZT2LogElement *NewElement = new CZT2LogElement;


                *DBStrm >> NewElement->mPassageDateTime
                        >> NewElement->mLogFileName
                        >> NewElement->mNbElements
                        >> NewElement-> mStationName
                        >> NewElement->mFlags
                        >> NewElement->mFileProtected;

                int NBDetect;
                *DBStrm >> NBDetect;

                for(int i = 0; i < NBDetect; i++)
                {
                    CZTDetectionData *NewDetection = new CZTDetectionData;
                    *DBStrm >> NewDetection->mDetectionID
                            >> NewDetection->mRank
                            >> NewDetection->mTimeStamp;

                    NewElement->mZTDetections.append(NewDetection);
                }

                NewLogParsed(NewElement);
            }
            else
            {
                qDebug("Invalid logtype in DB file...");
            }


        }

        DatabaseFile->close();
        delete DatabaseFile;
        delete DBStrm;

        DirParsingFinished(1);

    }
    return RET_OK;
}

unsigned int CLogMgr::SaveDatabaseFile()
{

    QDir BaseDir(mLogDataDir);
    QString DatabaseFilePath = BaseDir.filePath("Trains.zdb");
   // DatabaseFilePath += "Trains.zdb";
    QFile* DatabaseFile = new QFile(BaseDir.filePath("Trains.zdb")/*DatabaseFilePath*/);


    if(DatabaseFile)
    {
        if(DatabaseFile->open(QIODevice::WriteOnly | QIODevice::Unbuffered) == false)
        {
            QMessageBox::information(0,"Erreur","Impossible de créer le fichier DB");
            delete DatabaseFile;
            return RET_ERROR;
        }
    }
    else
    {
        QMessageBox::information(0,"Erreur","Impossible de créer le fichier DB");
        return RET_ERROR;
    }


    QByteArray byteArray;
    QBuffer FileBuffer(&byteArray);
    FileBuffer.open(QIODevice::WriteOnly);
    QDataStream *DBStrm = new QDataStream(&FileBuffer);
    DBStrm->setVersion(QDataStream::Qt_4_8);

    qint32 NBRecords = mPassagesList.size();
    *DBStrm << NBRecords;

    for(qint32 i = 0; i < NBRecords; i++)
    {
        unsigned int LogType = mPassagesList.at(i)->mZTLogType;
        *DBStrm << LogType;

        if(LogType == ZT1_LOG_TYPE)
        {
            CZT1LogElement *NewElement = (CZT1LogElement*)mPassagesList.at(i);

            QDateTime test = NewElement->mPassageDateTime;

//            *DBStrm << NewElement->mPassageDateTime
            *DBStrm << test
                    << NewElement->mTrainType
                    << NewElement->mNbElements
                    << NewElement->mThreadDataStartTime
                    << NewElement->mThreadDataEndTime
                    << NewElement->mLogFileName
                    << NewElement->mMeanSpeed
                    << NewElement->mFlags
                    << NewElement->mStationName
                    << NewElement->mFileProtected;

            int NBDetect = NewElement->mZTDetections.size();
            *DBStrm << NBDetect;

            for(int i = 0; i < NBDetect; i++)
            {
                *DBStrm << NewElement->mZTDetections.at(i)->mDetectionID
                        << NewElement->mZTDetections.at(i)->mRank
                        << NewElement->mZTDetections.at(i)->mTimeStamp;
            }
        }
        else if(LogType == ZT2_LOG_TYPE)
        {
            CZT2LogElement *NewElement = (CZT2LogElement*)mPassagesList.at(i);


            *DBStrm << NewElement->mPassageDateTime
                    << NewElement->mLogFileName
                    << NewElement->mNbElements
                    << NewElement->mStationName
                    << NewElement->mFlags
                    << NewElement->mFileProtected;

            int NBDetect = NewElement->mZTDetections.size();
            *DBStrm << NBDetect;

            for(int i = 0; i < NBDetect; i++)
            {
                *DBStrm << NewElement->mZTDetections.at(i)->mDetectionID
                        << NewElement->mZTDetections.at(i)->mRank
                        << NewElement->mZTDetections.at(i)->mTimeStamp;
            }
        }
        else
        {
            qDebug("Invalid log type in array!!!");
        }


    }


    FileBuffer.seek(0);
    DatabaseFile->write(FileBuffer.buffer());
    DatabaseFile->flush();

    FileBuffer.close();
    DatabaseFile->close();
    delete DatabaseFile;
    delete DBStrm;
    mSaveDBFile = false;
    return RET_OK;
}

unsigned int CLogMgr::RebuildDatabaseFile()
{
    ParseLogs(true,false);
    mSaveDBFile = true;
    return RET_OK;
}



int CLogMgr::ParseDir(QDir dir, bool KeepData)
{

    QStringList LogFilters;
    QFileInfoList list;
   // QString LogDataDir = mProgramHandle->GetLogDataPath();
    LogFilters << "*.bin";

    //Load files in base directory
    QDir LogDir(dir);
    LogDir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
    LogDir.setNameFilters(LogFilters);
    LogDir.setSorting(QDir::Name);
    list = LogDir.entryInfoList();
    if(list.size() != 0)
    {
        //Extract data for each passage
        for(int i = 0; i < list.size(); i++)
        {
            ParseNewLog(list.at(i).filePath(),KeepData);
        }
    }


    //Check for subdirectories
    QDir SubDirectories(dir);

    SubDirectories.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);

    QFileInfoList SubDirList = SubDirectories.entryInfoList();
    for(int i = 0; i < SubDirList.size(); i++)
    {
        ParseDir(QDir(SubDirList.at(i).absoluteFilePath()), KeepData);
    }

    return 1;
}

CLogElement* CLogMgr::LoadLogData(CLogElement *Element)
{
	if(Element->mZTLogType == ZT1_LOG_TYPE)
	{
		CZT1LogElement *TargetElement = (CZT1LogElement*)Element;
		QString FilePath = TargetElement->mLogFileName;
		unsigned int ret;

		//Make shure that the lists are empty in the target element
		for(int i = 0; i < TargetElement->mZTLogData.size(); i++)
		{
			delete TargetElement->mZTLogData.at(i);
		}
		for(int i = 0; i < TargetElement->mZTDetections.size(); i++)
		{
			delete TargetElement->mZTDetections.at(i);
		}

		TargetElement->mZTLogData.clear();
		TargetElement->mZTDetections.clear();

		//Passing the TargetElement to the OpenTrainLog function will fill it with data
		CTrainLogFileMgr::instance()->OpenTrainLog(FilePath,ret,TargetElement,true);

		return (CLogElement*)TargetElement;
	}
	else if(Element->mZTLogType == ZT2_LOG_TYPE)
	{
		CZT2LogElement *TargetElement = (CZT2LogElement*)Element;
		QString FilePath = TargetElement->mLogFileName;
		unsigned int ret;

		//Make shure that the lists are empty in the target element
		for(int i = 0; i < TargetElement->mZTLogData.size(); i++)
		{
			delete TargetElement->mZTLogData.at(i);
		}
		for(int i = 0; i < TargetElement->mZTDetections.size(); i++)
		{
			delete TargetElement->mZTDetections.at(i);
		}

		TargetElement->mZTLogData.clear();
		TargetElement->mZTDetections.clear();

		//Passing the TargetElement to the OpenTrainLog function will fill it with data
		CTrainLogFileMgr::instance()->OpenTrainLog(FilePath,ret,TargetElement,true);

		return (CLogElement*)TargetElement;
	}

	return 0;

}

unsigned int CLogMgr::FreeLogData(CLogElement *Element)
{
	if(Element->mZTLogType == ZT1_LOG_TYPE)
	{
		CZT1LogElement *temp = (CZT1LogElement*)Element;
		for(int i = 0; i < temp->mZTLogData.size(); i++)
			delete temp->mZTLogData.at(i);
		temp->mZTLogData.clear();
	}
	if(Element->mZTLogType == ZT2_LOG_TYPE)
	{
		CZT2LogElement *temp = (CZT2LogElement*)Element;
		for(int i = 0; i < temp->mZTLogData.size(); i++)
			delete temp->mZTLogData.at(i);
		temp->mZTLogData.clear();
	}

	return RET_OK;

}

unsigned int CLogMgr::DeleteLog(int LogIndex)
{
    if(LogIndex >= mPassagesList.size())
        return RET_ERROR;

    QString Filename;

    CLogElement *Element = mPassagesList.takeAt(LogIndex);
    FreeLogData(Element);   //make shure no data will be leaked
    {
        if(Element->mZTLogType == ZT1_LOG_TYPE)
        {
            CZT1LogElement *temp = (CZT1LogElement*)Element;
            Filename = temp->mLogFileName;

        }
        if(Element->mZTLogType == ZT2_LOG_TYPE)
        {
            CZT2LogElement *temp = (CZT2LogElement*)Element;
            Filename = temp->mLogFileName;
        }
    }
    QDir DirObject;
    DirObject.remove(Filename);
    delete Element;

    SaveDatabaseFile();

    return RET_OK;
}

bool CLogMgr::ParseNextLog()
{
    if((int)mLogsFileIndex >= mLogsFilelist.size())
        return false;

    //Extract data

    if(ParseNewLog(mLogsFilelist.at(mLogsFileIndex).filePath()) == RET_ERROR)
        return false;

    mLogsFileIndex++;

    return true;
}

void CLogMgr::NewLogParsed(QString Filename,bool KeepData)
{
    ParseNewLog(Filename,KeepData);
    mDatabaseParsingTimer->start();
}

void CLogMgr::NewLogParsed(CLogElement * NewLog)
{
    mPassagesList.append(NewLog);
    mDatabaseParsingTimer->start();
    mProgramHandle->DatabaseFetchingTick(this,mDatabaseFileCounter++);

}

void CLogMgr::DirParsingFinished(int Res)
{

    mDatabaseParsingTimer->stop();
    mParsingFinished = true;
    mDirParserThread->quit();
    mProgramHandle->LogsDatabaseLoaded(this,Res);

    if(mSaveDBFile == true)
    {
        SaveDatabaseFile();
    }

}

void CLogMgr::ParsingTimerExpired()
{
    qDebug("Parsing timer timeout");
    mDirParserWorkerThread->KillThread();
    mDirParserThread->terminate();
    DirParsingFinished(2);

}
void CLogMgr::EmptyDirParsed()
{
    //The thread is not stuck.  Kick the timer...
    mDatabaseParsingTimer->start();
}

void CLogMgr::ThreadQuit()
{
    qDebug("Thread quit slot");
}

void CLogMgr::ThreadTerminated()
{
    qDebug("Thread terminated slot");
}


unsigned int CLogMgr::ParseNewLog(QString FileName, bool KeepData)
{
	Q_UNUSED(KeepData)

    unsigned int ret;
    CLogElement *NewLog = CTrainLogFileMgr::instance()->OpenTrainLog(FileName,ret);

    if(ret == RET_ERROR)
	{
        return RET_ERROR;
	}

    //the date and time of the log entry is assigned the date and time of the first record...
    if(NewLog->mZTLogType == ZT1_LOG_TYPE)
    {
        mPassagesList.append(NewLog);
    }
    else if(NewLog->mZTLogType == ZT2_LOG_TYPE)
    {
        mPassagesList.append(NewLog);
    }
    return RET_OK;
}

unsigned int CLogMgr::ParseImportedLogs(QStringList *NewLogFiles)
{
    if(NewLogFiles == 0)
    {
        return RET_ERROR;
    }

    for(int i = 0; i < NewLogFiles->size(); i++)
    {
        ParseNewLog(NewLogFiles->at(i));
    }

    SaveDatabaseFile();
    mProgramHandle->LogsDatabaseLoaded(this,1);

    return RET_OK;
}

unsigned int CLogMgr::GetLogsCount()
{
	return mPassagesList.size();
}

QList<CLogElement*> * CLogMgr::GetLogsList()
{
	return &mPassagesList;
}

CLogElement::~CLogElement()
{
}

CZT1LogElement::~CZT1LogElement()
{
	qDeleteAll(mZTLogData);
	mZTLogData.clear();

	qDeleteAll(mZTDetections);
	mZTDetections.clear();
}

CZT2LogElement::~CZT2LogElement()
{
	for(int i = 0; i < mZTLogData.size(); i++)
		delete mZTLogData.at(i);
	mZTLogData.clear();

    for(int i = 0; i < mZTDetections.size(); i++)
        delete mZTDetections.at(i);
    mZTDetections.clear();

}
