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

#include <wx/datetime.h>

#include "lib/lib_string.h"

/**
 * prépare le parser pour scanner un fichier dont on passe les infos actuelles en paramètre
 * @param info informations actuellement connues sur le fichier (surtout le nom, l'id et le type)
 * @return true si le parser peut gérer le fichier demandé et que le parser n'est pas actuellement en fonction,
 *          false dans le cas contraire
 */
bool PovParser::prepareParserImpl(const FileInfo * _info)
{
    const PovFileInfo * info = dynamic_cast<const PovFileInfo *>(_info);
    if(!info)
        return false;
    this->infos = (*info);
    this->infos.clear();
    return true;
}

/**
 * récupère les infos collectées lors du dernier parsing (uniquement en état Finished).
 * Cette méthode est apellée par getParsedInfo uniquement si le parser est en état Finished.
 * @return NULL si il n'y a pas eu de parsing depuis la création de l'instance ou la dernière préparation
 */
FileInfo * PovParser::getParsedInfoImpl()
{
    return &(this->infos);
}

#define STATE_DEFAULT 0
#define STATE_KEYWORD 1
#define STATE_MACRO 2
#define STATE_MACRO_READ 3
#define STATE_MACRO_NAME 4
#define STATE_MACRO_EONAME 5
#define STATE_MACRO_PARAMS_START 6
#define STATE_MACRO_PARAMETER 7
#define STATE_MACRO_AFTER_PARAM 8
#define STATE_DECLARE 9
#define STATE_DECLARE_READ 10
#define STATE_DECLARE_NAME 11
#define STATE_LOCAL 12
#define STATE_LOCAL_READ 13
#define STATE_LOCAL_NAME 14
#define STATE_INCLUDE 15
#define STATE_INCLUDE_READ 16
#define STATE_INCLUDE_FILE 17
#define STATE_COMMENT_START 18
#define STATE_LINE_COMMENT 19
#define STATE_NAMED_BOOKMARK 20
#define STATE_LINE_COMMENT_EOL 21
#define STATE_BLOCK_COMMENT 22
#define STATE_BLOCK_COMMENT_END 23
#define STATE_BLOCK_COMMENT_SUB 24

/**
 * effectue le parsing du fichier préparé. Cette méthode est apellée lors du passage dans startParsing
 * uniquement si le parser est en état Prepared.
 * @return true si le parsing s'est déroulé correctement
 */
bool PovParser::parse()
{
    static const char szMacro[] = "macro";
    static const int iMacroLength = 5;
    static const char szDeclare[] = "declare";
    static const int iDeclareLength = 7;
    static const char szLocal[] = "local";
    static const int iLocalLength = 5;
    static const char szInclude[] = "include";
    static const int iIncludeLength = 7;

    this->infos.clear();

    std::istream * in = this->infos.initDataParsing();
    if(!in)
        return false;
    int iCurrentLine = 0;
    int iState = STATE_DEFAULT;
    int iKeywordStep = 0;
    uint iCommentLevel = 0;
    std::string sCurrentParam;
    char c = 0;
    bool bBlockStart = false;
    bool bStoreHelp = false;
    bool bErrors = false;

    PovFileElt::EltType eltType;
    std::string sEltName;
    TStringList params;
    uint iLine = 0;
    std::string sLastHelpBlock;

    sEltName.reserve(256);
    sLastHelpBlock.reserve(4096);

    long iLastModificationTime = this->infos.getLastModificationTime();

    while((!bErrors) && (!in->eof()))
    {
        if(this->checkStopRequested())
        {
            bErrors = true;
            break;
        }
        if(this->checkPauseRequested())
        {
            long iLastModificationTime2 = this->infos.getLastModificationTime();
            if(iLastModificationTime != iLastModificationTime2)
            {
                bErrors = true;
                break;
            }
        }

        if(c == '\n')
            iCurrentLine++;
        c = in->get();

        switch(iState)
        {
            case STATE_DEFAULT:
                if (c == '#')
                    iState = STATE_KEYWORD;
                else
                {
                    if(!isspace(c))
                        sLastHelpBlock.clear();
                    if (c == '/')
                        iState = STATE_COMMENT_START;
                }
                break;
            case STATE_KEYWORD:
                iKeywordStep = 1;
                if (c == 'm')
                    iState = STATE_MACRO;
                else if (c == 'd')
                    iState = STATE_DECLARE;
                else if (c == 'l')
                    iState = STATE_LOCAL;
                else if (c == 'i')
                    iState = STATE_INCLUDE;
                else if (c == '/')
                    iState = STATE_COMMENT_START;
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;


            case STATE_MACRO:
                if (c == szMacro[iKeywordStep])
                {
                    iKeywordStep++;
                    if (iKeywordStep == iMacroLength)
                        iState = STATE_MACRO_READ;
                }
                else
                    iState = STATE_DEFAULT;
                break;
            case STATE_MACRO_READ:
                if (isspace(c))
                {
                    iKeywordStep++;
                }
                else if ((iKeywordStep > iMacroLength) && (isalpha(c) || (c == '_')))
                {
                    // 1er caractere du nom de la macro
                    sEltName = wxChar(c);
                    eltType = PovFileElt::Macro;
                    iLine = iCurrentLine;
                    iState = STATE_MACRO_NAME;
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_MACRO_NAME:
                if (isalnum(c) || (c == '_'))
                {
                    sEltName += wxChar(c);
                }
                else if (isspace(c))
                    iState = STATE_MACRO_EONAME;
                else if (c == '(')
                    iState = STATE_MACRO_PARAMS_START;
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_MACRO_EONAME:
                if (c == '(')
                    iState = STATE_MACRO_PARAMS_START;
                else if (!(isspace(c)))
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_MACRO_PARAMS_START:
                if (c == ')')
                {
                    this->infos.addElement( PovFileElt(eltType,ISO2WX(sEltName.c_str()),params,iLine,ISO2WX(sLastHelpBlock.c_str())) );
                    sEltName.clear();
                    params.erase(params.begin(), params.end());
                    sLastHelpBlock.clear();

                    iState = STATE_DEFAULT;
                }
                else if (isalpha(c) || (c == '_'))
                {
                    sCurrentParam = c;
                    iState = STATE_MACRO_PARAMETER;
                }
                else if (!(isspace(c)))
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_MACRO_PARAMETER:
                if (isalnum(c) || (c == '_'))
                {
                    sCurrentParam += c;
                }
                else if (isspace(c) || (c == ',') || (c == ')'))
                {
                    params.push_back(ISO2WX(sCurrentParam.c_str()));
                    sCurrentParam.clear();
                    iState = STATE_MACRO_AFTER_PARAM;
                    if (c == ',')
                        iState = STATE_MACRO_PARAMS_START;
                    if (c == ')')
                    {
                        this->infos.addElement( PovFileElt(eltType,ISO2WX(sEltName.c_str()),params,iLine,ISO2WX(sLastHelpBlock.c_str())) );
                        sEltName.clear();
                        params.erase(params.begin(), params.end());
                        sLastHelpBlock.clear();

                        iState = STATE_DEFAULT;
                    }
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_MACRO_AFTER_PARAM:
                if (c == ')')
                {
                    this->infos.addElement( PovFileElt(eltType,ISO2WX(sEltName.c_str()),params,iLine,ISO2WX(sLastHelpBlock.c_str())) );
                    sEltName.clear();
                    params.erase(params.begin(), params.end());
                    sLastHelpBlock.clear();

                    iState = STATE_DEFAULT;
                }
                else if (c == ',')
                {
                    iState = 6;
                }
                else if (!(isspace(c)))
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;


                // declare
            case STATE_DECLARE:
                if (c == szDeclare[iKeywordStep])
                {
                    iKeywordStep++;
                    if (iKeywordStep == iDeclareLength)
                        iState = STATE_DECLARE_READ;
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_DECLARE_READ:
                if (isspace(c))
                {
                    iKeywordStep++;
                }
                else if ((iKeywordStep > iDeclareLength) && (isalpha(c)  || (c == '_')))
                {
                    eltType = PovFileElt::Declare;
                    sEltName = wxChar(c);
                    iLine = iCurrentLine;
                    iState = STATE_DECLARE_NAME;
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_DECLARE_NAME:
                if (isalnum(c) || (c == '_'))
                {
                    sEltName += wxChar(c);
                }
                else
                {
                    this->infos.addElement( PovFileElt(eltType,ISO2WX(sEltName.c_str()),params,iLine,ISO2WX(sLastHelpBlock.c_str())) );
                    sEltName.clear();
                    params.erase(params.begin(), params.end());
                    sLastHelpBlock.clear();

                    iState = STATE_DEFAULT;
                }
                break;


                // local
            case STATE_LOCAL:
                if (c == szLocal[iKeywordStep])
                {
                    iKeywordStep++;
                    if (iKeywordStep == iLocalLength)
                        iState = STATE_LOCAL_READ;
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_LOCAL_READ:
                if (isspace(c))
                {
                    iKeywordStep++;
                }
                else if ((iKeywordStep > iLocalLength) && (isalpha(c)  || (c == '_')))
                {
                    eltType = PovFileElt::Local;
                    sEltName = wxChar(c);
                    iLine = iCurrentLine;
                    iState = STATE_LOCAL_NAME;
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_LOCAL_NAME:
                if (isalnum(c) || (c == '_'))
                {
                    sEltName += c;
                }
                else
                {
                    this->infos.addElement( PovFileElt(eltType,ISO2WX(sEltName.c_str()),params,iLine,ISO2WX(sLastHelpBlock.c_str())) );
                    sEltName.clear();
                    params.erase(params.begin(), params.end());
                    sLastHelpBlock.clear();

                    iState = STATE_DEFAULT;
                }
                break;


                // includes
            case STATE_INCLUDE:
                if (c == szInclude[iKeywordStep])
                {
                    iKeywordStep++;
                    if (iKeywordStep == iIncludeLength)
                        iState = STATE_INCLUDE_READ;
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_INCLUDE_READ:
                if (isspace(c))
                {
                    iKeywordStep++;
                }
                else if ((iKeywordStep > iIncludeLength) && (c == '\"'))
                {
                    eltType = PovFileElt::Include;
                    sEltName.clear();
                    iLine = iCurrentLine;
                    iState = STATE_INCLUDE_FILE;
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;
            case STATE_INCLUDE_FILE:
                if ((c != '\"') && (c != '\\'))
                {
                    sEltName += wxChar(c);
                }
                else if (c == '\\')
                {
                    c = in->get();;
                    if(c != '\n')
                        sEltName += wxChar(c);
                }
                else if (c == '\"')
                {
                    if(trim(sEltName).length())
                    {
                        this->infos.addDependancie( ISO2WX(sEltName.c_str()) );
                        this->infos.addElement( PovFileElt(eltType,ISO2WX(sEltName.c_str()),params,iLine,ISO2WX(sLastHelpBlock.c_str())) );
                        sEltName.clear();
                        params.erase(params.begin(), params.end());
                        sLastHelpBlock.clear();
                    }

                    iState = STATE_DEFAULT;
                }
                break;




                // comments
            case STATE_COMMENT_START:
                if (c == '/')
                    iState = STATE_LINE_COMMENT;
                else if (c == '*')
                {
                    iCommentLevel++;
                    bBlockStart = true;
                    bStoreHelp = false;
                    iState = STATE_BLOCK_COMMENT;
                }
                else
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;

            case STATE_LINE_COMMENT:
                if (c == '\n')
                    iState = STATE_DEFAULT;
                else if (c == '§')
                {
                    eltType = PovFileElt::Bookmark;
                    sEltName.clear();
                    iLine = iCurrentLine;
                    iState = STATE_NAMED_BOOKMARK;
                }
                else
                    iState = STATE_LINE_COMMENT_EOL;
                break;

                // named bookmarks
            case STATE_NAMED_BOOKMARK:
                if (c == '\n')
                {
                    // trim
                    trim(sEltName);

                    if(sEltName.length())
                    {
                        this->infos.addElement( PovFileElt(eltType,ISO2WX(sEltName.c_str()),params,iLine,ISO2WX(sLastHelpBlock.c_str())) );
                        sEltName.clear();
                        params.erase(params.begin(), params.end());
                        sLastHelpBlock.clear();
                    }

                    iState = STATE_DEFAULT;
                }
                else
                    sEltName += wxChar(c);
                break;

            case STATE_LINE_COMMENT_EOL:
                if (c == '\n')
                {
                    sLastHelpBlock.clear();
                    iState = STATE_DEFAULT;
                }
                break;

            case STATE_BLOCK_COMMENT:
                if(bStoreHelp)
                    sLastHelpBlock += wxChar(c);
                if (c == '*')
                {
                    if(bBlockStart)
                    {
                        char cNext = in->peek();
                        if(cNext == ' ' || cNext == '\n') // on vient de lire la 2eme etoire le "/** " ==> il faut enregistrer la doc
                        {
                            bStoreHelp = true;
                            sLastHelpBlock.clear();
                        }
                    }
                    iState = STATE_BLOCK_COMMENT_END;
                }
                else if (c == '/')
                    iState = STATE_BLOCK_COMMENT_SUB;
                bBlockStart = false;
                break;

            case STATE_BLOCK_COMMENT_END:
                if(bStoreHelp)
                    sLastHelpBlock += wxChar(c);
                if (c == '/')
                {
                    iCommentLevel--;
                    if (!iCommentLevel)
                    {
                        iState = STATE_DEFAULT;
                        if(sLastHelpBlock.length() >= 2)
                        {
                            trim(sLastHelpBlock);
                            sLastHelpBlock.resize(sLastHelpBlock.length() - 2); // on enleve le "*/" de fin
                        }
                    }
                    else
                        iState = STATE_BLOCK_COMMENT;
                }
                else if (c != '*')
                    iState = STATE_BLOCK_COMMENT;
                break;

            case STATE_BLOCK_COMMENT_SUB:
                if(bStoreHelp)
                    sLastHelpBlock += wxChar(c);
                if (c == '*')
                    iCommentLevel++;
                if (c != '/')
                    iState = STATE_BLOCK_COMMENT;
                break;





            default:
                bErrors = true;
                break;
        }

    }

    this->infos.endDataParsing();

    if(!bErrors)
    {
        this->infos.setLastParseTime( wxDateTime::GetTimeNow() );
    }

    return !bErrors;
}

#undef STATE_DEFAULT
#undef STATE_KEYWORD
#undef STATE_MACRO
#undef STATE_MACRO_READ
#undef STATE_MACRO_NAME
#undef STATE_MACRO_EONAME
#undef STATE_MACRO_PARAMS_START
#undef STATE_MACRO_PARAMETER
#undef STATE_MACRO_AFTER_PARAM
#undef STATE_DECLARE
#undef STATE_DECLARE_READ
#undef STATE_DECLARE_NAME
#undef STATE_LOCAL
#undef STATE_LOCAL_READ
#undef STATE_LOCAL_NAME
#undef STATE_INCLUDE
#undef STATE_INCLUDE_READ
#undef STATE_INCLUDE_FILE
#undef STATE_COMMENT_START
#undef STATE_LINE_COMMENT
#undef STATE_NAMED_BOOKMARK
#undef STATE_LINE_COMMENT_EOL
#undef STATE_BLOCK_COMMENT
#undef STATE_BLOCK_COMMENT_END
#undef STATE_BLOCK_COMMENT_SUB
