/***************************************************************************
 *   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 "pov_enlighter.h"
#include "components/codeeditor/tcodeeditor.h"
#include "lib/lib_logging.h"





/** signale l'insertion de texte dans le document. */
void PovEnlighter::documentTextInserted(TDocument * doc, const TPoint & ptFrom, const TPoint & ptTo)
{
    this->TEnlighter::documentTextInserted(doc,ptFrom,ptTo);
    TIntList::iterator it = this->getCommentInfoLineIterator(ptFrom.y);
    if(ptFrom.y != ptTo.y)
        this->commentLevels.insert(it,ptTo.y - ptFrom.y,0);
}

/** signale la suppression de texte dans le document. */
void PovEnlighter::documentTextRemoved(TDocument * doc, const TPoint & ptFrom, const TPoint & ptTo)
{
    this->TEnlighter::documentTextRemoved(doc,ptFrom,ptTo);
    TIntList::iterator itB = this->getCommentInfoLineIterator(ptFrom.y);
    TIntList::iterator itE = this->getCommentInfoLineIterator(ptTo.y);
    if(ptFrom.y != ptTo.y)
    {
        itB++;
        itE++;
        this->commentLevels.erase(itB,itE);
    }
}

/**
 * ecoute les modifications sur un document
 */
void PovEnlighter::documentHeavilyModified(TDocument * doc)
{
    this->TEnlighter::documentHeavilyModified(doc);
    this->commentLevels.clear();
    for(uint i = 0 ; i < doc->getLinesCount() ; i++)
    {
        this->commentLevels.push_back(0);
    }
}

/** renvoie un iterateur sur une ligne */
TIntList::iterator PovEnlighter::getCommentInfoLineIterator(uint iLine)
{
    TIntList::iterator it = this->commentLevels.begin();
    for(uint i = 0 ; i < iLine ; i++)
        it++;
    return it;
}



/**
 * definis tout au style par defaut
 * ne pas appeler directement
 * @param iLine ligne a colorer
 * @param iPreviousLineEndState etat dans lequel se trouvait l'analyseur lors de la sortie de cette fonction pour la ligne precedente. La premiere ligne (0) est appellee avec 0.
 * @param info informations de mise en valeur a completer.
 * @return etat de l'analyseur a utiliser pour la ligne suivante.
 */
int PovEnlighter::enlightLine(int iLine, int iPreviousLineEndState, TLineEnlightenmentInformation & info)
{
    uint i = 0;
    uint iWordStart = 0;
    uint iWordEnd = 0;
    uint iState = iPreviousLineEndState;
    uint iCommentLevel = 0;
    wxChar c = 0;
    wxString sWord;
    const wxString & sText = this->getEditor()->getDocument()->getLine(iLine);


#define STATE_DEFAULT               0
#define STATE_WORD                  1
#define STATE_NUMBER_INT            2
#define STATE_NUMBER_FLOAT          3
#define STATE_COMMENTARY_START      4
#define STATE_COMMENTARY_TOEOL      5
#define STATE_COMMENTARY_TOEOB      6
#define STATE_COMMENTARY_EOB        7
#define STATE_TEXTSTRING            8
#define STATE_TEXTSTRING_BACKED     9
#define STATE_CHAR                  10
#define STATE_CHAR_BACKED           11
#define STATE_COMMENTARY_SUBSTART   12

#define STATE_COMMENTARY_NOTENDED   20

    if (iLine > 0)
    {
        iCommentLevel = this->commentLevels[iLine-1];
        if(iCommentLevel)
            iState = STATE_COMMENTARY_TOEOB;
    }

    info.clearStyleInformations();

    while (i < sText.length())
    {
        c = sText[i];
        switch (iState)
        {
        case STATE_DEFAULT:
            if (isalpha(c) || (c == '_'))
            {
                iState = STATE_WORD;
                sWord = c;
            }
            else if (isdigit(c))
                iState = STATE_NUMBER_INT;
            else if (c == '/')
                iState = STATE_COMMENTARY_START;
            else if (c == '\"')
                iState = STATE_TEXTSTRING;
            else if (c == '\'')
                iState = STATE_CHAR;

            if(iState != STATE_DEFAULT)
                iWordStart = i;

            break;

        case STATE_WORD:
            if ((c == '/') || (c == '\"') || (c == '\'') || (!(isalnum(c) || (c == '_'))))
            {
                iWordEnd = i;

                if (this->isKeyword(sWord))
                {
                    if(iWordStart > 0)
                    {
                        iWordStart--;
                        if(sText[iWordStart] != '#')
                            iWordStart++;
                    }

                    info.setStyle(STYLE_KEYWORD, iWordStart, iWordEnd);
                }
                else
                {
                    info.setStyle(STYLE_IDENT, iWordStart, iWordEnd);
                }

                if (c == '/')
                    iState = STATE_COMMENTARY_START;
                else if (c == '\"')
                    iState = STATE_TEXTSTRING;
                else if (c == '\'')
                    iState = STATE_CHAR;
                else
                    iState = STATE_DEFAULT;
                iWordStart = i;
            }
            sWord += c;
            break;

        case STATE_NUMBER_INT:
            if (c == '.')
            {
                iState = STATE_NUMBER_FLOAT;
            }
            else if ((c == '/') || (c == '\"') || (c == '\'') || (!isdigit(c)))
            {
                iWordEnd = i;
                info.setStyle(STYLE_NUMBER, iWordStart, iWordEnd);

                if (c == '/')
                    iState = STATE_COMMENTARY_START;
                else if (c == '\"')
                    iState = STATE_TEXTSTRING;
                else if (c == '\'')
                    iState = STATE_CHAR;
                else
                    iState = STATE_DEFAULT;
                iWordStart = i;
            }
            break;

        case STATE_NUMBER_FLOAT:
            if ((c == '/') || (c == '\"') || (c == '\'') || (!isdigit(c)))
            {
                iWordEnd = i;
                info.setStyle(STYLE_NUMBER, iWordStart, iWordEnd);

                if (c == '/')
                    iState = STATE_COMMENTARY_START;
                else if (c == '\"')
                    iState = STATE_TEXTSTRING;
                else if (c == '\'')
                    iState = STATE_CHAR;
                else
                    iState = STATE_DEFAULT;
                iWordStart = i;
            }
            break;

        case STATE_COMMENTARY_START:
            if (c == '/')
            {
                iState = STATE_COMMENTARY_TOEOL;
            }
            else if (c == '*')
            {
                iCommentLevel++;
                iState = STATE_COMMENTARY_TOEOB;
            }
            else if (c == '\"')
            {
                iState = STATE_TEXTSTRING;
                iWordStart = i;
            }
            else if (c == '\'')
            {
                iState = STATE_CHAR;
                iWordStart = i;
            }
            else
            {
                i--;
                iState = STATE_DEFAULT;
            }
            break;

        case STATE_COMMENTARY_TOEOL:
            i = sText.length();
            info.setStyle(STYLE_COMMENT, iWordStart);
            iState = STATE_DEFAULT;
            break;

        case STATE_COMMENTARY_TOEOB:
            // optimisation du passage du commentaire
            while ((i < sText.length()) && (sText[i] != '*') && (sText[i] != '/'))
                i++;
            if (i == sText.length())
            {
                info.setStyle(STYLE_COMMENT, iWordStart);
            }
            else if (sText[i] == '*')
                iState = STATE_COMMENTARY_EOB;
            else
                iState = STATE_COMMENTARY_SUBSTART;
            break;

        case STATE_COMMENTARY_EOB:
            if (c == '/')
            {
                iCommentLevel--;
                if (iCommentLevel == 0)
                {
                    iWordEnd = i+1;
                    info.setStyle(STYLE_COMMENT, iWordStart, iWordEnd);
                    iState = STATE_DEFAULT;
                }
                else
                    iState = STATE_COMMENTARY_TOEOB;
            }
            else if (c != '*')
                iState = STATE_COMMENTARY_TOEOB;
            break;

        case STATE_TEXTSTRING:
            // optimisation du passage du texte
            while ((i < sText.length()) && (sText[i] != '\"') && (sText[i] != '\\'))
                i++;
            if (i == sText.length())
            {
                iWordEnd = i+1;
                info.setStyle(STYLE_STRING, iWordStart, iWordEnd);
            }
            else if (sText[i] == '\"')
            {
                iWordEnd = i+1;
                info.setStyle(STYLE_STRING, iWordStart, iWordEnd);
                iState = STATE_DEFAULT;
            }
            else if (sText[i] == '\\')
                iState = STATE_TEXTSTRING_BACKED;
            break;

        case STATE_TEXTSTRING_BACKED:
            iState = STATE_TEXTSTRING;
            break;

        case STATE_CHAR:
            // optimisation du passage du texte
            while ((i < sText.length()) && (sText[i] != '\'') && (sText[i] != '\\'))
                i++;
            if (i == sText.length())
            {
                iWordEnd = i+1;
                info.setStyle(STYLE_CHAR, iWordStart, iWordEnd);
            }
            else if (sText[i] == '\'')
            {
                iWordEnd = i+1;
                info.setStyle(STYLE_CHAR, iWordStart, iWordEnd);
                iState = STATE_DEFAULT;
            }
            else if (sText[i] == '\\')
                iState = STATE_CHAR_BACKED;
            break;

        case STATE_CHAR_BACKED:
            iState = STATE_CHAR;
            break;

        case STATE_COMMENTARY_SUBSTART:
            if (c == '*')
            {
                iCommentLevel++;
                iState = STATE_COMMENTARY_TOEOB;
            }
            else if (c != '/')
                iState = STATE_COMMENTARY_TOEOB;
            break;

        default:
            iState = STATE_DEFAULT;
            i--;
            break;
        }
        i++;
    }

    //fermeture des colorations
    switch (iState)
    {
    case STATE_WORD:
        if (this->isKeyword(sWord))
        {
            iWordStart--;
            if (sText[iWordStart] != '#')
                iWordStart++;

            info.setStyle(STYLE_KEYWORD, iWordStart);
        }
        else
            info.setStyle(STYLE_IDENT, iWordStart);
        iState = STATE_DEFAULT;
        break;
    case STATE_NUMBER_INT:
    case STATE_NUMBER_FLOAT:
        info.setStyle(STYLE_NUMBER, iWordStart);
        iState = STATE_DEFAULT;
        break;
    case STATE_COMMENTARY_TOEOB:
    case STATE_COMMENTARY_EOB:
    case STATE_COMMENTARY_SUBSTART:
        info.setStyle(STYLE_COMMENT, iWordStart);
        break;
    case STATE_COMMENTARY_TOEOL:
        info.setStyle(STYLE_COMMENT, iWordStart);
        iState = STATE_DEFAULT;
        break;
    case STATE_TEXTSTRING:
    case STATE_TEXTSTRING_BACKED:
        info.setStyle(STYLE_STRING, iWordStart);
        break;
    case STATE_CHAR:
    case STATE_CHAR_BACKED:
        info.setStyle(STYLE_CHAR, iWordStart);
        break;
    }


    if (iCommentLevel > 0)
        iState = STATE_COMMENTARY_NOTENDED + iCommentLevel;

    this->commentLevels[iLine] = iCommentLevel;

    return iState;


#undef STATE_DEFAULT
#undef STATE_WORD
#undef STATE_NUMBER_INT
#undef STATE_NUMBER_FLOAT
#undef STATE_COMMENTARY_START
#undef STATE_COMMENTARY_TOEOL
#undef STATE_COMMENTARY_TOEOB
#undef STATE_COMMENTARY_EOB
#undef STATE_TEXTSTRING
#undef STATE_TEXTSTRING_BACKED
#undef STATE_CHAR
#undef STATE_CHAR_BACKED
#undef STATE_COMMENTARY_SUBSTART

#undef STATE_COMMENTARY_NOTENDED
}

bool PovEnlighter::isKeyword(const wxString & sWord) const
{
    if(this->keywords == NULL) return false;

    TStringList::const_iterator it = this->keywords->find(sWord);

    return (it != this->keywords->end());
}


