/***************************************************************************
 *   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 "parserres.h"

#include "lib/lib_logging.h"

#include "fileinfo.h"
#include "povfileinfo.h"
#include "parsingthread.h"

#include "lib/lib_logging.h"

#include "xpe_components/mainwindow.h"
#include "xpe_components/editor/editor_panelelement.h"

#define EVENT_ID_GET_CURRENT_EDITOR_ID 1000002
#define EVENT_ID_FIRE_FILE_INFO_UPDATED 1000003

DEFINE_EVENT_TYPE(EVENT_FILEPARSERRES_SYNC);


FileParserRes::~FileParserRes()
{
}

/** decharge la ressource */
void FileParserRes::prepareRessourceUnload()
{
    this->stopParser();

    this->dumpAll();

    this->terminateEvtHandler();

    TApplicationRessource::prepareRessourceUnload();
}

/** dump toutes les entrées dans le cache */
void FileParserRes::dumpAll()
{
    wxCriticalSectionLocker(this->csFileInfo);
    TEditorInfoMap::iterator itE = this->editorsInfoMap.begin();

    while(itE != this->editorsInfoMap.end())
    {
        FileInfo * info = (*itE).second;
        TFileInfoMap::iterator itF = this->filesInfoMap.end();
        if(info->getFileName().length())
            itF = this->filesInfoMap.find(info->getFileName());
        if(itF != this->filesInfoMap.end() && (*itF).second->getEditorId() == info->getEditorId())
            this->filesInfoMap.erase(itF);

        info->dumpToCache();

        TInfoLockMap::iterator itl = this->infosLocksMap.find(info);
        if(itl != this->infosLocksMap.end())
            LOG_MESSAGE(wxString(wxT("Le fichier ")) + info->getFileName() + wxT("(") + wxString::Format(wxT("%d"),info->getEditorId()) + wxT(") est locke. On ne libere pas la memoire pour ne pas planter."),logging::_ERROR_);
        else
            delete info;

        itE++;
    }

    this->editorsInfoMap.erase(this->editorsInfoMap.begin(),this->editorsInfoMap.end());
}

/** démarre le thread de parsing */
void FileParserRes::startParser()
{
    if(this->isParserRunning())
        return;

    wxCriticalSectionLocker(this->csParser);
    if(!this->thread)
        this->thread = new ParsingThread(this);

    wxThreadError err = this->thread->Create();
    if(err == wxTHREAD_NO_RESOURCE)
    {
        LOG_MESSAGE("Pas assez de ressources pour créer un nouveau thread",logging::_ERROR_);
    }

    if(err != wxTHREAD_NO_ERROR)
    {
        this->stopParser();
        return;
    }

    this->thread->SetPriority(WXTHREAD_MIN_PRIORITY);
    err = this->thread->Run();
    if(err == wxTHREAD_NO_RESOURCE)
    {
        LOG_MESSAGE("Pas assez de ressources pour exécuter le nouveau thread",logging::_ERROR_);
    }

    if(err != wxTHREAD_NO_ERROR)
    {
        this->stopParser();
    }

}
/** pause le thread de parsing */
void FileParserRes::pauseParser()
{
    if((!this->isParserRunning()) || this->isParserPaused())
        return;

    wxCriticalSectionLocker(this->csParser);
    if(!this->thread)
        return;

    this->thread->Pause();
}
/** relance le thread de parsing */
void FileParserRes::continueParser()
{
    if((!this->isParserRunning()) || (!this->isParserPaused()))
        return;

    wxCriticalSectionLocker(this->csParser);
    if(!this->thread)
        return;

    this->thread->Resume();
}
/** arrête le thread de parsing */
void FileParserRes::stopParser()
{
    wxCriticalSectionLocker(this->csParser);
    if(!this->thread)
        return;

    while(this->mutexThreadStop.TryLock() != wxMUTEX_NO_ERROR)
        CurrentApplication()->Yield();

    this->thread->Delete();
    delete this->thread;
    this->thread = NULL;
    this->mutexThreadStop.Unlock();
}

void FileParserRes::haltThread()
{
    this->stopParser();
}

/** indique si le thread a été démarré */
bool FileParserRes::isParserRunning()
{
    wxCriticalSectionLocker(this->csParser);
    if(!this->thread)
        return false;

    return this->thread->IsAlive();
}

/** indique si le thread a été démarré mais mis en pause */
bool FileParserRes::isParserPaused()
{
    wxCriticalSectionLocker(this->csParser);
    if(!this->thread)
        return false;

    return this->thread->IsPaused();
}


/** ajoute un écouteur de modifs d'infos des fichiers */
bool FileParserRes::addListener(FileParserResListener * l)
{
    return this->listeners.insert(l).second;
}

/** supprime un écouteur de modifs d'infos des fichiers */
bool FileParserRes::removeListener(FileParserResListener * l)
{
    return this->listeners.erase(l) != 0;
}

/** dispatche un évènement aux écouteurs de modifs */
void FileParserRes::fireFileInfoUpdated(FileInfo * file)
{
    if(wxThread::IsMain())
    {
        TListenersList::iterator it = this->listeners.begin();
        while(it != this->listeners.end())
        {
            (*it)->fileInfoUpdated(this,file);
            it++;
        }
    }
    else
    {
        wxCriticalSectionLocker locker(this->csDataSync);
        wxCriticalSectionLocker locker2(this->csDataSync2);
        if(this->mutexThreadStop.TryLock() != wxMUTEX_NO_ERROR)
            return;

        this->updatedInfo = file;
        wxCommandEvent event( EVENT_FILEPARSERRES_SYNC, EVENT_ID_FIRE_FILE_INFO_UPDATED );
        event.SetEventObject( this );
        this->AddPendingEvent( event );

        this->csDataSync2.Enter();

        this->mutexThreadStop.Unlock();
    }
}

/**
 * renvoie les information concernant un fichier et les verrouille pour éviter toute libération mémoire inopinée
 * @param bCreateUnexistant créer l'entité relative au fichier si elle n'existe pas
 * @return NULL si aucune info sur ce fichier n'existe
 * @see releaseLock()
 */
FileInfo * FileParserRes::getFileInfoLock(const wxString & _sFileName, bool bCreateUnexistant)
{
    if(!_sFileName.length())
        return NULL;
    wxCriticalSectionLocker(this->csFileInfo);
    return this->_getFileInfo(libfile::normalize(_sFileName), true);
}

/**
 * renvoie les information concernant un fichier
 * @param bCreateUnexistant créer l'entité relative au fichier si elle n'existe pas
 * sans gestion de Lock
 */
FileInfo * FileParserRes::_getFileInfo(const wxString & _sFileName, bool bLock, bool bCreateUnexistant)
{
    if(!_sFileName.length())
        return NULL;

    TFileInfoMap::iterator it = this->filesInfoMap.find(_sFileName);
    FileInfo * info = NULL;
    if(it == this->filesInfoMap.end())
    {
        if(!bCreateUnexistant)
            return NULL;

        info = this->createInfoFor( _sFileName );
        info->readFromCache();
        info->checkType();
        this->filesInfoMap[info->getFileName()] = info;
        if(info->getEditorId())
            this->editorsInfoMap[info->getEditorId()] = info;
    }
    else
        info = (*it).second;
    if(bLock)
    {
        TInfoLockMap::iterator itl = this->infosLocksMap.find(info);
        if(itl == this->infosLocksMap.end())
            this->infosLocksMap[info] = 1;
        else
            this->infosLocksMap[info] = this->infosLocksMap[info] + 1;
    }
    return info;
}
/** renvoie les information concernant un fichier */
FileInfo * FileParserRes::getFileInfoLock(const int iEditorId)
{
    if(!iEditorId)
        return NULL;

    wxCriticalSectionLocker(this->csFileInfo);
    TEditorInfoMap::iterator it = this->editorsInfoMap.find(iEditorId);
    if(it == this->editorsInfoMap.end())
        return NULL;
    FileInfo * info = (*it).second;
    TInfoLockMap::iterator itl = this->infosLocksMap.find(info);
    if(itl == this->infosLocksMap.end())
        this->infosLocksMap[info] = 1;
    else
        this->infosLocksMap[info] = (*itl).second + 1;
    return info;
}
/**
 * dévérouille un fichier vérouillé préalablement par getFileInfoLock().
 * Il est nécessaire de libérer un fichier autant de fois qu'on l'a locké
 * @return true si le fichier n'est plus locké
 */
bool FileParserRes::releaseLock(FileInfo * info)
{
    wxCriticalSectionLocker(this->csFileInfo);
    TInfoLockMap::iterator itl = this->infosLocksMap.find(info);
    if(itl == this->infosLocksMap.end())
        return true;
    else
    {
        int iCurrentLocks = (*itl).second - 1;
        if(iCurrentLocks)
            this->infosLocksMap[info] = iCurrentLocks;
        else
            this->infosLocksMap.erase(itl);
        return iCurrentLocks == 0;
    }
}

/** mets a jours les infos concernant un fichier avec le contenu de celles passées en paramètre */
void FileParserRes::updateFileInfo(const FileInfo * info)
{
    wxCriticalSectionLocker(this->csFileInfo);
    FileInfo * infoptr = NULL;
    TEditorInfoMap::iterator it = info->getEditorId() ? this->editorsInfoMap.find(info->getEditorId()) : this->editorsInfoMap.end();
    if(it == this->editorsInfoMap.end())
    {
        TFileInfoMap::iterator it2 = this->filesInfoMap.find(info->getFileName());
        if(it2 == this->filesInfoMap.end())
            return;
        infoptr = (*it2).second;
    }
    else
        infoptr = (*it).second;

    if(infoptr)
        infoptr->copyParsingInfo( info );

    this->fireFileInfoUpdated(infoptr);
}

/**
 * mets a jour les informations concernant le fichier en cours de modification dans l'éditeur mentionné
 * (ajout d'un editeur, suppression d'un éditeur, changement de nom de fichier)
 */
void FileParserRes::updateEditorInfo(const int iEditorId)
{
    if(!iEditorId)
        return;

    wxCriticalSectionLocker(this->csFileInfo);
    FileInfo * info = NULL;
    EditorPanelElement * editor = dynamic_cast<EditorPanelElement *>(static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getTabFromId(iEditorId));
    TEditorInfoMap::iterator it = this->editorsInfoMap.find(iEditorId);
    if(it == this->editorsInfoMap.end() && editor && this->mightHandleEditor( editor ))
    {
        info = this->createInfoFor(iEditorId);
        info->readFromCache();
    }
    else if(it != this->editorsInfoMap.end() && (!editor))
    {
        this->removeFileInfo((*it).second);
        it = this->editorsInfoMap.end();
        this->_dumpUnusedInfos();
    }
    else if (it != this->editorsInfoMap.end() && editor)
    {
        info = (*it).second;
        if(info->getFileName().length() && info->getFileName() != editor->getFilename())
        {
            TFileInfoMap::iterator itF = this->filesInfoMap.find(info->getFileName());
            if(itF != this->filesInfoMap.end())
                this->filesInfoMap.erase(itF);

            info->dumpToCache();

            info->setFileName( editor->getFilename() );
        }
        else if((!info->getFileName().length()) && editor->getFilename().length())
        {
            info->setFileName( editor->getFilename() );
        }
    }
    if(info == NULL)
        return;
    info->checkType();

    if(info->getFileName().length())
    {
        TFileInfoMap::iterator itF = this->filesInfoMap.find(info->getFileName());
        if(itF == this->filesInfoMap.end())
        {
            this->filesInfoMap[info->getFileName()] = info;
        }
    }
    if(info->getEditorId())
    {
        TEditorInfoMap::iterator itE = this->editorsInfoMap.find(info->getEditorId());
        if(itE == this->editorsInfoMap.end())
        {
            this->editorsInfoMap[info->getEditorId()] = info;
        }
    }
}

/** scanne un fichier (si nécessaire) sans le passer par la file de traitement ni par le thread */
void FileParserRes::requestImmediateScan(const wxString & _sFileName)
{
    if(!_sFileName.length())
        return;
    FileInfo * info = this->getFileInfoLock( _sFileName );
    this->requestImmediateScan(info);
    if(info)
        this->releaseLock(info);
}

/** scanne le contenu d'un éditeur (si nécessaire) sans le passer par la file de traitement ni par le thread */
void FileParserRes::requestImmediateScan(const int iEditorId)
{
    if(!iEditorId)
        return;
    FileInfo * info = this->getFileInfoLock( iEditorId );
    this->requestImmediateScan(info);
    if(info)
        this->releaseLock(info);
}

/** scanne le contenu référencé par un objet d'info sans le passer par la file de traitement ni par le thread */
void FileParserRes::requestImmediateScan(FileInfo * info)
{
    if(!info)
        return;

    try
    {
        if(ParsingThread::requireScan( info ))
        {
            this->pauseParser();
            ParsingThread::scanFile( info, this );
            this->continueParser();
        }
    }
    catch( ... )
    {
        LOG_MESSAGE("Unexpected exception",logging::_ERROR_);
    }
}

/**
 * recherche les fichiers pour lesquels des infos sont stockées en mémoire alors que ce n'est plus
 * nécessaire et écris ces informations dans le cache sur support persistant
 */
void FileParserRes::dumpUnusedInfos()
{
    wxCriticalSectionLocker(this->csFileInfo);
    this->_dumpUnusedInfos();
}

/**
 * recherche les fichiers pour lesquels des infos sont stockées en mémoire alors que ce n'est plus
 * nécessaire et écris ces informations dans le cache sur support persistant
 */
void FileParserRes::_dumpUnusedInfos()
{
    TStringSet requiredFiles;

    // récup de tous les fichiers requis
    TEditorInfoMap::iterator itE = this->editorsInfoMap.begin();
    while(itE != this->editorsInfoMap.end())
    {
        requiredFiles.insert((*itE).second->getFileName());
        TStringSet localDeps = this->getFullDependanciesList( (*itE).first );
        requiredFiles.insert(localDeps.begin(),localDeps.end());
        itE++;
    }

    std::deque<FileInfo *> infosToRemove;
    // vérif de l'utilisation de chaque fichier
    TFileInfoMap::iterator itF = this->filesInfoMap.begin();
    while(itF != this->filesInfoMap.end())
    {
        if(requiredFiles.find((*itF).first) == requiredFiles.end())
            infosToRemove.push_back((*itF).second);
        itF++;
    }

    std::deque<FileInfo *>::iterator itI = infosToRemove.begin();
    while(itI != infosToRemove.end())
    {
        this->removeFileInfo(*itI);
        itI++;
    }
}

/** supprime les infos concernant un fichier de la mémoire et supprime l'élément passé en parametre */
void FileParserRes::removeFileInfo(FileInfo * info)
{
    if(!info)
        return;

    TInfoLockMap::iterator itl = this->infosLocksMap.find(info);
    if(itl != this->infosLocksMap.end())
        return;

    TEditorInfoMap::iterator itE = this->editorsInfoMap.end();
    if(info->getEditorId())
        itE = this->editorsInfoMap.find(info->getEditorId());
    if(itE != this->editorsInfoMap.end())
        this->editorsInfoMap.erase(itE);

    TFileInfoMap::iterator itF = this->filesInfoMap.end();
    if(info->getFileName().length())
        itF = this->filesInfoMap.find(info->getFileName());
    if(itF != this->filesInfoMap.end() && (*itF).second->getEditorId() == info->getEditorId())
        this->filesInfoMap.erase(itF);

    info->dumpToCache();

    delete info;
}

/** recupère la liste complète des inclusions et sous inclusions pour un editeur */
TStringSet FileParserRes::getFullDependanciesList(int iEditorId)
{
    wxCriticalSectionLocker(this->csFileInfo);
    return this->_getFullDependanciesList(iEditorId);
}

/**
 * recupère la liste complète des inclusions et sous inclusions pour un editeur
 * Ne gère pas la synchro sur lock
 */
TStringSet FileParserRes::_getFullDependanciesList(int iEditorId)
{
    TStringSet depsList;
    TStringSet depsToRecurseIn;

    TEditorInfoMap::const_iterator it = this->editorsInfoMap.find(iEditorId);
    if(it == this->editorsInfoMap.end())
        return depsList;

    wxString sEditorFile = (*it).second->getFileName(); // pour ne pas la remettre dans les dépendances
    FileContext context(libfile::dirname( sEditorFile ));

    depsToRecurseIn = (*it).second->getDependancies(context);
    depsToRecurseIn.erase(sEditorFile);
    while(depsToRecurseIn.size())
    {
        wxString sDep = *depsToRecurseIn.begin();
        depsList.insert(sDep);
        depsToRecurseIn.erase(sDep);

        FileInfo * info = this->_getFileInfo(sDep,false);
        if(!info)
            continue;

        TStringSet includes = info->getDependancies( context );
        TStringSet::iterator itInc = includes.begin();
        while(itInc != includes.end())
        {
            if( ((*itInc) != sEditorFile) && (depsList.find(*itInc) == depsList.end()) )
                depsToRecurseIn.insert(*itInc);
            itInc++;
        }
    }

    return depsList;
}

/** renvoie l'id de l'éditeur qui est actuellement au premier plan */
int FileParserRes::getCurrentEditorId()
{
    if(wxThread::IsMain())
    {
        TPanelElement* elt = static_cast<XPEMainWindow *>(CurrentApplication()->getMainWindow())->getCenterPanel()->getFirstVisibleElement();
        if(elt)
            return elt->getID();
        else
            return 0;
    }
    else
    {
        wxCriticalSectionLocker locker(this->csDataSync);
        wxCriticalSectionLocker locker2(this->csDataSync2);
        if(this->mutexThreadStop.TryLock() != wxMUTEX_NO_ERROR)
            return 0;

        this->iData = 0;
        wxCommandEvent event( EVENT_FILEPARSERRES_SYNC, EVENT_ID_GET_CURRENT_EDITOR_ID );
        event.SetEventObject( this );
        this->AddPendingEvent( event );

        this->csDataSync2.Enter();

        this->mutexThreadStop.Unlock();
        return this->iData;
    }
}

/** gestionnaire d'evenement pour récupérer des infos du thread principal de maniere synchrone */
void FileParserRes::onEventSync( wxCommandEvent &event )
{
    if(event.GetId() == EVENT_ID_GET_CURRENT_EDITOR_ID)
    {
        this->iData = 0;
        if(getMainWindowCenterPanel())
        {
            TPanelElement* elt = getMainWindowCenterPanel()->getFirstVisibleElement();
            if(elt)
                this->iData = elt->getID();
        }
    }
    else if(event.GetId() == EVENT_ID_FIRE_FILE_INFO_UPDATED)
    {
        this->fireFileInfoUpdated(this->updatedInfo);
    }
    this->csDataSync2.Leave();
}

void FileParserRes::initEvtHandler()
{
    this->Connect(EVENT_FILEPARSERRES_SYNC,wxCommandEventHandler(FileParserRes::onEventSync),NULL,this);
}

void FileParserRes::terminateEvtHandler()
{
    this->Disconnect(EVENT_FILEPARSERRES_SYNC,wxCommandEventHandler(FileParserRes::onEventSync),NULL,this);
}
