/***************************************************************************
                        PreFormatter.cpp  -  description
                             -------------------
    begin                : Mo Jan 03 2005
    copyright            : (C) 2005-2023 by Andre Simon
    email                : a.simon@mailbox.org
 ***************************************************************************/


/*
This file is part of Highlight.

Highlight 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 3 of the License, or
(at your option) any later version.

Highlight 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 Highlight.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "preformatter.h"

#include <iostream>
#include "stringtools.h"

using std::string;

namespace highlight
{

const std::string PreFormatter::LB_CHARS = " \t[](){}-+<>.:,;";
const std::string PreFormatter::WS_CHARS = " \n\r\t";
const std::string PreFormatter::INDENT_MARKERS = "{(=";

PreFormatter::PreFormatter() :
    maxLineLength ( 80 ),
    index ( 0 ),
    numberSpaces ( 0 ),
    lineNumber ( 0 ),
    wsPrefixLength ( string::npos ),
    hasMore ( false ),
    indentAfterOpenBraces ( true ),
    redefineWsPrefix ( false ),
    wrapLines ( false ),
    replaceTabs ( false )
{
}

PreFormatter::~PreFormatter()
{
}

bool PreFormatter::hasMoreLines()
{
    return hasMore;
}

bool PreFormatter::indentCode()
{
    return indentAfterOpenBraces;
}

void PreFormatter::setLine ( const std::string& newLine )
{

    line=newLine;

    if ( replaceTabs && numberSpaces ) {
        size_t tabPos=line.find ( '\t' );
        while ( tabPos!=string::npos ) {
            line.replace ( tabPos , 1, numberSpaces - ( tabPos % numberSpaces ) , ' ' );
            tabPos = line.find ( '\t', tabPos+1 );
        }
    }

    if ( wrapLines ) {
        wsPrefix.clear();
        index=0;
        wsPrefixLength=string::npos;
        hasMore=true;
        redefineWsPrefix=false;
    }
}

std::string PreFormatter::getNextLine()
{

    if ( !wrapLines ) {
        hasMore = false;
        return line;
    }

    ++lineNumber;

    if ( !index && line.length() > maxLineLength ) { // erster Durchlauf...
        // wenn moeglich an oeffnender Klammer oder Geichheitszeichen ausrichten
        if ( indentAfterOpenBraces ) {
            wsPrefixLength=line.find_first_of ( INDENT_MARKERS );
        }
        // sonst die Einrckung der Originalzeile beibehalten
        if ( wsPrefixLength==string::npos || wsPrefixLength-index>maxLineLength ) {
            wsPrefixLength=line.find_first_not_of ( WS_CHARS );
        } else {
            // wsPrefix in allen neu umgebrochenen Zeilen durch Spaces ersetzen
            redefineWsPrefix=true;
            //  Position hinter oeffnende Klammer springen
            wsPrefixLength=line.find_first_not_of ( WS_CHARS,wsPrefixLength+1 );
        }

        if ( wsPrefixLength!=string::npos ) {
            index = wsPrefixLength;
            // Falls Anzahl der Whitespaces am Beginn der ersten Zeile groesser
            // als Max. Zeilenlaenge, Whitespaces verwerfen
            if ( wsPrefixLength>maxLineLength ) {
                wsPrefixLength=0;
                return string();
            } else {
                wsPrefix=line.substr ( 0, wsPrefixLength );
            }
        }
        // Zeile enthaelt nur Whitespace; verwerfen
        else {
            hasMore= false;
            return string();
        }
    } else {
        if ( redefineWsPrefix ) {
            wsPrefix.clear();
            wsPrefix.append ( wsPrefixLength, ' ' );
        }
        redefineWsPrefix=false;
    }

    string resultString;

    // Position, ab der rueckwaerts nach Umbruchmglichkeit gesucht wird
    unsigned int searchEndPos = maxLineLength - wsPrefixLength;

    // letztes Teilstueck der Zeile ausgeben; Parsen beenden
    if ( line.length()-index < searchEndPos ) {
        hasMore=false;
        resultString= ( index>0 ) ? wsPrefix + line.substr ( index ) : line.substr ( index );
        return resultString;
    }

    // Umbrechposition suchen
    size_t lbPos = line.find_last_of ( LB_CHARS, index+searchEndPos );
    if ( lbPos <= index || lbPos == string::npos ) {
        // nichts gefunden, hart umbrechen
        lbPos = index + searchEndPos;
    }
    // Einrckung der Originalzeile erhalten
    resultString+=wsPrefix;
    // Neue Zeile erzeugen
    resultString += line.substr ( index, lbPos-index+1 );

    // Whitespace am neuen Zeilenbeginn ignorieren, ausser beim ersten Durchlauf
    size_t newIndex=line.find_first_not_of ( WS_CHARS, lbPos+1 );
    index= ( newIndex!=string::npos ) ?newIndex:line.length();

    hasMore=index!=line.length(); // unnoetigen Leerstring vermeiden

    if ( hasMore ) wrappedLines.insert ( lineNumber ); // diese Zeile wurde umgebrochen

    return resultString;
}

void PreFormatter::setWrapLineLength ( unsigned int maxLineLength )
{
    this->maxLineLength = maxLineLength;
}
void PreFormatter::setWrapIndentBraces ( bool indentAfterOpenBraces )
{
    this->indentAfterOpenBraces = indentAfterOpenBraces;
}

void PreFormatter::setNumberSpaces ( unsigned int num )
{
    numberSpaces = num;
}

}
