// Scintilla source code edit control
// Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
/*
This is the Lexer for Gui4Cli, included in SciLexer.dll
- by d. Keletsekis, 2/10/2003

To add to SciLexer.dll:
1. Add the values below to INCLUDE\Scintilla.iface
2. Run the include/HFacer.py script
3. Run the src/lexGen.py script

val SCE_GC_DEFAULT=0
val SCE_GC_COMMENTLINE=1
val SCE_GC_COMMENTBLOCK=2
val SCE_GC_GLOBAL=3
val SCE_GC_EVENT=4
val SCE_GC_ATTRIBUTE=5
val SCE_GC_CONTROL=6
val SCE_GC_COMMAND=7
val SCE_GC_STRING=8
val SCE_GC_OPERATOR=9
*/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "LexerModule.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif

#define debug Platform::DebugPrintf

static inline bool IsAWordChar(const int ch) {
    return (ch < 0x80) && (isalnum(ch) || ch == '.' || ch == '_' || ch =='\\');
}

static inline bool IsAWordStart(const int ch) {
    return (ch < 0x80) && (isalnum(ch) || ch == '_' || ch == '.');
}

inline bool isGCOperator(int ch)
{   if (isalnum(ch))
        return false;
    // '.' left out as it is used to make up numbers
    if (ch == '*' || ch == '/' || ch == '-' || ch == '+' ||
         ch == '(' || ch == ')' || ch == '=' || ch == '%' ||
         ch == '[' || ch == ']' || ch == '<' || ch == '>' ||
         ch == ',' || ch == ';' || ch == ':')
        return true;
    return false;
}

#define isSpace(x)      ((x)==' ' || (x)=='\t')
#define isNL(x)         ((x)=='\n' || (x)=='\r')
#define isSpaceOrNL(x)  (isSpace(x) || isNL(x))
#define BUFFSIZE 500
#define isFoldPoint(x)  ((styler.LevelAt(x) & SC_FOLDLEVELNUMBERMASK) == 1024)

static void colorFirstWord(WordList *keywordlists[], Accessor &styler,
                                    StyleContext *sc, char *buff, int length, int)
{
    int c = 0;
    while (sc->More() && isSpaceOrNL(sc->ch))
    {   sc->Forward();
    }
    styler.ColourTo(sc->currentPos - 1, sc->state);

    if (!IsAWordChar(sc->ch)) // comment, marker, etc..
        return;

    while (sc->More() && !isSpaceOrNL(sc->ch) && (c < length-1) && !isGCOperator(sc->ch))
    {   buff[c] = static_cast<char>(sc->ch);
        ++c; sc->Forward();
    }
    buff[c] = '\0';
    char *p = buff;
    while (*p)  // capitalize..
    {   if (islower(*p)) *p = static_cast<char>(toupper(*p));
        ++p;
    }

    WordList &kGlobal       = *keywordlists[0]; // keyword lists set by the user
    WordList &kEvent        = *keywordlists[1];
    WordList &kAttribute    = *keywordlists[2];
    WordList &kControl  = *keywordlists[3];
    WordList &kCommand  = *keywordlists[4];

    int state = 0;
    // int level = styler.LevelAt(line) & SC_FOLDLEVELNUMBERMASK;
    // debug ("line = %d, level = %d", line, level);

    if       (kGlobal.InList(buff))     state = SCE_GC_GLOBAL;
    else if (kAttribute.InList(buff))   state = SCE_GC_ATTRIBUTE;
    else if (kControl.InList(buff))     state = SCE_GC_CONTROL;
    else if (kCommand.InList(buff))     state = SCE_GC_COMMAND;
    else if (kEvent.InList(buff))           state = SCE_GC_EVENT;

    if (state)
    {   sc->ChangeState(state);
        styler.ColourTo(sc->currentPos - 1, sc->state);
        sc->ChangeState(SCE_GC_DEFAULT);
    }
    else
    {   sc->ChangeState(SCE_GC_DEFAULT);
        styler.ColourTo(sc->currentPos - 1, sc->state);
    }
}

// Main colorizing function called by Scintilla
static void
ColouriseGui4CliDoc(unsigned int startPos, int length, int initStyle,
                    WordList *keywordlists[], Accessor &styler)
{
    styler.StartAt(startPos);

    int quotestart = 0, oldstate, currentline = styler.GetLine(startPos);
    styler.StartSegment(startPos);
    bool noforward;
    char buff[BUFFSIZE+1];  // buffer for command name

    StyleContext sc(startPos, length, initStyle, styler);
    buff[0] = '\0'; // cbuff = 0;

    if (sc.state != SCE_GC_COMMENTBLOCK) // colorize 1st word..
        colorFirstWord(keywordlists, styler, &sc, buff, BUFFSIZE, currentline);

    while (sc.More())
    {   noforward = 0;

        switch (sc.ch)
        {
            case '/':
                if (sc.state == SCE_GC_COMMENTBLOCK || sc.state == SCE_GC_STRING)
                    break;
                if (sc.chNext == '/')   // line comment
                {   sc.SetState (SCE_GC_COMMENTLINE);
                    sc.Forward();
                    styler.ColourTo(sc.currentPos, sc.state);
                }
                else if (sc.chNext == '*')  // block comment
                {   sc.SetState(SCE_GC_COMMENTBLOCK);
                    sc.Forward();
                    styler.ColourTo(sc.currentPos, sc.state);
                }
                else
                    styler.ColourTo(sc.currentPos, sc.state);
                break;

            case '*':   // end of comment block, or operator..
                if (sc.state == SCE_GC_STRING)
                    break;
                if (sc.state == SCE_GC_COMMENTBLOCK && sc.chNext == '/')
                {   sc.Forward();
                    styler.ColourTo(sc.currentPos, sc.state);
                    sc.ChangeState (SCE_GC_DEFAULT);
                }
                else
                    styler.ColourTo(sc.currentPos, sc.state);
                break;

            case '\'':  case '\"': // strings..
                if (sc.state == SCE_GC_COMMENTBLOCK || sc.state == SCE_GC_COMMENTLINE)
                    break;
                if (sc.state == SCE_GC_STRING)
                {   if (sc.ch == quotestart)    // match same quote char..
                    {   styler.ColourTo(sc.currentPos, sc.state);
                        sc.ChangeState(SCE_GC_DEFAULT);
                        quotestart = 0;
                }   }
                else
                {   styler.ColourTo(sc.currentPos - 1, sc.state);
                    sc.ChangeState(SCE_GC_STRING);
                    quotestart = sc.ch;
                }
                break;

            case ';':   // end of commandline character
                if (sc.state != SCE_GC_COMMENTBLOCK && sc.state != SCE_GC_COMMENTLINE &&
                     sc.state != SCE_GC_STRING)
                {
                    styler.ColourTo(sc.currentPos - 1, sc.state);
                    styler.ColourTo(sc.currentPos, SCE_GC_OPERATOR);
                    sc.ChangeState(SCE_GC_DEFAULT);
                    sc.Forward();
                    colorFirstWord(keywordlists, styler, &sc, buff, BUFFSIZE, currentline);
                    noforward = 1; // don't move forward - already positioned at next char..
                }
                break;

            case '+': case '-': case '=':   case '!':   // operators..
            case '<': case '>': case '&': case '|': case '$':
                if (sc.state != SCE_GC_COMMENTBLOCK && sc.state != SCE_GC_COMMENTLINE &&
                     sc.state != SCE_GC_STRING)
                {
                    styler.ColourTo(sc.currentPos - 1, sc.state);
                    styler.ColourTo(sc.currentPos, SCE_GC_OPERATOR);
                    sc.ChangeState(SCE_GC_DEFAULT);
                }
                break;

            case '\\':  // escape - same as operator, but also mark in strings..
                if (sc.state != SCE_GC_COMMENTBLOCK && sc.state != SCE_GC_COMMENTLINE)
                {
                    oldstate = sc.state;
                    styler.ColourTo(sc.currentPos - 1, sc.state);
                    sc.Forward(); // mark also the next char..
                    styler.ColourTo(sc.currentPos, SCE_GC_OPERATOR);
                    sc.ChangeState(oldstate);
                }
                break;

            case '\n': case '\r':
                ++currentline;
                if (sc.state == SCE_GC_COMMENTLINE)
                {   styler.ColourTo(sc.currentPos, sc.state);
                    sc.ChangeState (SCE_GC_DEFAULT);
                }
                else if (sc.state != SCE_GC_COMMENTBLOCK)
                {   colorFirstWord(keywordlists, styler, &sc, buff, BUFFSIZE, currentline);
                    noforward = 1; // don't move forward - already positioned at next char..
                }
                break;

//          case ' ': case '\t':
//          default :
        }

        if (!noforward) sc.Forward();

    }
    sc.Complete();
}

// Main folding function called by Scintilla - (based on props (.ini) files function)
static void FoldGui4Cli(unsigned int startPos, int length, int,
                                WordList *[], Accessor &styler)
{
    bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;

    unsigned int endPos = startPos + length;
    int visibleChars = 0;
    int lineCurrent = styler.GetLine(startPos);

    char chNext = styler[startPos];
    int styleNext = styler.StyleAt(startPos);
    bool headerPoint = false;

    for (unsigned int i = startPos; i < endPos; i++)
    {
        char ch = chNext;
        chNext = styler[i+1];

        int style = styleNext;
        styleNext = styler.StyleAt(i + 1);
        bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');

        if (style == SCE_GC_EVENT || style == SCE_GC_GLOBAL)
        {   headerPoint = true; // fold at events and globals
        }

        if (atEOL)
        {   int lev = SC_FOLDLEVELBASE+1;

            if (headerPoint)
                lev = SC_FOLDLEVELBASE;

            if (visibleChars == 0 && foldCompact)
                lev |= SC_FOLDLEVELWHITEFLAG;

            if (headerPoint)
                lev |= SC_FOLDLEVELHEADERFLAG;

            if (lev != styler.LevelAt(lineCurrent)) // set level, if not already correct
            {   styler.SetLevel(lineCurrent, lev);
            }

            lineCurrent++;      // re-initialize our flags
            visibleChars = 0;
            headerPoint = false;
        }

        if (!(isspacechar(ch))) // || (style == SCE_GC_COMMENTLINE) || (style != SCE_GC_COMMENTBLOCK)))
            visibleChars++;
    }

    int lev = headerPoint ? SC_FOLDLEVELBASE : SC_FOLDLEVELBASE+1;
    int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
    styler.SetLevel(lineCurrent, lev | flagsNext);
}

// I have no idea what these are for.. probably accessible by some message.
static const char * const gui4cliWordListDesc[] = {
    "Globals", "Events", "Attributes", "Control", "Commands",
    0
};

// Declare language & pass our function pointers to Scintilla
LexerModule lmGui4Cli(SCLEX_GUI4CLI, ColouriseGui4CliDoc, "gui4cli", FoldGui4Cli, gui4cliWordListDesc);

#undef debug

