/***************************************************************************
 *   Copyright (C) 2005 by Thierry CHARLES   *
 *   thierry@les-charles.net   *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "fileinfo.h"

#include <wx/filename.h>
#include <wx/event.h>
#include <wx/ffile.h>
#include <iostream>
#include <fstream>

#include "components/framework/tapplication.h"
#include "components/framework/tpanelelement.h"
#include "xpe_components/editor/editor_panelelement.h"
#include "xpe_components/mainwindow.h"
#include "xpe_components/res_id.h"
#include "xpe_components/lib/pathres.h"
#include "xpe_components/parser/parserres.h"


#define EVENT_ID_GET_DOCUMENT_CONTENT 1000000
#define EVENT_ID_GET_MODIFICATION_TIME 1000001

DEFINE_EVENT_TYPE(EVENT_FILEINFO_GET_DATA);

/** vérifie que le type déclaré est le bon */
void FileInfo::checkType()
{
    if( this->type == SourcedFile || this->type == EditedFile)
    {
        if((!this->sFileName.length()) || (!libfile::isFile(this->sFileName)))
        {
            if(this->iEditorId)
                this->type = NewFile;
            else
                this->type = UnexistantFile;
            this->checkType();
            return;
        }
    }
    if( this->type == SourcedFile )
    {
        TPanelElement* elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromFile(this->sFileName);
        if(elt)
        {
            this->type = EditedFile;
        }
        return;
    }
    if( this->type == EditedFile )
    {
        TPanelElement* elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromFile(this->sFileName);
        if(!elt)
        {
            this->type = SourcedFile;
            return;
        }
        return;
    }
    if( this->type == NewFile )
    {
        if(!this->iEditorId)
        {
            this->type = UnexistantFile;
            this->checkType();
            return;
        }

        TPanelElement* elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromId(this->iEditorId);
        if(!elt)
        {
            this->type = UnexistantFile;
            this->checkType();
            return;
        }
        else
        {
            EditorPanelElement * edElt = dynamic_cast<EditorPanelElement *>(elt);
            if(edElt && edElt->getFilename().length())
            {
                this->type = EditedFile;
                this->sFileName = edElt->getFilename();
                this->checkType();
            }
        }
        return;
    }
    if( this->type == UnexistantFile )
    {
        if(!this->sFileName.length())
            return;

        if(libfile::isFile(this->sFileName))
        {
            this->type = SourcedFile;
            this->checkType();
            return;
        }
        return;
    }
}

/** indique la date/heure a laquelle le fichier a été modifié pour la dernière fois */
long FileInfo::getLastModificationTime()
{
    this->checkType();
    if( this->type == SourcedFile )
    {
        wxFileName fn(this->sFileName);
        wxDateTime lastMod;
        fn.GetTimes(NULL,&lastMod,NULL);
        return lastMod.GetTicks();
    }
    else if( this->type != UnexistantFile )
    {
        if(wxThread::IsMain())
        {
            TPanelElement* elt = NULL;
            if(this->type == EditedFile)
                elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromFile(this->sFileName);
            else
                elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromId(this->iEditorId);
            EditorPanelElement * edElt = dynamic_cast<EditorPanelElement *>(elt);
            if(edElt)
                return edElt->getLastModificationTime();
            else
                return 0;
        }
        else
        {
            wxCommandEvent event( EVENT_FILEINFO_GET_DATA, EVENT_ID_GET_MODIFICATION_TIME );
            event.SetEventObject( NULL );
            this->AddPendingEvent( event );

            while(this->bSyncDataBufferFillRequested) // attente du remplissage du buffer
                wxThread::Sleep(50);

            return this->iDataBuffer;
        }
    }
    return 0;
}

/** ajoute un fichier a la liste des dépendances */
void FileInfo::addDependancie(const wxString & _sDepName)
{
    wxString sDepName = libfile::normalize( _sDepName );
    this->dependancies.insert(sDepName);
}

/** eleve un fichier a la liste des dépendances */
void FileInfo::removeDependancie(const wxString & _sDepName)
{
    wxString sDepName = libfile::normalize( _sDepName );
    TStringSet::iterator it = this->dependancies.find(sDepName);
    if(it != this->dependancies.end())
        this->dependancies.erase(it);
}

/**
 * renvoie la taille du fichier
 */
uint FileInfo::getFileSize()
{
    if( this->type == SourcedFile )
    {
        wxFFile f(this->sFileName);
        return f.Length();
    }
    else if( this->type != UnexistantFile )
    {
        TPanelElement* elt = NULL;
        if(this->type == EditedFile)
            elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromFile(this->sFileName);
        else
            elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromId(this->iEditorId);
        EditorPanelElement * edElt = dynamic_cast<EditorPanelElement *>(elt);
        if(edElt)
            return edElt->getEditor()->getDocument()->getContentSize();
        else
            return 0;
    }
    return 0;
}

/**
 * ferme les flux ouverts pour la lecture du fichier
 */
void FileInfo::endDataParsing()
{
    wxCriticalSectionLocker lock(this->csFillingDataBuffer);
    this->sDataBuffer.clear();
    if(this->dataStream)
    {
        delete this->dataStream;
        this->dataStream = NULL;
    }
}

/**
 * prépare la lecture des données du fichier
 */
std::istream * FileInfo::initDataParsing()
{
    this->endDataParsing();
    wxCriticalSectionLocker lock(this->csFillingDataBuffer);

    if( this->type == SourcedFile )
    {
        this->dataStream = new std::ifstream((const char *)this->sFileName.fn_str(),std::ios::in);
        if(this->dataStream->fail())
        {
            delete this->dataStream;
            this->dataStream = NULL;
        }
    }
    else if( this->type != UnexistantFile )
    {
        // récupérer les données du document de manière synchronisée
        if(wxThread::IsMain())
        {
            TPanelElement* elt = NULL;
            if(this->type == EditedFile)
                elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromFile(this->sFileName);
            else
                elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromId(this->iEditorId);
            EditorPanelElement * edElt = dynamic_cast<EditorPanelElement *>(elt);
            if(edElt)
                this->sDataBuffer = WX2ISO(edElt->getEditor()->getDocument()->getFullText());
        }
        else
        {
            this->bSyncDataBufferFillRequested = true;

            wxCommandEvent event( EVENT_FILEINFO_GET_DATA, EVENT_ID_GET_DOCUMENT_CONTENT );
            event.SetEventObject( this );
            this->AddPendingEvent( event );

            this->csFillingDataBuffer.Leave();
            while(this->bSyncDataBufferFillRequested) // attente du remplissage du buffer
                wxThread::Sleep(50);
            this->csFillingDataBuffer.Enter();
        }

        this->dataStream += '\n'; // on fini la dernière ligne
        this->dataStream = new std::istringstream(this->sDataBuffer,std::ios::in | std::ios::binary);
    }

    return this->dataStream;
}

void FileInfo::initEvtHandler()
{
    this->Connect(EVENT_FILEINFO_GET_DATA,wxCommandEventHandler(FileInfo::onEventGetData),NULL,this);
}

void FileInfo::terminateEvtHandler()
{
    this->Disconnect(EVENT_FILEINFO_GET_DATA,wxCommandEventHandler(FileInfo::onEventGetData),NULL,this);
}

/** gestionnaire d'evenement pour récupérer le contenu d'un editeur de manière synchrone */
void FileInfo::onEventGetData( wxCommandEvent &event )
{
    if(event.GetId() == EVENT_ID_GET_DOCUMENT_CONTENT)
    {
        if(this->bSyncDataBufferFillRequested)
        {
            wxCriticalSectionLocker lock(this->csFillingDataBuffer);
            // on est dans le thrad principal donc pas de soucis
            TPanelElement* elt = NULL;
            if(this->type == EditedFile)
                elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromFile(this->sFileName);
            else
                elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromId(this->iEditorId);
            EditorPanelElement * edElt = dynamic_cast<EditorPanelElement *>(elt);
            if(edElt)
                this->sDataBuffer = WX2ISO(edElt->getEditor()->getDocument()->getFullText());

            this->bSyncDataBufferFillRequested = false;
        }
    }
    else if(event.GetId() == EVENT_ID_GET_MODIFICATION_TIME)
    {
        this->iDataBuffer = this->getLastModificationTime();
    }
}

/** renvoie la liste des fichiers dont dépend celui-ci (inclusions) sous la forme de chemins absolus
 * normalisés en fonction d'un contexte d'exécution
 * @param context contexte d'exécution du rendu
 */
TStringSet FileInfo::getDependancies(const FileContext & context) const
{
    wxString sExecPath(PathsListRes::normalizePath(context.getExecutionPath(),true));
    TStringSet deps;
    PathsListRes * paths = static_cast<PathsListRes *>(CurrentApplication()->getRessource(PATHS_LIST_RESSOURCE_ID));
    TStringSet::iterator it = this->dependancies.begin();
    while(it != this->dependancies.end())
    {
        if(libfile::isAbsolute((*it)))
        {
            deps.insert((*it));
        }
        else
        {
            if(paths)
            {
                wxString sAbsPath = paths->getAbsoluteFilePath( (*it), sExecPath );
                if(sAbsPath.length())
                    deps.insert(sAbsPath);
            }
            else
                deps.insert(sExecPath + (*it));
        }
        it++;
    }
    return deps;
}

/** vidange toutes les infos issues du parsing */
void FileInfo::clear()
{
    this->dependancies.erase(this->dependancies.begin(),this->dependancies.end());
}

FileInfo & FileInfo::operator=(const FileInfo & info)
{
    this->type = info.type;
    this->sFileName = info.sFileName;
    this->iEditorId = info.iEditorId;

    this->copyParsingInfo(&info);

    return (*this);
}

void FileInfo::copyParsingInfo(const FileInfo * info)
{
    this->iLastParseTime = info->iLastParseTime;
    this->dependancies = info->dependancies;
}

FileInfoLocker::FileInfoLocker(FileParserRes * _res, int iEltId)
    : info(NULL), res(_res)
{
    this->info = this->res->getFileInfoLock(iEltId);
}

FileInfoLocker::FileInfoLocker(FileParserRes * _res, wxString sFileName, bool bAutoCreate)
    : info(NULL), res(_res)
{
    this->info = this->res->getFileInfoLock(sFileName,bAutoCreate);
}

FileInfoLocker::~FileInfoLocker()
{
    if(this->info)
        this->res->releaseLock(this->info);
}

