/****************************************************************************
    Kalkulon - A programmable calculator for programmers

    This file is part of the Kalkulon GUI.

    If you find this source code useful, your are encouraged to use it in your own projects.
    Please give me credit!

    COPYRIGHT by Juergen Holetzeck 2003 - 2007
    e-mail:		contact@kalkulon.de
    homepage:	www.kalkulon.de
****************************************************************************/


#include <algorithm>
#include <QTextCharFormat>
#include <QSettings>

#include "SyntaxHighlighter.h"


SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent)
     : QSyntaxHighlighter(parent)
{
    restoreDefaults();
}

void SyntaxHighlighter::restoreDefaults()
{
    Settings s;

    s.sameDigitsHex_ = 4;
    s.sameDigitsBin_ = 4;
    s.sameDigitsDec_ = 3;
    s.sameDigitsOct_ = 2;

    s.numColor_[0] = QColor("black").rgb();
    s.numColor_[1] = QColor("blue").rgb();
    s.numColor_[2] = QColor("black").rgb();
    s.numColor_[3] = QColor("blue").rgb();
    s.numColor_[4] = QColor("black").rgb();
    s.numColor_[5] = QColor("green").rgb();
    s.numColor_[6] = QColor("black").rgb();
    s.numColor_[7] = QColor("green").rgb();
    s.opColor_     = QColor("red").rgb();
    s.funcColor_   = QColor("red").rgb();
    s.stringColor_ = QColor("darkGreen").rgb();
    s.labelColor_  = QColor("darkGray").rgb();

    setSettings(s);
}

void SyntaxHighlighter::setSettings(const Settings& s)
{
    s_=s;

    for(int i=0; i<NUM_NUMFORMATS; ++i)
        numFormat_[i].setForeground(QColor(s_.numColor_[i]));
    opFormat_.setForeground    (QColor(s_.opColor_));
    funcFormat_.setForeground  (QColor(s_.funcColor_));
    stringFormat_.setForeground(QColor(s_.stringColor_));
    labelFormat_.setForeground (QColor(s_.labelColor_));

    rehighlight();
};


void SyntaxHighlighter::highlightBlock(const QString &text)
{
    int textlen  = text.size();
    int pos = 0;
    while(pos<textlen)
    {
        // find hex, bin or oct numbers
        if(pos+1 < textlen && text[pos] == '0' &&
            ((text[pos+1] == 'x' || text[pos+1] == 'X') ||
            (text[pos+1] == 'b' || text[pos+1] == 'B') ||
            (text[pos+1] == 'o' || text[pos+1] == 'O')
            )
        )
        {
            // color base specifiers
            setFormat(pos, 2, numFormat_[0]);
            // hex numbers
            if(text[pos+1] == 'x' || text[pos+1] == 'X')
            {
                int begin;
                pos = begin = pos+2;
                while( pos<textlen && isxdigit(text[pos].toLatin1())) ++pos;
                doNumColoring(pos, begin, s_.sameDigitsHex_);
            }
            else
            // bin numbers
            if(text[pos+1] == 'b' || text[pos+1] == 'B')
            {
                int begin;
                pos+=2;
                begin = pos;
                while( pos<textlen && (text[pos] == '0' || text[pos] == '1')) ++pos;
                doNumColoring(pos, begin, s_.sameDigitsBin_);
            }
            else
            // oct numbers
            if(text[pos+1] == 'o' || text[pos+1] == 'O')
            {
                int begin;
                pos+=2;
                begin = pos;
                while( pos<textlen && (text[pos] >= '0' && text[pos] <= '7')) ++pos;
                doNumColoring(pos, begin, s_.sameDigitsOct_);
            }
        }
        else
            // find dec numbers
            if (isdigit(text[pos].toLatin1()))
            {
                int begin;
                begin = pos;
                while( pos<textlen && isdigit(text[pos].toLatin1())) ++pos;
                doNumColoring(pos, begin, s_.sameDigitsDec_);
                // handle fractional part
                if (pos<textlen && text[pos] == '.')
                {
                    setFormat(pos, 1, numFormat_[0]);

                    begin = ++pos;
                    while( pos<textlen && isdigit(text[pos].toLatin1())) ++pos;
                    doNumColoring(begin, pos, s_.sameDigitsDec_);
                }
                // handle exponent 'e'
                if(pos<textlen && (text[pos] == 'e' || text[pos] == 'E'))
                {
                    setFormat(pos, 1, numFormat_[0]);
                    ++pos;
                }
            }
            else
            {
                // "string"
                if(text[pos] == '"')
                {
                    int begin;
                    begin = pos;
                    ++pos;
                    while( pos<textlen && (text[pos]!='"')) ++pos;
                    ++pos;
                    setFormat(begin, pos-begin, stringFormat_);
                }
                else
                    // text
                    if(isalpha(text[pos].toLatin1()) || text[pos] == '#')
                    {
                        int begin;
                        begin = pos++;
                        while( pos<textlen && (isalnum(text[pos].toLatin1()) || text[pos]=='_' || isspace(text[pos].toLatin1()) || text[pos]=='@')) ++pos;
                        if(pos<textlen && text[pos] =='(')
                        {
                            // function call
                            setFormat(begin, pos-begin, funcFormat_);
                        }
                        else
                        {
                            // label
                            setFormat(begin, pos-begin, labelFormat_);
                        }
                    }
                    else
                    {
                        // find operators
                        char ch=text[pos].toLatin1();
                        switch(ch)
                        {
                        case ';':
                        case ',':
                        case '+':
                        case '-':
                        case '*':
                        case '/':
                        case '%':
                        case '~':
                        case '&':
                        case '^':
                        case '|':
                        case '=':
                        case '(':
                        case ')':
                        case '<':
                        case '>':
                        case '!':
                            setFormat(pos, 1, opFormat_);
                            break;
                        }
                        ++pos;
                    }
            }
    }	// end while
}

void SyntaxHighlighter::doNumColoring(int begin, int end, int same_digits)
{
    int i=0;
    if(begin>end)
        while(begin>end)
        {
            int from = std::max(end,begin-same_digits);
            int len = begin-from;
            setFormat(from, len, numFormat_[i]);
            ++i;
            i%=NUM_NUMFORMATS;
            begin -= same_digits;
        }
        else
            while(end>begin)
            {
                setFormat(begin, std::min(end-begin, same_digits), numFormat_[i]);
                ++i;
                i%=NUM_NUMFORMATS;
                begin += same_digits;
            }
}

void SyntaxHighlighter::writeSettings(QSettings *psettings)
{
    psettings->setValue("sameDigitsHex", s_.sameDigitsHex_);
    psettings->setValue("sameDigitsBin", s_.sameDigitsBin_);
    psettings->setValue("sameDigitsDec", s_.sameDigitsDec_);
    psettings->setValue("sameDigitsOct", s_.sameDigitsOct_);

    psettings->beginGroup("color");
        psettings->setValue("operators", s_.opColor_);
        psettings->setValue("functions", s_.funcColor_);
        psettings->setValue("strings",   s_.stringColor_);
        psettings->setValue("labels",    s_.labelColor_);

        psettings->beginWriteArray("numbers");
        for(int i=0; i<NUM_NUMFORMATS; ++i)
        {
            psettings->setArrayIndex(i);
            psettings->setValue("segment", s_.numColor_[i]);
        }
        psettings->endArray();
    psettings->endGroup();
}

void SyntaxHighlighter::readSettings(QSettings *psettings)
{
    Settings s;

    s.sameDigitsHex_ = psettings->value("sameDigitsHex", s_.sameDigitsHex_).toInt();
    s.sameDigitsBin_ = psettings->value("sameDigitsBin", s_.sameDigitsBin_).toInt();
    s.sameDigitsDec_ = psettings->value("sameDigitsDec", s_.sameDigitsDec_).toInt();
    s.sameDigitsOct_ = psettings->value("sameDigitsOct", s_.sameDigitsOct_).toInt();

    psettings->beginGroup("color");
            s.opColor_ 		= psettings->value("operators", s_.opColor_).toInt();
            s.funcColor_ 	= psettings->value("functions", s_.funcColor_).toInt();
            s.stringColor_ 	= psettings->value("strings", s_.stringColor_).toInt();
            s.labelColor_ 	= psettings->value("labels", s_.labelColor_).toInt();

        psettings->beginReadArray("numbers");
        for(int i=0; i<NUM_NUMFORMATS; ++i)
        {
            psettings->setArrayIndex(i);
            s.numColor_[i] = psettings->value("segment", s_.numColor_[i]).toInt();
        }
        psettings->endArray();
    psettings->endGroup();

    setSettings(s);
}

