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

#include "tmainwindow.h"
#include <wx/filename.h>
#include <wx/debugrpt.h>

#include "lib/tinyxml/tinyxml.h"
#include "lib/lib_logging.h"
#include "lib/lib_file.h"

#include "tapplwindow.h"
#include "components/stdgui/tbitmap.h"

#define CONFIG_FILENAME "config.xml"
#define UTRQ_FILENAME "unsatisfied_translations_queries.xml"

TApplication * CurrentApplication() { return dynamic_cast<TApplication *>(wxTheApp); }


void TApplicationRessource::prepareRessourceUnload()
{
    TRessourceListenerList::iterator it = this->listeners.begin();
    TRessourceListenerList::iterator itE = this->listeners.end();

    while(it != itE)
    {
        (*it)->ressourceUnloading(this);
        it++;
    }
}

bool TApplicationRessource::addRessourceListener(TRessourceListener * l)
{
    return this->listeners.insert(l).second;
}

bool TApplicationRessource::removeRessourceListener(TRessourceListener * l)
{
    return this->listeners.erase(l) != 0;
}

void TApplicationRessource::fireRessourceEvent(const int iEvent)
{
    TRessourceListenerList::iterator it = this->listeners.begin();
    TRessourceListenerList::iterator itE = this->listeners.end();

    while(it != itE)
    {
        (*it)->ressourceEvent(this, iEvent);
        it++;
    }
}




TApplication::~TApplication()
{
    this->mainWindow = NULL; // si on en arrive ici, la fenetre principale a été détruite depuis longtemps
    TRessourcesMap::iterator it = this->resMap.begin();
    while(it != this->resMap.end())
    {
        delete (*it).second;
        it++;
    }
    this->resMap.erase(this->resMap.begin(), this->resMap.end());

    TBitmapsMap::iterator itBmp = this->bmpMap.begin();
    while(itBmp != this->bmpMap.end())
    {
        delete (*itBmp).second;
        itBmp++;
    }
    this->bmpMap.erase(this->bmpMap.begin(), this->bmpMap.end());
}

TMainWindow * TApplication::getMainWindow()
{
    if(this->bInClosingPhase)
        return NULL; // la fenetre ne va pas tarder a etre supprimée : on en refuse l'acces

    if(this->mainWindow == NULL)
    {
        this->mainWindow = this->buildMainWindow();
        this->registerWindow(this->mainWindow);
        this->mainWindow->endInit();
    }
    return this->mainWindow;
}

bool TApplication::OnInit()
{
//    ::wxHandleFatalExceptions(true);
    try
    {
        if(wxApp::OnInit() && this->start())
        {
            this->getMainWindow();
            if(!this->loadParameters())
                this->loadDefaultParameters();
            this->getMainWindow()->Show(true);
            this->getMainWindow()->Show(this->bMainWindowVisible);
            if(this->bMainWindowVisible)
                this->SetTopWindow(this->getMainWindow());

            this->applicationStarted();

            return true;
        }
    }
    catch( ... )
    {}
    this->applicationStartupFailed();
    return false;
}

int TApplication::OnExit()
{
    this->stop();
    this->dumpUnsatisfiedTranslationsQueries();
    return wxApp::OnExit();
}



/** defini si la fenetre principale est visible ou non */
void TApplication::setMainWindowVisible( bool b )
{
    this->bMainWindowVisible = b;
    this->getMainWindow()->Show(b);
}

/** renvoie le chemin a utiliser pour sauvegarder la config de l'appli, termine par un separateur */
wxString TApplication::getApplConfigPath() const
{
    wxString sEnv;
    wxGetEnv(wxT("XDG_CONFIG_HOME"),&sEnv);
    if(!sEnv)
    {
        sEnv = wxFileName::GetHomeDir();
        sEnv += wxFileName::GetPathSeparator();
        sEnv += wxT(".config");
    }
    if(sEnv.Last() != wxFileName::GetPathSeparator())
        sEnv += wxFileName::GetPathSeparator();
    wxString sApplSubDir = this->getName();
    sApplSubDir.Replace(wxT(" "),wxT("_"));
    sEnv += sApplSubDir;
    sEnv += wxFileName::GetPathSeparator();
    return sEnv;
}

/** renvoie le chemin a utiliser pour sauvegarder les données de l'appli, termine par un separateur */
wxString TApplication::getApplDataPath() const
{
    wxString sEnv;
    wxGetEnv(wxT("XDG_DATA_HOME"),&sEnv);
    if(!sEnv)
    {
        sEnv = wxFileName::GetHomeDir();
        sEnv += wxFileName::GetPathSeparator();
        sEnv += wxT(".local");
        sEnv += wxFileName::GetPathSeparator();
        sEnv += wxT("share");
    }
    if(sEnv.Last() != wxFileName::GetPathSeparator())
        sEnv += wxFileName::GetPathSeparator();
    wxString sApplSubDir = this->getName();
    sApplSubDir.Replace(wxT(" "),wxT("_"));
    sEnv += sApplSubDir;
    sEnv += wxFileName::GetPathSeparator();
    return sEnv;
}

/** renvoie le chemin a utiliser pour sauvegarder le cache de l'appli, termine par un separateur */
wxString TApplication::getApplCachePath() const
{
    wxString sEnv;
    wxGetEnv(wxT("XDG_CACHE_HOME"),&sEnv);
    if(!sEnv)
    {
        sEnv = wxFileName::GetHomeDir();
        sEnv += wxFileName::GetPathSeparator();
        sEnv += wxT(".cache");
    }
    if(sEnv.Last() != wxFileName::GetPathSeparator())
        sEnv += wxFileName::GetPathSeparator();
    wxString sApplSubDir = this->getName();
    sApplSubDir.Replace(wxT(" "),wxT("_"));
    sEnv += sApplSubDir;
    sEnv += wxFileName::GetPathSeparator();
    return sEnv;
}

/** renvoie le chemin a utiliser pour sauvegarder les parametres de l'appli terminé par un séparateur */
wxString TApplication::getApplPath() const
{
    wxString sPath = wxFileName::GetHomeDir();
    sPath += wxFileName::GetPathSeparator();
    sPath += wxT(".");
    wxString sApplSubDir = this->getName();
    sApplSubDir.Replace(wxT(" "),wxT("_"));
    sPath += sApplSubDir;
    sPath += wxFileName::GetPathSeparator();
    return sPath;
}

/** decharge les ressources (ne les supprime pas) */
void TApplication::unloadAllRessources()
{
    TRessourcesMap::iterator it = this->resMap.begin();
    while(it != this->resMap.end())
    {
        (*it).second->prepareRessourceUnload();
        it++;
    }
}


/** lis les parametres et les propage aux composants */
bool TApplication::loadParameters()
{
    wxString sFilename = this->getApplConfigPath() + wxT(CONFIG_FILENAME);

    TiXmlDocument doc((const char *)sFilename.fn_str());
    bool bLoadXML = doc.LoadFile();

    if(!bLoadXML)
    {
        LOG_MESSAGE("Impossible de charger le fichier de configuration.",logging::_INFO_);
        return false;
    }

    TiXmlElement * elt = NULL;
    if(doc.RootElement())
    {
        // lecture des parametres d'application
        elt = doc.RootElement()->FirstChildElement(XMLCC_NODE_APP_PARAMS);
        if(elt)
            static_cast<XmlConfiguredComponent *>(this)->loadParameters(elt);

        // lecture des fenetres
        elt = doc.RootElement()->FirstChildElement(XMLCC_NODE_APP_WIN_ROOT);
        if(elt)
        {
            elt = elt->FirstChildElement(XMLCC_NODE_WINDOW);
            while(elt)
            {
                const char * szAttr = elt->Attribute( XMLCC_ATTR_WINDOW_ID );
                if((!szAttr) || (!szAttr[0]))
                        return false;
                wxString sId(ISO2WX(szAttr));

                TApplicationWindow * window = this->getWindowById(sId);
                if(window)
                {
                    TiXmlElement * winParams = elt->FirstChildElement(XMLCC_NODE_WINDOW_PARAMS);
                    if(winParams)
                    {
                        if(!window->loadParameters(winParams))
                            return false;
                    }
                    else
                        window->loadDefaultParameters();

                    TiXmlElement * winPanels = elt->FirstChildElement(XMLCC_NODE_WINDOW_PANELS);
                    if(winPanels)
                    {
                        if(!window->loadPanels(winPanels))
                            return false;
                    }
                }

                elt = elt->NextSiblingElement(XMLCC_NODE_WINDOW);
            }
        }
    }

    return true;
}

/** ecris les parametres de toute l'appli sur disque dans un rep nomme ~/.ApplName/config.xml */
void TApplication::saveParameters()
{
    if(!this->mightBeSaved())
        return;

    wxString sFilename = this->getApplConfigPath() + wxT(CONFIG_FILENAME);

    if(!libfile::mkdir(libfile::dirname(sFilename)))
    {
        LOG_MESSAGE(wxString(wxT("Impossible de creer le repertoire de configuration de l'application : ")) + libfile::dirname(sFilename),logging::_INFO_);
        return;
    }

    TiXmlDocument doc((const char *)sFilename.fn_str());

    doc.LinkEndChild(new TiXmlElement(XMLCC_NODE_APPLICATION));

    TiXmlElement * elt = doc.RootElement();
    if(elt)
    {
        // sauvegarde des parametres d'application
        TiXmlElement * paramsElt = static_cast<TApplication *>(this)->getParameters();
        if(paramsElt)
            elt->LinkEndChild(paramsElt);

        // sauvegarde des fenetres

        TiXmlElement * windowsElt = new TiXmlElement(XMLCC_NODE_APP_WIN_ROOT);
        elt->LinkEndChild(windowsElt);

        TIdWindowMap::iterator itB = this->windowsMap.begin();
        TIdWindowMap::iterator itE = this->windowsMap.end();
        while(itB != itE)
        {
            if((*itB).second->mightBeSaved())
            {
                elt = new TiXmlElement(XMLCC_NODE_WINDOW);

                elt->SetAttribute( XMLCC_ATTR_WINDOW_ID, (const char *)(*itB).second->getId().fn_str() );

                TiXmlElement * winParams = (*itB).second->getParameters();
                if(winParams)
                    elt->LinkEndChild(winParams);

                TiXmlElement * winPanels = (*itB).second->getPanelsNode();
                if(winPanels)
                {
                    elt->LinkEndChild(winPanels);
                }

                windowsElt->LinkEndChild(elt);
                elt = NULL;
            }

            itB++;
        }

    }

    if(!doc.SaveFile())
    {
        LOG_MESSAGE(wxString(wxT("Impossible d'ecrire le fichier de configuration de l'application : ")) + sFilename,logging::_INFO_);
    }
}

/** renvoie un pointeur sur une fenetre a partir de son ID */
TApplicationWindow * TApplication::getWindowById(const wxString & sId) const
{
    if(this->bInClosingPhase)
        return NULL; // la fenetre ne va pas tarder a etre supprimée : on en refuse l'acces

    TIdWindowMap::const_iterator it = this->windowsMap.find(sId);
    if(it == this->windowsMap.end())
        return NULL;
    return (*it).second;
}

/** enregistre une fenetre dans l'application pour pouvoir la reservir via le getWindowById */
bool TApplication::registerWindow(TApplicationWindow * window)
{
    if(window == NULL)
        return false;

    TIdWindowMap::iterator it = this->windowsMap.find(window->getId());
    if(it != this->windowsMap.end())
        return false;

    this->windowsMap[window->getId()] = window;
    return true;
}

/** supprime une référence a une fenetre */
bool TApplication::unregisterWindow(const wxString & sId)
{
    TIdWindowMap::iterator it = this->windowsMap.find(sId);
    if(it == this->windowsMap.end())
        return false;

    this->windowsMap.erase(it);
    return true;
}

/** renvoie un pointeur sur une ressource publiée de manière globale a l'applilcation */
TApplicationRessource * TApplication::getRessource(int iResId) const
{
    TRessourcesMap::const_iterator it = this->resMap.find(iResId);
    if(it != this->resMap.end())
        return (*it).second;

    return NULL;
}

/**
 * publie une ressource dans l'application. Une ressource publiée ne doit plus etre deletée
 * avant la fin de l'application.
 * @returns true en cas de succes, false autrement (typiquement, l'Id existe déjà)
 */
bool TApplication::publishRessource(int iResId, TApplicationRessource * res)
{
    if(!res)
        return false;

    TRessourcesMap::iterator it = this->resMap.find(iResId);
    if(it != this->resMap.end())
        return false;

    this->resMap[iResId] = res;
    return true;
}

/** traduit un texte a partir des informations de traduction disponnibles */
wxString TApplication::translate(const wxString & sContext, const wxString & sToTranslate)
{
    static const wxString defaultContext;

    TTranslationMap & context = this->translations[sContext];
    TTranslationMap::iterator it = context.find(sToTranslate);
    if(it == context.end())
    {
        it = this->translations[defaultContext].find(sToTranslate);
        if(it == this->translations[defaultContext].end())
        {
            // on memorise la valeur qui a ete retournee mais n'existe pas dans le fichier de traductions
            context.insert(TTranslationMap::value_type(sToTranslate,sToTranslate));
            this->unsatisfiedTranslationQueries[sContext].insert(sToTranslate);
            return sToTranslate;
        }
    }
    return (*it).second;
}

/** defini le fichier de traduction a utiliser */
void TApplication::setTranslationFile(const wxString & sFile)
{
    this->sTranslationFile = sFile;
    this->loadTranslations();
}

#define XMLTR_ROOT_ELT "context"
#define XMLTR_CONTEXT_ELT "context"
#define XMLTR_CONTEXT_NAME_ATTR "name"
#define XMLTR_TR_ELT "tr"
#define XMLTR_TR_TOKEN_ATTR "token"
#define XMLTR_TR_TRANSLATION_ATTR "translation"

/** charge le fichier de traductions */
void TApplication::loadTranslations()
{
    TiXmlDocument doc((const char *)sTranslationFile.fn_str());
    bool bLoadXML = doc.LoadFile();

    if(!bLoadXML)
    {
        LOG_MESSAGE("Impossible de charger le fichier de traduction.",logging::_INFO_);
        return;
    }

    if(doc.RootElement())
    {
        // vidange des traductions échouées
        this->unsatisfiedTranslationQueries.erase(
                this->unsatisfiedTranslationQueries.begin(),
                this->unsatisfiedTranslationQueries.end());

        // RAZ du systeme de traduction
        this->translations.erase(this->translations.begin(),this->translations.end());

        // lecture des parametres d'application
        TiXmlElement * ctxElt = doc.RootElement()->FirstChildElement();
        while(ctxElt)
        {
            const char * szAttr = ctxElt->Attribute( XMLTR_CONTEXT_NAME_ATTR );
            if((!szAttr)/* || (!szAttr[0])*/)
            {
                LOG_MESSAGE("Fichier de traduction corrompu",logging::_INFO_);
                return;
            }

            wxString sContext(UTF82WX(szAttr));

            TiXmlElement * trElt = ctxElt->FirstChildElement();
            while(trElt)
            {
                const char * szToken = trElt->Attribute( XMLTR_TR_TOKEN_ATTR );
                const char * szTrans = trElt->Attribute( XMLTR_TR_TRANSLATION_ATTR );
                if((!szToken) || (!szToken[0]) || (!szTrans) /*|| (!szTrans[0])*/)
                {
                    LOG_MESSAGE("Fichier de traduction corrompu",logging::_INFO_);
                    return;
                }

                if(szTrans[0])
                {
                    wxString sToken(UTF82WX(szToken));
                    if(!sToken.length())
                        sToken = ISO2WX(szToken);
    
                    wxString sTrans(UTF82WX(szTrans));
                    if(!sTrans.length())
                        sTrans = ISO2WX(szTrans);
    
                    this->translations[sContext][sToken] = sTrans;
                }

                trElt = trElt->NextSiblingElement();
            }
            ctxElt = ctxElt->NextSiblingElement();
        }
    }

}

/** ecris un fichier contenant les traductions n'ayant pas pu etre satisfaites */
void TApplication::dumpUnsatisfiedTranslationsQueries() const
{
    wxString sFilename = this->getApplDataPath() + wxT(UTRQ_FILENAME);

    if(!unsatisfiedTranslationQueries.size())
    {
        wxRemoveFile(sFilename);
        return;
    }

    TTranslationQueries::const_iterator it = unsatisfiedTranslationQueries.begin();
    TTranslationQueries::const_iterator itE = unsatisfiedTranslationQueries.end();

    if(!libfile::mkdir(libfile::dirname(sFilename)))
    {
        LOG_MESSAGE(wxString(wxT("Impossible de creer le repertoire de stockage de l'application : ")) + libfile::dirname(sFilename),logging::_INFO_);
        return;
    }

    TiXmlDocument doc((const char *)sFilename.fn_str());

    doc.LinkEndChild(new TiXmlElement(XMLTR_ROOT_ELT));

    while (it != itE)
    {
        wxString sContext = (*it).first;
        TiXmlElement * ctxElt = new TiXmlElement(XMLTR_CONTEXT_ELT);
        ctxElt->SetAttribute(XMLTR_CONTEXT_NAME_ATTR,(const char *)sContext.fn_str());
        doc.RootElement()->LinkEndChild(ctxElt);

        const TStringsSet & tokens = (*it).second;
        TStringsSet::const_iterator itQ = tokens.begin();
        TStringsSet::const_iterator itQE = tokens.end();

        while (itQ != itQE)
        {
            TiXmlElement * trElt = new TiXmlElement(XMLTR_TR_ELT);
            trElt->SetAttribute(XMLTR_TR_TOKEN_ATTR,(const char *)(*itQ).fn_str());
            trElt->SetAttribute(XMLTR_TR_TRANSLATION_ATTR,"");
            ctxElt->LinkEndChild(trElt);

            itQ++;
        }

        it++;
    }

    doc.SaveFile();
}

/** ajoute un bitmap a la liste des bitmaps permanents */
bool TApplication::addBitmap(int iBitmapID, TBitmap * bmp)
{
    TBitmapsMap::iterator it = this->bmpMap.find(iBitmapID);
    if(it != this->bmpMap.end())
    {
        LOG_MESSAGE(wxString::Format(wxT("addBitmap : Bitmap ID (%d) already in use."), iBitmapID),logging::_FATAL_);
        return false;
    }

    this->bmpMap[iBitmapID] = bmp;
    return true;
}

/** renvoie un pointeur vers un bitmap permanent */
const TBitmap * TApplication::getBitmap(int iBitmapID) const
{
    TBitmapsMap::const_iterator it = this->bmpMap.find(iBitmapID);
    if(it != this->bmpMap.end())
        return (*it).second;

    return NULL;
}


/** gestion des exceptions inattendues */
void TApplication::OnFatalException()
{
/*    wxDebugReport report;
    wxDebugReportPreviewStd preview;

    report.AddCurrentContext();  // could also use AddAll()
//    report.AddCurrentDump();     // to do both at once

    if ( preview.Show(report) )
        report.Process();*/
}


/** renvoie un identifiant unique de menu */
int TApplication::getUniqueMenuId()
{
    static long iIdCount = MENU_ID_STATIC_FIRST + 1024;
    static wxCriticalSection UniqueMenuIdSection;

    wxCriticalSectionLocker locker(UniqueMenuIdSection); // synchronise l'acces a la fonction entre les threads

    int i = (++iIdCount);

    return i;
}




