/*
 * re_plugin_highlighter.c
 *
 * A programmers editor
 *
 * re_data plugin to support the syntax highlighter.
 *
 * Author: Dario Rodriguez antartica@whereismybit.com
 * This program is licensed under the terms of GNU GPL v2.1+
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>


#include "re_plugin_highlighter.h"

#define PLUGINNAME "highlighter"
#define HLINETBLOCK (16*1024)
#define BUFBLOCK (64*1024)
#define HIGHLIGHTERGROWSIZE (256*1024)
#define DEFAULTCOLOR "\x80\x80\x80\xff"


typedef enum colorsenum_t {
        color_normal=0,
        color_directive,
        color_directivekeyword,
        color_directivestring,
        color_directiveinclude,
        color_directiveincludestring,
        color_keyword,
        color_string,
        color_multilinecomment,
        color_linecomment,
        color_number,
        color_operator,
        color_symbol,
        color_endenum,
} colorsenum_t;


static int redata_highlighter_add_or_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo, int is_unadd);
static int redata_highlighter_postload(redata_t *redata, redata_plugin_t *slot,char *filename);

static int hl_invalidatefrom(redata_t *redata, highlighter_t *hl, int nline);
hcolor_t *hl_initcolors(int *ncolors, /* int color, char *colordef, */ ...);
static highlighter_t *hl_getplugin(redata_t *redata);
static int hl_doline(redata_t *redata, highlighter_t *hl, int nline);
int hl_C_getkeywords(char ***keywords,int *nkeywords, int *maxlen);
int hl_C_getdirectives(char ***directives,int *ndirectives, int *maxlen);
int hl_searchlist(char **wordlist, int wordlistlen, char *word, int wordlen, int *res);
linecolor_t *hl_addtolinecolor(int *opaque, highlighter_t *hl,linecolor_t *linecolor,int posoff, int color);

int
redata_highlighter_register(redata_t *redata, redata_plugin_t *slot)
{
        hcolor_t *colors;
        int ncolors;
        highlighter_t *hl;
        if(redata==NULL || slot==NULL)
                return(-1);
#if 1
        /* "forest" theme */
        colors=hl_initcolors(&ncolors,
                             color_keyword,"\x38\x17\x1e\xff",
                             color_directive,"\x38\x4b\x00\xff",
                             color_directivekeyword,"\x63\x7d\x16\xff",
                             color_directivestring,"\x07\x20\x3b\xff",
                             color_directiveinclude,"\x16\x63\x7d\xff",
                             color_directiveincludestring,"\x6\x13\x2d\xff",
                             color_normal,"\x9a\x67\x43\xff",
                             color_string,"\x9d\x15\x00\xff",
                             color_multilinecomment,"\x4f\x40\x57\xff",
                             color_linecomment,"\xc6\x8c\xa4\xff",
                             color_number,"\x3b\x10\x35\xff",
                             color_operator,"\xaa\x8c\xcd\xff",
                             color_symbol,"\x69\x2a\x44\xff",
                             color_endenum,DEFAULTCOLOR,
                             -1);
#else
        /* "alternative" theme (dark) */
        colors=hl_initcolors(&ncolors,
                             color_keyword,"\xcc\x99\xcc\xff",//
                             color_directive,"\x38\x4b\x00\xff",
                             color_directivekeyword,"\x63\x7d\x16\xff",
                             color_directivestring,"\x07\x20\x3b\xff",
                             color_directiveinclude,"\x16\x63\x7d\xff",
                             color_directiveincludestring,"\x6\x13\x2d\xff",
                             color_normal,"\xf2\xf0\xec\xff",//
                             color_string,"\x99\xcc\x99\xff",//
                             color_multilinecomment,"\x74\x73\x69\xff",//
                             color_linecomment,"\x74\x73\x69\xff",//
                             color_number,"\xf9\x91\x57\xff",//
                             color_operator,"\x66\xcc\xcc\xff",//
                             color_symbol,"\x91\xa7\xff\xff",//
                             color_endenum,"\xf2\xf0\xec\xff",//
                             -1);
#endif
        if(colors==NULL || (hl=malloc(sizeof(highlighter_t)))==NULL) {
                if(colors!=NULL)
                        free(colors),colors=NULL;
                return(-1); /* insufficient memory */
        }
        memset(hl,0,sizeof(highlighter_t));
        hl->sizebuf=hl->usedbuf=0;
        hl->sizecolors=ncolors;
        hl->colors=colors;
        strncpy(slot->name,PLUGINNAME,sizeof(slot->name));
        slot->name[sizeof(slot->name)-1]='\0';
        slot->unregister=redata_highlighter_unregister;
        slot->postload=redata_highlighter_postload;
        slot->add_or_unadd=redata_highlighter_add_or_unadd;
        slot->userptr=hl;
        return(0);
}

int
redata_highlighter_unregister(redata_t *redata, redata_plugin_t *slot, char *filename)
{
        highlighter_t *hl=(highlighter_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || hl==NULL)
                return(-1);
        if(hl->buf!=NULL)
                free(hl->buf),hl->buf=NULL;
        hl->sizebuf=hl->usedbuf=0;
        if(hl->colors!=NULL)
                free(hl->colors),hl->colors=NULL;
        hl->sizecolors=0;
        if(hl->lines!=NULL)
                free(hl->lines),hl->lines=NULL;
        hl->sizelines=hl->usedlines=0;
        if(hl->keywordbuf!=NULL)
                free(hl->keywordbuf),hl->keywordbuf=NULL;
        hl->sizekeywordbuf=hl->usedkeywordbuf=0;
        if(hl->directivebuf!=NULL)
                free(hl->directivebuf),hl->directivebuf=NULL;
        hl->sizedirectivebuf=hl->useddirectivebuf=0;
        if(slot->userptr!=NULL)
                free(slot->userptr),slot->userptr=NULL;
        return(0);
}

hcolor_t *
redata_highlighter_getcolors(redata_t *redata, int *ncolors)
{
        highlighter_t *hl;
        if(redata==NULL || (hl=hl_getplugin(redata))==NULL)
                return(NULL); /* sanity check failed or plugin not found */
        if(ncolors!=NULL)
                *ncolors=hl->sizecolors;
        return(hl->colors);
}

linecolor_t *
redata_highlighter_getline(redata_t *redata, int line, int *nlinecolors)
{
        highlighter_t *hl;
        if(redata==NULL || line<0 || (hl=hl_getplugin(redata))==NULL)
                return(NULL); /* sanity check failed or plugin not found */
        if(!hl->flag_doneall)
                hl_doline(redata,hl,line);
        if(line<0 || line>=hl->usedlines)
                return(NULL);
        if(nlinecolors!=NULL)
                *nlinecolors=(hl->lines[line].len)/sizeof(linecolor_t);
        return((linecolor_t *) (hl->buf+hl->lines[line].off));
}

int
redata_highlighter_getcolorindex(redata_t *redata, int line, int nthbyte)
{
        int i,n;
        int nlinecolors;
        linecolor_t *linecolors;
        if((linecolors=redata_highlighter_getline(redata,line,&nlinecolors))==NULL)
                return(-1);
        for(i=0,n=0;n<nlinecolors;i+=linecolors[n].len,n++) {
                if(nthbyte<(i+linecolors[n].len))
                        return(linecolors[n].color);
        }
        return(-1);
}

static void
redata_highlighter_util_applytheme(int *template3,hcolor_t *color,int invert)
{
        if(template3==NULL || color==NULL || template3[0]<0 || template3[0]>2 || template3[1]<0 || template3[1]>2 || template3[2]<0 || template3[2]>2)
                return; /* sanity check error */
        if(invert==0) {
                ((unsigned char *)color->rgba)[0]=((unsigned char *)color->origrgba)[template3[0]];
                ((unsigned char *)color->rgba)[1]=((unsigned char *)color->origrgba)[template3[1]];
                ((unsigned char *)color->rgba)[2]=((unsigned char *)color->origrgba)[template3[2]];
        } else {
                ((unsigned char *)color->rgba)[0]=255-((unsigned char *)color->origrgba)[template3[0]];
                ((unsigned char *)color->rgba)[1]=255-((unsigned char *)color->origrgba)[template3[1]];
                ((unsigned char *)color->rgba)[2]=255-((unsigned char *)color->origrgba)[template3[2]];
        }
        return;
}

int
redata_highlighter_settheme(redata_t *redata, int ntheme, int invert)
{
        int permutations[6][3]={{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,1,0},{2,0,1}};
        int *permutationtemplate;
        hcolor_t *colors;
        int ncolors,i;
        ntheme=(ntheme<0)?0:ntheme;
        ntheme%=6;
        permutationtemplate=permutations[ntheme];
        if((colors=redata_highlighter_getcolors(redata, &ncolors))==NULL)
                return(-1);
        for(i=0;i<ncolors;i++)
                redata_highlighter_util_applytheme(permutationtemplate,colors+i,invert);
        return(0);
}

static int
redata_highlighter_add_or_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo, int is_unadd)
{
        long pos;
        int nline;
        highlighter_t *hl=(highlighter_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || hl==NULL || undo==NULL || slot->active==0)
                return(-1); /* sanity check failed */
        if(hl->usedlines==0) {
                return(0); /* nothing to do */
        }
        /* get the first pos of the operation */
        pos=undo->posorig;
        if((!is_unadd && undo->type=='D') || (is_unadd && undo->type=='A'))
                pos-=undo->len;
        if(undo->type=='M' && undo->posdest<pos)
                pos=undo->posdest;
        /* get line of pos */
        for(nline=0;nline<hl->usedlines;nline++) {
                if(hl->lines[nline].pos>pos) {
                        nline--;
                        break;
                }
        }
        /* special case: check if pos is inside last line */
        if(nline>=hl->usedlines)
                nline=hl->usedlines-1;
        /* invalidate from this line on */
        nline=(nline<0)?0:nline;
        hl_invalidatefrom(redata,hl,nline);
        return(0);
}

static int
redata_highlighter_postload(redata_t *redata, redata_plugin_t *slot,char *filename)
{
#warning XXX TODO: get the language from the filename
        return(-1);
}


static int
hl_invalidatefrom(redata_t *redata, highlighter_t *hl, int nline)
{
        if(redata==NULL || hl==NULL || nline<0)
                return(-1); /* sanity check failed */
        if(nline>=hl->usedlines)
                return(0); /* nothing to do */
        if(nline==0) {
                hl->usedbuf=0;
                hl->usedlines=0;
        }
        hl->usedbuf=hl->lines[nline].off;
        hl->usedlines=nline;
        hl->flag_doneall=0;
        return(0);
}

hcolor_t *
hl_initcolors(int *ncolors, /* int color, char *colordef, */ ...)
{
        int maxcolor;
        int color;
        char *colordef;
        int round;
        hcolor_t *hcolors;
        va_list paramlist;
        int i;
        static char defcolor[]={DEFAULTCOLOR};
        if(ncolors==NULL)
                return(NULL); /* sanity check failed */
        *ncolors=0;
        hcolors=NULL;
        for(round=0;round<2;round++) {
                maxcolor=-1;
                va_start(paramlist,ncolors);
                while((color=va_arg(paramlist,int))>=0) {
                        colordef=va_arg(paramlist,char *);
                        maxcolor=(maxcolor<color)?color:maxcolor;
                        if(round==1) {
                                strncpy(hcolors[color].origrgba,colordef,sizeof(hcolors[color].origrgba));
                                hcolors[color].origrgba[sizeof(hcolors[color].origrgba)-1]='\0';
                                strncpy(hcolors[color].rgba,colordef,sizeof(hcolors[color].rgba));
                                hcolors[color].rgba[sizeof(hcolors[color].rgba)-1]='\0';
                        }
                }
                va_end(paramlist);
                if(maxcolor<0)
                        return(NULL); /* no colors were defined */
                if(round==0) {
                        if((hcolors=malloc(sizeof(hcolor_t)*(maxcolor+1)))==NULL)
                                return(NULL); /* insufficient memory */
                        memset(hcolors,0,sizeof(hcolor_t)*(maxcolor+1));
                        for(i=0;i<=maxcolor;i++)
                                memcpy(hcolors[i].rgba,defcolor,sizeof(defcolor));
                }
        }
        *ncolors=maxcolor+1;
        return(hcolors);
}

static highlighter_t *
hl_getplugin(redata_t *redata)
{
        highlighter_t *hl;
        int i;
        if(redata==NULL)
                return(NULL); /* sanity check failed */
        for(i=0;i<redata->sizeplugins;i++) {
                if(strcmp(redata->plugins[i].name,PLUGINNAME)==0)
                        break;
        }
        if(i>=redata->sizeplugins || redata->plugins[i].active==0)
                return(NULL); /* plugin not found or nor active */
        hl=(highlighter_t *) (redata->plugins[i].userptr);
        if(hl==NULL)
                return(NULL); /* internal error */
        return(hl);
}

static int
hl_doline(redata_t *redata, highlighter_t *hl, int nline)
{
        int i;
        long realpos;
        long pos;
        char *ptr;
        int len;
        hline_t *line;
        long redataused;
        int has_nl;
        int has_next;
        char **keywords;
        int nkeywords;
        char **directives;
        int ndirectives;
        int maxlenkeywords,maxlendirectives;
        linecolor_t *linecolor;
        int prev_char;
        int prev_char_mask;
        int cant_define;
        int cant_directivekeyword;
        int is_directiveinclude;
        int opaque;
        int posoffset;
        enum {
                mode_whatever=0,
                mode_in_multilinecomment,
                mode_in_linecomment,
                mode_in_directive,
                mode_in_string,
                mode_in_singlestring,
                mode_in_directivestring,
        } mode,mlcprevmode;
        if(redata==NULL || hl==NULL || nline<0)
                return(-1); /* sanoty check failed */
        if(hl->usedlines>nline)
                return(0); /* nothing to do */
        if(hl_C_getkeywords(&keywords,&nkeywords,&maxlenkeywords)==-1)
                return(-1); /* couldn't get keyword list */
        if(hl_C_getdirectives(&directives,&ndirectives,&maxlendirectives)==-1)
                return(-1); /* couldn't get directive list */
        /* make sure keywordbuf is large enough */
        if(maxlenkeywords>hl->sizekeywordbuf) {
                char *newkeywordbuf;
                int newsize;
                newsize=maxlenkeywords;
                if((newkeywordbuf=realloc(hl->keywordbuf,newsize))==NULL)
                        return(-1); /* insufficient memory */
                memset(newkeywordbuf,0,newsize);
                hl->keywordbuf=newkeywordbuf;
                hl->sizekeywordbuf=newsize;
                hl->usedkeywordbuf=0;
        }
        /* make sure directivebuf is large enough */
        if(maxlendirectives>hl->sizedirectivebuf) {
                char *newdirectivebuf;
                int newsize;
                newsize=maxlendirectives;
                if((newdirectivebuf=realloc(hl->directivebuf,newsize))==NULL)
                        return(-1); /* insufficient memory */
                memset(newdirectivebuf,0,newsize);
                hl->directivebuf=newdirectivebuf;
                hl->sizedirectivebuf=newsize;
                hl->useddirectivebuf=0;
        }
        /* make sure we have enough hline_t structs */
        if(hl->sizelines<=nline) {
                hline_t *newlines;
                int newsize=(nline+1+HLINETBLOCK)/HLINETBLOCK;
                newsize*=HLINETBLOCK;
                if((newlines=realloc(hl->lines,newsize*sizeof(hline_t)))==NULL)
                        return(-1); /* insufficient memory */
                hl->lines=newlines;
                memset(hl->lines+hl->sizelines,0,(newsize-hl->sizelines)*sizeof(hline_t));
                hl->sizelines=newsize;
        }
        /* make sure we have previous lines highlighted */
        for(i=hl->usedlines;i<nline;i++) {
                if(hl_doline(redata,hl,i)==-1)
                        return(-1); /* error highlighting line */             
        }
        hl_invalidatefrom(redata,hl,nline);
        line=hl->lines+nline;
        if(redata_linecol2pos(redata,nline,0,&realpos,NULL)==-1)
                return(-1); /* couldn't get line pos */
        /* NOTE: here comes the highlighter */
        line->pos=realpos;
        line->off=(nline==0)?0:hl->lines[nline-1].off+hl->lines[nline-1].len;
        line->len=0;
        line->endingmode=mode_whatever;
        redataused=redata_getused(redata);
        mode=(nline>0)?line[-1].endingmode:mode_whatever;
        cant_define=0;
        cant_directivekeyword=0;
        is_directiveinclude=0;
        opaque=0;
        linecolor=hl_addtolinecolor(&opaque,hl,NULL,color_normal,0);
        posoffset=0;
        prev_char='\0';
        prev_char_mask=0xff;
        hl->usedkeywordbuf=0;
        hl->useddirectivebuf=0;
        mlcprevmode=mode_whatever;
        do {
                if(redata_line_rawinfo(redata,realpos+posoffset,&pos,&ptr,&len,NULL)==-1)
                        return(-1); /* couldn't get line data */                
                has_nl=((len>0 && ptr[len-1]=='\n')?1:0);
                has_next=(len==0)?1:(ptr[len-1]=='\n')?0:1;
                /* special case: line with only a newline */
                if(posoffset==0 && has_nl==1 && len==1) {
                        /* delete the existing linecolor and break */
                        line->len=0;
                        break;
                }
                /* iterate */
                for(i=0;i<(len-has_nl);prev_char=(ptr[i]&prev_char_mask),prev_char_mask=0xff,i++) {
                        /* special case: keyword ends in a change of mode */
                        if(mode!=mode_whatever && hl->usedkeywordbuf>0) {
                                if(hl_searchlist(keywords,nkeywords,hl->keywordbuf,hl->usedkeywordbuf,NULL)==0) {
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->keywordbufstart,color_keyword);
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->keywordbufstart+hl->usedkeywordbuf-1,color_keyword);
                                }
                                hl->usedkeywordbuf=0;
                        }
                        /* end of special case */
                        if(mode==mode_whatever) {
                                if(prev_char=='\\' || ptr[i]=='\\') {
                                        /* escape char mark or escaped char */
                                        continue;
                                }
                                if(prev_char=='/' && ptr[i]=='/') {
                                        mode=mode_in_linecomment;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i-1,color_linecomment);
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_linecomment);
                                        continue;
                                }
                                if(prev_char=='/' && ptr[i]=='*') {
                                        mlcprevmode=mode;
                                        mode=mode_in_multilinecomment;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i-1,color_multilinecomment);
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_multilinecomment);
                                        continue;
                                }
                                if(ptr[i]=='\"') {
                                        mode=mode_in_string;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_string);
                                        continue;
                                }
                                if(ptr[i]=='\'') {
                                        mode=mode_in_singlestring;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_string);
                                        continue;
                                }
                                if(!cant_define && ptr[i]=='#') {
                                        mode=mode_in_directive;
                                        is_directiveinclude=0;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_directive);
                                        continue;
                                }
                                /* keyword detection */
                                if(hl->usedkeywordbuf==0 && ((ptr[i]>='a' && ptr[i]<='z') || (ptr[i]>='A' && ptr[i]<='Z') || ptr[i]=='_')) {
                                        cant_define=1;
                                        hl->keywordbuf[hl->usedkeywordbuf++]=ptr[i];
                                        hl->keywordbufstart=posoffset+i;
                                } else if(hl->usedkeywordbuf>0 && ((ptr[i]>='0' && ptr[i]<='9') || (ptr[i]>='a' && ptr[i]<='z') || (ptr[i]>='A' && ptr[i]<='Z') || ptr[i]=='_')) {
                                        if(hl->usedkeywordbuf<hl->sizekeywordbuf) {
                                                hl->keywordbuf[hl->usedkeywordbuf++]=ptr[i];
                                        } else {
                                                hl->keywordbuf[0]='\0'; /* too long */
                                        }
                                }
                                if(hl->usedkeywordbuf>0 && !((ptr[i]>='0' && ptr[i]<='9') || (ptr[i]>='a' && ptr[i]<='z') || (ptr[i]>='A' && ptr[i]<='Z') || ptr[i]=='_')) {
                                        if(hl_searchlist(keywords,nkeywords,hl->keywordbuf,hl->usedkeywordbuf,NULL)==0) {
                                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->keywordbufstart,color_keyword);
                                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->keywordbufstart+hl->usedkeywordbuf-1,color_keyword);
                                        }
                                        hl->usedkeywordbuf=0;

                                }
                                /* keyword detection */
                                if(strchr("0123456789",ptr[i])!=NULL) {
                                        cant_define=1;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_number);
                                        continue;
                                }
                                if(strchr("<>=+-%*/!|&^",ptr[i])!=NULL) {
                                        cant_define=1;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_operator);
                                        continue;
                                }
                                if(strchr("(){}[],;",ptr[i])!=NULL) {
                                        cant_define=1;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_symbol);
                                        continue;
                                }
                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_normal);
                                continue;
                        }
                        if(mode==mode_in_multilinecomment) {
                                if(prev_char=='\\' || ptr[i]=='\\') {
                                        continue;
                                }
                                if(prev_char=='*' && ptr[i]=='/') {
                                        prev_char_mask=0x00;
                                        mode=mlcprevmode;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_multilinecomment);
                                        continue;
                                }
                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_multilinecomment);
                                continue;
                        }
                        if(mode==mode_in_directive) {
                                if(prev_char=='\\' || ptr[i]=='\\') {
                                        continue;
                                }
                                if(prev_char=='/' && ptr[i]=='/') {
                                        mode=mode_in_linecomment;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i-1,color_linecomment);
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_linecomment);
                                        continue;
                                }
                                if(prev_char=='/' && ptr[i]=='*') {
                                        mlcprevmode=mode;
                                        mode=mode_in_multilinecomment;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i-1,color_multilinecomment);
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_multilinecomment);
                                        continue;
                                }
                                /* directive detection */
                                if(!cant_directivekeyword && hl->useddirectivebuf==0 && ((ptr[i]>='a' && ptr[i]<='z') || (ptr[i]>='A' && ptr[i]<='Z') || ptr[i]=='_')) {
                                        hl->directivebuf[hl->useddirectivebuf++]=ptr[i];
                                        hl->directivebufstart=posoffset+i;
                                } else if(hl->useddirectivebuf>0 && ((ptr[i]>='0' && ptr[i]<='9') || (ptr[i]>='a' && ptr[i]<='z') || (ptr[i]>='A' && ptr[i]<='Z') || ptr[i]=='_')) {
                                        if(hl->useddirectivebuf<hl->sizedirectivebuf) {
                                                hl->directivebuf[hl->useddirectivebuf++]=ptr[i];
                                        } else {
                                                hl->directivebuf[0]='\0'; /* too long */
                                                cant_directivekeyword=1;
                                        }
                                }
                                if(hl->useddirectivebuf>0 && !((ptr[i]>='0' && ptr[i]<='9') || (ptr[i]>='a' && ptr[i]<='z') || (ptr[i]>='A' && ptr[i]<='Z') || ptr[i]=='_')) {
                                        int nfound;
                                        if(hl_searchlist(directives,ndirectives,hl->directivebuf,hl->useddirectivebuf,&nfound)==0) {
                                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->directivebufstart,color_directivekeyword);
                                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->directivebufstart+hl->useddirectivebuf-1,color_directivekeyword);
                                                if(strcmp(directives[nfound],"include")==0)
                                                        is_directiveinclude=1;
                                        }
                                        hl->useddirectivebuf=0;
                                        cant_directivekeyword=1;
                                }
                                if(ptr[i]!=' ' && ptr[i]!='\t')
                                        cant_directivekeyword=1;
                                /* directive detection */
                                if(ptr[i]=='\"') {
                                        mode=mode_in_directivestring;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,(is_directiveinclude==0)?color_directivestring:color_directiveincludestring);
                                        continue;
                                }
                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,(is_directiveinclude==0)?color_directive:color_directiveinclude);
                                continue;
                        }
                        if(mode==mode_in_directivestring) {
                                if(prev_char=='\\' || ptr[i]=='\\') {
                                        continue;
                                }
                                if(ptr[i]=='\"') {
                                        mode=mode_in_directive;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,(is_directiveinclude==0)?color_directivestring:color_directiveincludestring);
                                        continue;
                                }
                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,(is_directiveinclude==0)?color_directivestring:color_directiveincludestring);
                                continue;
                        }
                        if(mode==mode_in_string || mode_in_singlestring) {
                                if(prev_char=='\\' || ptr[i]=='\\') {
                                        continue;
                                }
                                if((mode==mode_in_string && ptr[i]=='\"')
                                  || (mode==mode_in_singlestring && ptr[i]=='\'')) {
                                        mode=mode_whatever;
                                        linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_string);
                                        continue;
                                }
                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_string);
                                continue;
                        }
                        if(mode==mode_in_linecomment) {
                                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,posoffset+i,color_linecomment);
                                continue;
                        }
                }
                posoffset+=len;
        } while(has_next!=0 && (realpos+posoffset)<redataused);
        /* special case: keyword ends at end-of-line */
        if(mode==mode_whatever && hl->usedkeywordbuf>0 && hl_searchlist(keywords,nkeywords,hl->keywordbuf,hl->usedkeywordbuf,NULL)==0) {
                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->keywordbufstart,color_keyword);
                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->keywordbufstart+hl->usedkeywordbuf-1,color_keyword);
        }
        /* end of special case */
        /* special case: deirectivekeyword ends at end-of-line */
        if(mode==mode_in_directive && hl->useddirectivebuf>0 && hl_searchlist(directives,ndirectives,hl->directivebuf,hl->useddirectivebuf,NULL)==0) {
                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->directivebufstart,color_directivekeyword);
                linecolor=hl_addtolinecolor(&opaque,hl,linecolor,hl->directivebufstart+hl->useddirectivebuf-1,color_directivekeyword);
        }
        /* end of special case */
        if(linecolor==NULL)
                return(-1);
        if((prev_char=='\\' && mode!=mode_in_linecomment) || mode==mode_in_multilinecomment)
                line->endingmode=mode;
        hl->usedlines++;
        return(0);
}


int
hl_C_getkeywords(char ***keywords,int *nkeywords, int *maxlen)
{
        static int init=0;
        static int staticnkeywords=0;
        static int staticmaxlen=0;
        static const char *C_keywords[]={
"auto",
"break",
"case",
"char",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extern",
"float",
"for",
"goto",
"if",
"inline",
"int",
"long",
"register",
"restrict",
"return",
"short",
"signed",
"sizeof",
"static",
"struct",
"switch",
"typedef",
"union",
"unsigned",
"void",
"volatile",
"while",
"_Alignas",
"_Alignof",
"_Atomic",
"_Bool",
"_Complex",
"_Generic",
"_Imaginary",
"_Noreturn",
"_Static_assert",
"_Thread_local",
NULL
};
        if(keywords==NULL || nkeywords==NULL)
                return(-1);
        if(init==0) {
                int k,l,maxl;
                for(k=0,maxl=0;C_keywords[k]!=NULL;k++) {
                        l=strlen(C_keywords[k]);
                        maxl=(l>maxl)?l:maxl;
                }
                staticnkeywords=k;
                staticmaxlen=maxl;
                init=1;
        }
        *keywords=(char **)C_keywords;
        *nkeywords=staticnkeywords;
        if(maxlen!=NULL)
                *maxlen=staticmaxlen;
        return(0);
}

int
hl_C_getdirectives(char ***directives,int *ndirectives, int *maxlen)
{
        static int init=0;
        static int staticndirectives=0;
        static int staticmaxlen=0;
        static const char *C_directives[]={
"define",
"undef",
"include",
"if",
"ifdef",
"ifndef",
"else",
"elif",
"endif",
"line",
"error",
"pragma",
NULL
};
        if(directives==NULL || ndirectives==NULL)
                return(-1);
        if(init==0) {
                int k,l,maxl;
                for(k=0,maxl=0;C_directives[k]!=NULL;k++) {
                        l=strlen(C_directives[k]);
                        maxl=(l>maxl)?l:maxl;
                }
                staticndirectives=k;
                staticmaxlen=maxl;
                init=1;
        }
        *directives=(char **)C_directives;
        *ndirectives=staticndirectives;
        if(maxlen!=NULL)
                *maxlen=staticmaxlen;
        return(0);
}

int
hl_searchlist(char **wordlist, int wordlistlen, char *word, int wordlen, int *res)
{
        int k;
        if(wordlist==NULL || wordlistlen<0 || word==NULL || wordlen<0)
                return(-1); /* sanity check failed */
        for(k=0;k<wordlistlen;k++) {
                if(memcmp(wordlist[k],word,wordlen)==0 && (wordlist[k])[wordlen]=='\0') {
                        if(res!=NULL)
                                *res=k;
                        return(0); /* word found */
                }
        }
        return(-1); /* word not found */
}


linecolor_t *
hl_addtolinecolor(int *opaque, highlighter_t *hl,linecolor_t *linecolor,int posoff,int color)
{
        hline_t *line;
        if(opaque==NULL || *opaque<0 || hl==NULL || (posoff!=0 && linecolor==NULL) || color<0 || posoff<0)
                return(NULL); /* sanity check failed */
        /* make sure there is a space for a linecolor_t in buf */
        if((hl->usedbuf+sizeof(linecolor_t))>=hl->sizebuf) {
                char *newbuf;
                int newsize=(hl->sizebuf+sizeof(linecolor_t)+BUFBLOCK-1)/BUFBLOCK;
                newsize*=BUFBLOCK;
                if((newbuf=realloc(hl->buf,newsize))==NULL)
                        return(NULL); /* insufficient memory */
                hl->buf=newbuf;
                memset(hl->buf+hl->sizebuf,0,(newsize-hl->sizebuf));
                hl->sizebuf=newsize;
        }
        line=hl->lines+hl->usedlines;
        /* posoff==0 means "do init" */
        if(posoff==0) {
                line->off=(hl->usedlines==0)?0:hl->lines[hl->usedlines-1].off+hl->lines[hl->usedlines-1].len;
                line->len=sizeof(linecolor_t);
                linecolor=(linecolor_t *) (hl->buf+line->off);
                linecolor->len=1;
                linecolor->color=color;
                *opaque=1;
                hl->usedbuf=line->off+line->len;
                return(linecolor);
        }
        /* if posoff was already done, truncate */
        if(posoff<*opaque) {
                int delta,maxdelta;
                int l;
                maxdelta=line->len;
                line->len=0;
                for(delta=0,l=0;delta<maxdelta;l+=linecolor->len,delta+=sizeof(linecolor_t)) {
                        linecolor=(linecolor_t *) (hl->buf+line->off+delta);
                        line->len=delta;
                        if((l+linecolor->len)>=posoff) {
                                line->len+=sizeof(linecolor_t);
                                linecolor->len=posoff-l;
                                *opaque=posoff;
                                break;
                        }
                }
                hl->usedbuf=line->off+line->len;
        }
        linecolor=(linecolor_t *) (hl->buf+line->off+line->len);
        /* if we have not changed color, expand */
        if(linecolor[-1].color==color) {
                int added=(posoff-*opaque)+1;
                linecolor[-1].len+=added;
                *opaque+=added;
                return(linecolor-1);
        }
        /* add new linecolor */
        linecolor->len=1;
        linecolor->color=color;
        *opaque+=1;
        line->len+=sizeof(linecolor_t);
        hl->usedbuf=line->off+line->len;
        return(linecolor);
}