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

#include <fstream>

#include <exception>
#include <stdexcept>
#include <iterator>

#include "lib/lib_logging.h"
#include "lib/lib_string.h"
#include <math.h>

#include "components/framework/tapplication.h"

TDocument::TDocument()
    : bModeUndo(true), bDoingRedo(false), iUndoCapacity(1), currentComposedAction(NULL), iComposedActionStack(0)
{
    this->clear();
}

TDocument::~TDocument()
{
    this->clearUndoRedo();
    if(this->listeners.size())
    {
        if(!CurrentApplication()->isInClosingPhase()) // dans le cas de la fermeture de l'application, c'est wx qui delete les composants sans pour autant deleter les objets C++ du framework
            LOG_MESSAGE("Il reste des écouteurs sur le document. Cela provoquera probablement des erreurs de segmentation.", logging::_WARN_);
    }
}

/**
 * insère un caratère à la position indiquée
 * @return le nb de caractères insérés
 */
int TDocument::insertAt(wxChar c, uint iLine, uint iColumn)
{
    if(c == wxT('\r') || c == wxT('\b'))
    {
        return 0;
    }

    int iInsertedChars = 1;
    if(c == wxT('\n'))
    {
        TStringList::iterator it = this->getLineIterator(iLine);
        wxString sEndingPart = (*it).Mid(iColumn);
        (*it).Truncate(iColumn);
        this->textData.insert(it+1,sEndingPart);
    }
    else
    {
        wxChar szBuf[5];
        if(c == wxT('\t'))
        {
            szBuf[0] = ' ';
            szBuf[1] = ' ';
            szBuf[2] = ' ';
            szBuf[3] = ' ';
            szBuf[4] = '\0';
            iInsertedChars = 4;
        }
        else
        {
            szBuf[0] = c;
            szBuf[1] = '\0';
        }

        // on récupère la ligne qu'on va altérer
        if(iLine < this->getLinesCount())
        {
            TStringList::iterator it = this->getLineIterator(iLine);

            // on intègre les nouvelles données
            if(iColumn < (*it).length())
                (*it).insert(iColumn,szBuf);
            else
                (*it) += szBuf;
        }
        else
        {
            iLine = this->getLinesCount();
            iColumn = 0;
            this->textData.push_back(wxString(szBuf));
        }
    }

    // gestion du undo/redo
    TPoint ptFrom(iColumn,iLine);
    TPoint ptTo(iColumn+iInsertedChars,iLine);
    if(c == wxT('\n'))
    {
        ptTo.x = 0;
        ptTo.y++;
    }
    this->addAction(new TSimpleDocAction(this,TSimpleDocAction::Delete,ptFrom,ptTo,wxT("")));

    // déclanchement des evenements
    this->fireDocumentTextInserted(ptFrom,ptTo);

    return iInsertedChars;
}

/**
 * insère une chaine de carateres (null terminated string) à la position indiquée.
 * @return le nb de caracteres inseres
 */
int TDocument::insertAt(const wxChar * szSrc, uint iLine, uint iColumn)
{
    TStringList lines;
    wxString sWorkingLine;
    wxString sCurrentLine;

    uint iSkippedCharacters = 0;

    // on récupère la ligne qu'on va altérer
    if(iLine < this->getLinesCount())
        sWorkingLine = wxString(this->getLine(iLine));
    else
        iLine = this->getLinesCount();

    int iInsertedChars = sWorkingLine.length();

    // on intègre les nouvelles données
    if(iColumn < sWorkingLine.length())
        sWorkingLine.insert(iColumn,szSrc);
    else
    {
        iColumn = sWorkingLine.length();
        sWorkingLine += szSrc;
    }
    sWorkingLine.Replace(wxT("\t"),wxT("    "));
    iInsertedChars = sWorkingLine.length() - iInsertedChars;
    const wxChar * sz = sWorkingLine.c_str();

    // et on parse toute la ligne (ou les lignes) nouvellement creee
    wxChar buf[2];
    buf[1]=0;
    uint i = 0;
    while(sz[i])
    {
        // les retours a la ligne
        if(sz[i] == wxT('\r') || sz[i] == wxT('\n'))
        {
            lines.push_back(sCurrentLine);
            sCurrentLine.resize(0);
            if((sz[i] == wxT('\r') && sz[i+1] == wxT('\n')) && (sz[i] == wxT('\n') && sz[i+1] == wxT('\r')))
            {
                i++;
                iSkippedCharacters++;
            }
        }
        // caracteres non gerables
        else if(sz[i] == wxT('\b'))
        {
            iSkippedCharacters++;
        }
        // caractere normal
        else
        {
            buf[0] = sz[i];
            sCurrentLine.append(buf);
        }
        i++;
    }
    lines.push_back(sCurrentLine);
    iInsertedChars -= iSkippedCharacters;

    TStringList::iterator it = this->getLineIterator(iLine);

    // on supprime les anciennes donnees dans la liste de lignes
    if(it != this->textData.end())
    {
        this->textData.erase(it);
        it = this->getLineIterator(iLine);
    }

    // on ajoute les nouvelles donnees dans la liste de lignes
    if(it == this->textData.end())
    {
        while(lines.size())
        {
            this->textData.push_back(lines.front());
            lines.pop_front();
        }
    }
    else
    {
        this->textData.insert(it, lines.begin(), lines.end());
    }

    // gestion du undo/redo
    TPoint ptFrom(iColumn,iLine);
    TPoint ptTo = this->getPosition(ptFrom,iInsertedChars,Character);
    this->addAction(new TSimpleDocAction(this,TSimpleDocAction::Delete,ptFrom,ptTo,wxT("")));

    // déclanchement des evenements
    this->fireDocumentTextInserted(ptFrom,ptTo);

    return iInsertedChars;
}

/** renvoie une référence inaltérable sur une ligne */
const wxString & TDocument::getLine(uint iLine) const
{
    return const_cast<TDocument *>(this)->_getLineRef(iLine);
}

/** renvoie une référence altérable sur une ligne */
wxString & TDocument::_getLineRef(uint iLine)
{
    return this->textData.at(iLine);
}

/** renvoie une portion du document sous forme d'une seule chaine de caracteres (ptB est exclu) */
wxString TDocument::getTextBetween(const TPoint & ptA, const TPoint & ptB) const
{
    wxString sRes;
    if(!(ptA.isValid() && ptB.isValid()))
        return sRes;
    if(ptA == ptB)
        return sRes;

    TPoint pt1, pt2;
    if(ptA <= ptB)
    {
        pt1 = ptA;
        pt2 = ptB;
    }
    else
    {
        pt2 = ptA;
        pt1 = ptB;
    }

    if(uint(pt1.y) >= this->getLinesCount())
        return sRes;

    if(uint(pt2.y) >= this->getLinesCount())
    {
        pt2.y = this->getLinesCount() - 1;
        pt2.x = this->getLineLength(pt2.y);
    }

    if(uint(pt1.x) > this->getLineLength(pt1.y))
        pt1.x = this->getLineLength(pt1.y);
    if(uint(pt2.x) > this->getLineLength(pt2.y))
        pt2.x = this->getLineLength(pt2.y);

    sRes = (this->getLine(pt1.y).c_str() + pt1.x);

    if(pt2.y == pt1.y)
    {
        sRes.resize(pt2.x - pt1.x);
    }
    else
    {
        sRes += wxChar('\n');

        TStringList::const_iterator itB = this->getConstLineIterator(pt1.y + 1);
        TStringList::const_iterator itE = itB + (pt2.y - pt1.y - 1);

        while(itB != itE)
        {
            sRes += (*itB) + wxChar('\n');
            itB++;
        }

        sRes += wxString((*itE).c_str(),0,pt2.x);
    }
    return sRes;
}

/** renvoie le nombre de lignes dans le document */
uint TDocument::getLinesCount() const
{
    return this->textData.size();
}

/** renvoie la longueur d'une ligne */
uint TDocument::getLineLength(uint iLine) const
{
    return this->getLine(iLine).length();
}

/** vide le contenu du document */
void TDocument::clear()
{
    this->textData.clear();
    this->textData.push_back(wxString());
    this->fireDocumentHeavilyModified();
}

/** renvoie un iterateur sur une ligne */
TStringList::iterator TDocument::getLineIterator(uint iLine)
{
    TStringList::iterator it = this->textData.begin();
    for(uint i = 0 ; i < iLine ; i++)
        it++;
    return it;
}

/** renvoie un iterateur sur une ligne */
TStringList::const_iterator TDocument::getConstLineIterator(uint iLine) const
{
    TStringList::const_iterator it = this->textData.begin();
    for(uint i = 0 ; i < iLine ; i++)
        it++;
    return it;
}

/** supprime un caractï¿½e dans le document */
void TDocument::removeCharAt(uint iLine, int iColumn)
{
    if(iLine >= this->getLinesCount())
        return;

    if(iColumn < 0)
    {
        if(iLine > 0)
        {
            iLine--;
            iColumn = this->getLineLength(iLine);
        }
        else
            return;
    }

    TPoint pos(iColumn,iLine);
    TPoint nextPos = this->getPosition(pos,1,Character);

    if(uint(iColumn) >= this->getLineLength(iLine))
    {
        if(iLine == this->getLinesCount()-1)
            return;
        // on joint les 2 lignes
        this->_getLineRef(iLine) += this->getLine(iLine+1).c_str();
        TStringList::iterator it = this->getLineIterator(iLine+1);
        this->textData.erase(it);
        this->addAction(new TSimpleDocAction(this,TSimpleDocAction::Insert,TPoint(iColumn,iLine),TPoint(),wxT("\n")));
    }
    else
    {
        // on supprime le caractère
        const wxString & sLine = this->getLine(iLine);

        wxChar szDel[2];
        szDel[1] = 0;
        szDel[0] = sLine[iColumn];
        this->addAction(new TSimpleDocAction(this,TSimpleDocAction::Insert,TPoint(iColumn,iLine),TPoint(),szDel));

        wxChar szNewLine[sLine.length()];
        szNewLine[0] = 0;

        for(uint i = 0, j = 0 ; i < sLine.length() ; i++)
        {
            if(i != uint(iColumn))
            {
                szNewLine[j] = sLine[i];
                j++;
                szNewLine[j] = 0;
            }
        }

        this->_getLineRef(iLine) = szNewLine;
    }
    this->fireDocumentTextRemoved(pos,nextPos);
}

/** supprime une portion du document (le plus grand est exclu) */
void TDocument::removeRange(const TPoint & ptA, const TPoint & ptB)
{
    if(!(ptA.isValid() && ptB.isValid()))
        return;
    if((uint(ptA.y) >= this->getLinesCount()) || (uint(ptB.y) >= this->getLinesCount()))
        return;
    if(ptA == ptB)
        return;

    TPoint pt1, pt2;
    if(ptA <= ptB)
    {
        pt1 = ptA;
        pt2 = ptB;
    }
    else
    {
        pt2 = ptA;
        pt1 = ptB;
    }

    this->addAction(new TSimpleDocAction(this,TSimpleDocAction::Insert,pt1,TPoint(),this->getTextBetween(pt1,pt2)));

    // ligne modifiée par la suppression (début de la première plus fin de la dernière)
    wxString sLine(this->getLine(pt1.y));

    if(uint(pt1.x) < sLine.length())
        sLine.resize(pt1.x);

    if(uint(pt2.y) < this->getLinesCount())
    {
        if(uint(pt2.x) < this->getLineLength(pt2.y))
            sLine += (this->getLine(pt2.y).c_str() + pt2.x);
    }

    TStringList::iterator itB = this->getLineIterator(pt1.y);
    TStringList::iterator itE = itB + (pt2.y+1 - pt1.y);
    this->textData.erase(itB,itE);

    itB = this->getLineIterator(pt1.y);
    if(itB == this->textData.end())
        this->textData.push_back(sLine);
    else
        this->textData.insert(itB,sLine);

    this->fireDocumentTextRemoved(pt1,pt2);
}



/** charge un fichier apres avoir vidï¿½le document */
bool TDocument::loadFile(const wxString & sFileName)
{
    std::ifstream in_stream;

    in_stream.open((const char *)sFileName.fn_str(), std::ios::in);
    if(in_stream.good())
    {
        this->textData.clear();

        this->loadFromStream(in_stream);
        in_stream.close();
    }
    else
        return false;

    if(!this->textData.size())
    {
        this->textData.push_back(wxString());
        this->fireDocumentHeavilyModified();
    }

    this->clearUndoRedo();
    return true;
}

/** charge du texte depuis un flux (sans vider le document) */
void TDocument::loadFromStream(std::istream & stream)
{
    int iReadLines = 0;
    char szBuffer[65536];
    do
    {
        stream.getline(szBuffer,65536);
        iReadLines++;
        wxString sBuf = ISO2WX(szBuffer);
        sBuf.Replace(wxT("\t"),wxT("    "));
        this->textData.push_back(sBuf);
    }while(!stream.eof());
    if(iReadLines)
        this->fireDocumentHeavilyModified();
}

/** écrit le contenu du document dans un flux */
void TDocument::writeToStream(std::ostream & stream)
{
    unsigned int iLine = 0;
    for(TStringList::iterator it = this->textData.begin() ; it != this->textData.end() ; it++)
    {
        iLine++;
        stream << WX2ISO( (*it) );
        if(iLine < this->getLinesCount())
            stream << std::endl;
    }
}

/** écrit le contenu du document dans un fichier */
bool TDocument::writeFile(const wxString & sFileName)
{
    std::ofstream out_stream;

    out_stream.open((const char *)sFileName.fn_str(), std::ios::out | std::ios::binary);
    if(out_stream.good())
    {
        this->writeToStream(out_stream);
        out_stream.close();
        return true;
    }
    else
    {
        wxString sMessage = wxString::Format(wxTr("Unable to open file %s in WRITE mode."),sFileName.c_str());
        LOG_MESSAGE(sMessage,logging::_ERROR_);
        wxMessageBox(sMessage,wxTr("Error"),wxOK|wxICON_ERROR);
        return false;
    }
}

/**
 * calcule une position dans le document a partir d'une autre
 * @param ptFrom point de depart du calcul de position
 * @param iSens direction du calcul (>= 0 vers l'avant) et nombre d'iterations
 * @param unit l'unite du deplacement : un caractere ou un mot
 */
TPoint TDocument::getPosition(const TPoint & ptFrom, int iSens, TUnit unit)
{
    if(!ptFrom.isValid())
        return TPoint(0,0);
    if(iSens >= -1 && uint(ptFrom.y) >= this->getLinesCount())
        return TPoint(this->getLineLength(this->getLinesCount()-1),this->getLinesCount()-1);
    if((iSens < 0 && ptFrom.x == 0 && ptFrom.y == 0)
        || (iSens >= 0 && uint(ptFrom.y) == (this->getLinesCount() - 1) && uint(ptFrom.x) == this->getLineLength(ptFrom.y)))
        return TPoint(ptFrom);

    int iCol = ptFrom.x, iLine = ptFrom.y;
    int iIncrement = (iSens >= 0) ? 1 : -1;
    int iQuantity = abs(iSens);
    if(!iQuantity)
        iQuantity = 1;

    if(uint(iLine) >= this->getLinesCount())
        iLine = this->getLinesCount() - 1;

    while(iQuantity)
    {
        if((iIncrement < 0 && iLine == 0 && iCol == 0)
            || (iIncrement > 0 && uint(iLine) == (this->getLinesCount() - 1) && uint(iCol) == this->getLineLength(iLine)))
            break;

        if(uint(iCol) > this->getLineLength(iLine))
            iCol = this->getLineLength(iLine);

        // traitement du dï¿½ut / fin de ligne
        if(iCol == 0 && iSens < 0)
        {
            iLine--;
            iCol = this->getLineLength(iLine);
        }
        else if(uint(iCol) == this->getLineLength(iLine) && iSens >= 0)
        {
            iLine++;
            iCol = 0;
        }
        else
        {
            if(unit == Character)
            {
                iCol += iIncrement;
            }
            else if(unit == Word)
            {
                const wxString & sLine = this->getLine(iLine);
                int iLineLength = sLine.length();
                iCol += iIncrement;
                bool bIsAlpha = (isalnum(sLine[iCol]) != 0 || sLine[iCol] == '_');
                while(iCol && (iCol < iLineLength) && ((isalnum(sLine[iCol]) != 0 || sLine[iCol] == '_') == bIsAlpha))
                    iCol += iIncrement;
                if(iIncrement < 0 && iCol)
                    iCol++;
            }
        }
        iQuantity--;
    }

    return TPoint(iCol,iLine);
}

/** ajoute un ecouteur */
bool TDocument::addDocumentListener(TDocumentListener * l)
{
    if(l)
        return this->listeners.insert(l).second;
    else
        return false;
}

/** enleve un ecouteur */
bool TDocument::removeDocumentListener(TDocumentListener * l)
{
    return this->listeners.erase(l) != 0;
}

/** envoie un evenement indiquant que le contenu du document a change */
void TDocument::fireDocumentChanged()
{
    TDocumentListenersList::iterator itB = this->listeners.begin();
    TDocumentListenersList::iterator itE = this->listeners.end();

    while(itB != itE)
    {
        (*itB)->documentChanged(this);
        itB++;
    }
}

/** envoie un ï¿½enement indiquant l'insertion de texte */
void TDocument::fireDocumentTextInserted(const TPoint & ptFrom, const TPoint & ptTo)
{
    TDocumentListenersList::iterator itB = this->listeners.begin();
    TDocumentListenersList::iterator itE = this->listeners.end();

    while(itB != itE)
    {
        (*itB)->documentTextInserted(this,ptFrom,ptTo);
        itB++;
    }
    this->fireDocumentChanged();
}

/** envoie un ï¿½enement indiquant la suppression de texte */
void TDocument::fireDocumentTextRemoved(const TPoint & ptFrom, const TPoint & ptTo)
{
    TDocumentListenersList::iterator itB = this->listeners.begin();
    TDocumentListenersList::iterator itE = this->listeners.end();

    while(itB != itE)
    {
        (*itB)->documentTextRemoved(this,ptFrom,ptTo);
        itB++;
    }
    this->fireDocumentChanged();
}

/** signale de grosses modifs sur le document. puis appelle documentChanged */
void TDocument::fireDocumentHeavilyModified()
{
    TDocumentListenersList::iterator itB = this->listeners.begin();
    TDocumentListenersList::iterator itE = this->listeners.end();

    while(itB != itE)
    {
        (*itB)->documentHeavilyModified(this);
        itB++;
    }
    this->fireDocumentChanged();
}


/**
 * dï¿½ini la longueur du tampon d'actions pour les undo/redo
 * @param iUndoCapacity taille du tampon d'undo/redo (<0 pour illimitï¿½ 0 pour hors service)
 */
void TDocument::setUndoBufferCapacity(int iUndoCapacity)
{
    this->iUndoCapacity = iUndoCapacity;
    if(this->iUndoCapacity >= 0)
    {
        while(this->undoActions.size() > uint(this->iUndoCapacity))
        {
            delete this->undoActions.front();
            this->undoActions.pop_front();
        }
    }
}

/** Undo last action */
void TDocument::undo()
{
    if(!this->undoActions.size())
        return;
    this->iComposedActionStack = 0;
    this->currentComposedAction = NULL;
    this->bModeUndo = false;
    this->undoActions.back()->executeAction();
    delete this->undoActions.back();
    this->undoActions.pop_back();
    this->bModeUndo = true;
}

/** Redo last action */
void TDocument::redo()
{
    if(!this->redoActions.size())
        return;

    this->bDoingRedo = true;
    this->redoActions.back()->executeAction();
    delete this->redoActions.back();
    this->redoActions.pop_back();
    this->bDoingRedo = false;
}

/**
 * démarre une action composée.
 * Une action composée est une action en regroupant d'autres plus petites pour
 * qu'un undo les annule toutes en une seule fois.
 */
void TDocument::startComposedAction()
{
    if(!this->iUndoCapacity)
        return;

    if(!this->currentComposedAction)
    {
        TComposedDocAction * act = new TComposedDocAction(this);
        this->addAction(act);
        this->currentComposedAction = act;
        this->iComposedActionStack = 1;
    }
    else
    {
        this->iComposedActionStack++;
    }
}

/**
 * termine une action composée.
 * si il y a eu plusieurs appels a startComposedAction, il en faut autant a stopComposedAction
 */
void TDocument::stopComposedAction()
{
    if(!this->currentComposedAction)
        this->iComposedActionStack = 0;
    else
    {
        this->iComposedActionStack--;
        if(!this->iComposedActionStack)
            this->currentComposedAction = NULL;
    }
}

/** ajoute une action a la pile des actions undo/redo */
void TDocument::addAction(TDocAction * action)
{
    if(this->iUndoCapacity == 0)
    {
        delete action;
        return;
    }

    if(this->currentComposedAction)
    {
        this->currentComposedAction->addAction(action);
    }
    else
    {
        if(this->bModeUndo)
        {
            this->undoActions.push_back(action);

            if(this->iUndoCapacity > 0)
            {
                while(this->undoActions.size() > uint(this->iUndoCapacity))
                {
                    delete this->undoActions.front();
                    this->undoActions.pop_front();
                }
            }

            while(!this->bDoingRedo && this->redoActions.size())
            {
                delete this->redoActions.front();
                this->redoActions.pop_front();
            }
        }
        else
        {
            this->redoActions.push_back(action);
        }
    }
}

/** dï¿½ruit toutes les infos de undo/redo */

/** dï¿½ruit toutes les infos de undo/redo */
void TDocument::clearUndoRedo()
{
    TDocActionsList::iterator itB = this->undoActions.begin();
    TDocActionsList::iterator itE = this->undoActions.end();

    while(itB != itE)
    {
        delete (*itB);
        itB++;
    }
    this->undoActions.clear();

    itB = this->redoActions.begin();
    itE = this->redoActions.end();

    while(itB != itE)
    {
        delete (*itB);
        itB++;
    }
    this->redoActions.clear();
}

/** recherche la ligne la plus longue et renvoie sa taille */
long TDocument::calcMaxLineLength() const
{
    long iSize = 0;
    for(TStringList::const_iterator it = this->textData.begin() ; it != this->textData.end() ; it++)
    {
        if(long((*it).length()) > iSize)
            iSize = (*it).length();
    }
    return iSize;
}

/** recherche le texte passe en parametre dans le document a partir de la position ptFrom */
TPoint TDocument::find(wxString s, const TPoint & ptFrom, bool bCaseSensitive)
{
    if(!s.length())
        return TPoint();

    if(ptFrom.isValid() && static_cast<unsigned int>(ptFrom.y) >= this->getLinesCount())
        return TPoint();

    if(!bCaseSensitive)
        s = s.Lower();

    TPoint pt = ptFrom;
    if(!pt.isValid())
        pt.x = pt.y = 0;

    wxString sSub;
    wxString * sSearchString;
    while(static_cast<unsigned int>(pt.y) < this->getLinesCount())
    {
        if(bCaseSensitive && !(pt.x))
        {
            sSearchString = &this->textData[pt.y];
        }
        else
        {
            if(pt.x)
            {
                sSub = this->textData[pt.y].Mid(pt.x);
                if(!bCaseSensitive)
                    sSub = sSub.Lower();
            }
            else
                sSub = this->textData[pt.y].Lower();

            sSearchString = &sSub;
        }

        int iPos = sSearchString->Find(s.c_str());
        if( iPos < 0)
        {
            pt.y++;
            pt.x = 0;
        }
        else
        {
            pt.x += iPos;
            return pt;
        }
    }

    return TPoint();
}

/** renvoie la taille du contenu de l'éditeur (équivalent à la taille si on écrivait le fichier sur disque) */
uint TDocument::getContentSize()
{
    uint iDocSize = 0;
    uint iLine = 0;
    for(TStringList::iterator it = this->textData.begin() ; it != this->textData.end() ; it++)
    {
        iDocSize += (*it).length();
        iLine++;
        if(iLine < this->getLinesCount())
            iDocSize += 1; // retour à la ligne
    }
    return iDocSize;
}

