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

TEnlighter::TEnlighter(TCodeEditor * ed)
    : editor(ed)
{
    if(this->editor == NULL)
    {
        LOG_MESSAGE("editor is null : there will be no enlightenment",logging::_FATAL_);
        return;
    }

    this->editor->getDocument()->addDocumentListener(this);
    this->invalidateAll();
}

TEnlighter::~TEnlighter()
{
    if(this->editor->getEnlighter() == this)
        this->editor->setEnlighter(NULL);

    this->editor->getDocument()->removeDocumentListener(this);
}

/** renvoie les informations de style pour la ligne iLine */
const TLineEnlightenmentInformation & TEnlighter::getLineEnlightenmentInformation(int iLine)
{
    this->checkEnlightenmentForLine(iLine);
    return this->enlightenmentInformation.at(iLine);
}

/** renvoie les informations de style pour la ligne iLine */
TLineEnlightenmentInformation & TEnlighter::getLineEnlightenmentInformationRef(int iLine)
{
    this->checkEnlightenmentForLine(iLine);
    return this->enlightenmentInformation.at(iLine);
}

/**
 * effectue les mises en valeur nécessaires pour obtenir les infos de la ligne demandée
 * @param bDoPrimaryCheck indique si il faut vérifier ou non que des lignes précédentes ne sont pas invalidées
 */
void TEnlighter::checkEnlightenmentForLine(int iLine, bool bDoPrimaryCheck)
{
    if(iLine < 0)
    {
        LOG_MESSAGE("iLine < 0",logging::_ERROR_);
        return;
    }

    if(uint(iLine) >= this->enlightenmentInformation.size())
        this->invalidateAll();

    if(bDoPrimaryCheck)
    {
        TIntSet::iterator it;
        while(this->invalidatedLines.size())
        {
            it = this->invalidatedLines.begin();
            if((*it) > iLine)
                break;
            this->enlightenmentInformation.at(iLine).invalidate();
            this->checkEnlightenmentForLine((*it),false);
        }
    }

    TLineEnlightenmentInformation & info = this->enlightenmentInformation.at(iLine);
    if(!info.isValid())
    {
        int iPreviousLineState = 0;
        if(iLine > 0)
            iPreviousLineState = this->enlightenmentInformation.at(iLine-1).getEndLineState();

        int iEndState = this->enlightLine(iLine,iPreviousLineState,info);
        if(iEndState != info.getEndLineState())
        {
            if(uint(iLine + 1) < this->enlightenmentInformation.size())
                this->setLineValidated(iLine + 1, false);
        }
        info.setEndLineState(iEndState);
    }
    this->setLineValidated(iLine, true);
}

/** effectue les mises en valeur nécessaires pour obtenir les infos de la ligne demandée */
void TEnlighter::checkEnlightenmentForLine(int iLine)
{
    this->checkEnlightenmentForLine(iLine,true);
}



/** signale l'insertion de texte dans le document. */
void TEnlighter::documentTextInserted(TDocument * doc, const TPoint & ptFrom, const TPoint & ptTo)
{
    TLineEnlightenmentInformationsList::iterator it = this->getLineIterator(ptFrom.y);
    (*it).invalidate();
    if(ptFrom.y != ptTo.y)
        this->enlightenmentInformation.insert(it,ptTo.y - ptFrom.y, TLineEnlightenmentInformation());
    for(int i = ptFrom.y ; i <= ptTo.y ; i++)
    {
        this->invalidatedLines.insert(i);
    }
}

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


/** invalide toutes les informations de mise en valeur */
void TEnlighter::invalidateAll()
{
    this->enlightenmentInformation.clear();
    for(uint i = 0 ; i < this->getEditor()->getDocument()->getLinesCount() ; i++)
    {
        this->invalidatedLines.insert(i);
        this->enlightenmentInformation.push_back(TLineEnlightenmentInformation());
    }
}

/**
 * écoute les modifications sur un document
 */
void TEnlighter::documentHeavilyModified(TDocument * doc)
{
    this->invalidateAll();
}

/** renvoie un itérateur sur une ligne */
TEnlighter::TLineEnlightenmentInformationsList::iterator TEnlighter::getLineIterator(uint iLine)
{
    TLineEnlightenmentInformationsList::iterator it = this->enlightenmentInformation.begin();
    for(uint i = 0 ; i < iLine ; i++)
        it++;
    return it;
}

/** valide ou invalide les lignes correspondantes */
void TEnlighter::setLineValidated(int iLine, bool bValidated)
{
    if(bValidated)
    {
        TIntSet::iterator it = this->invalidatedLines.find(iLine);
        if(it != this->invalidatedLines.end())
            this->invalidatedLines.erase(it);
        this->enlightenmentInformation.at(iLine).validate();
    }
    else
    {
        this->invalidatedLines.insert(iLine);
        this->enlightenmentInformation.at(iLine).invalidate();
    }
}














/**
 * défini le style sur une partie de la ligne
 * @param iStyleId ID du style a utiliser sur le segment
 * @param iColStart position de début d'application du style (inclu)
 * @param iColEnd  position de fin d'application du style (exclu). Jusqu'a la fin de la ligne si iEnd < 0.
 */
void TLineEnlightenmentInformation::setStyle(sbyte iStyleId, int iColStart, int iColEnd)
{
    if(iColStart < 0)
        iColStart = 0;
    if((iColEnd >= 0) && iColEnd <= iColStart)
        return;

    int iPreviousStyleId = 0;
    if(iColEnd > 0)
        iPreviousStyleId = this->getStyleAt(iColEnd);

    this->enlightenment[iColStart] = iStyleId;

    TLineStylesMap::iterator itB = this->enlightenment.find(iColStart);
    TLineStylesMap::iterator itE = this->enlightenment.end();
    if(iColEnd > 0)
    {
        this->enlightenment[iColEnd] = iPreviousStyleId;
        itE = this->enlightenment.find(iColEnd);
    }
    itB++;
    this->enlightenment.erase(itB,itE);
}


/** renvoie l'ID du style à une position */
sbyte TLineEnlightenmentInformation::getStyleAt(int iCol) const
{
    if(!this->enlightenment.size())
        return DEFAULT_STYLE;

    TLineStylesMap::const_iterator it = this->enlightenment.lower_bound(iCol);
    if(it != this->enlightenment.end())
    {
        if((*it).first > iCol)
            it--;
        return (*it).second;
    }
    else
    {
        TLineStylesMap::const_reverse_iterator it2 = this->enlightenment.rbegin();
        return (*it2).second;
    }
}


/** renvoie l'index de la colonne (exclue) fermant le segment de style en vigueur à la position iCol ou -1 si celui-ci va jusqu'à la fin de la ligne */
int TLineEnlightenmentInformation::getSegmentEnd(int iCol) const
{
    if(!this->enlightenment.size())
        return -1;

    TLineStylesMap::const_iterator it = this->enlightenment.upper_bound(iCol);
    if(it == this->enlightenment.end())
        return -1;

    return (*it).first;
}


/** optimise la segmentation de la ligne */
void TLineEnlightenmentInformation::optimise()
{
    TLineStylesMap::iterator itB = this->enlightenment.begin();
    TLineStylesMap::iterator itE = this->enlightenment.end();
    TLineStylesMap::iterator itDel = itE;
    int iPreviousStyleId = -1;
    while(itB != itE)
    {
        itDel = itB;
        itB++;

        if((*itDel).second == iPreviousStyleId)
            this->enlightenment.erase(itDel);
        else
            iPreviousStyleId = (*itDel).second;
    }
}











/**
 * définis tout au style par défaut
 * ne pas appeler directement
 * @param iLine ligne a colorer
 * @param iPreviousLineEndState état dans lequel se trouvait l'analyseur lors de la sortie de cette fonction pour la ligne précédente. La première ligne (0) est appellée avec 0.
 * @param info informations de mise en valeur a compléter.
 * @return état de l'analyseur à utiliser pour la ligne suivante.
 */
int TDefaultEnlighter::enlightLine(int iLine, int iPreviousLineEndState, TLineEnlightenmentInformation & info)
{
    info.clearStyleInformations();
    return 0;
}

