/*
 * re_plugin_highlighter.c
 *
 * A programmers editor
 *
 * re_data plugin to support the syntax highlighter.
 *
 * Author: Dario Rodriguez dario@softhome.net
 * 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 HIGHLIGHTERGROWSIZE (256*1024)
#define DEFAULTCOLOR "\x80\x80\x80\xff"


typedef enum colorsenum_t {
        color_normal=0,
        color_define,
        color_definestring,
        color_keyword,
        color_string,
        color_multilinecomment,
        color_linecomment,
        color_number,
        color_endenum,
} colorsenum_t;


static int redata_highlighter_add(redata_t *redata, redata_plugin_t *slot, undo_t *undo);
static int redata_highlighter_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo);
static int redata_highlighter_add_or_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo, int is_unadd);
static int redata_highlighter_commit(redata_t *redata, redata_plugin_t *slot,char *filename);
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
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);
        colors=hl_initcolors(&ncolors,
                             color_normal,"\x38\x17\x1e\xff",
                             color_define,"\x0d\x2b\x04\xff",
                             color_definestring,"\x07\x20\3b\xff",
                             color_keyword,"\x9d\x15\x00\xff",
                             color_string,"\x68\x00\x01\xff",
                             color_multilinecomment,"\x4f\x40\x57\xff",
                             color_linecomment,"\xaa\x8c\xcd\xff",
                             color_number,"\x3b\x10\x35\xff",
                             color_endenum,DEFAULTCOLOR,
                             -1);
        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=redata_highlighter_add;
        slot->unadd=redata_highlighter_unadd;
        slot->commit=redata_highlighter_commit;
        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(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(hcolor_t);
        return((linecolor_t *) (hl->buf+hl->lines[line].off));
}

static int
redata_highlighter_add(redata_t *redata, redata_plugin_t *slot, undo_t *undo)
{
        return(redata_highlighter_add_or_unadd(redata, slot, undo, 0));
}

static int
redata_highlighter_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo)
{
        return(redata_highlighter_add_or_unadd(redata, slot, undo, 1));
}


static int
redata_highlighter_add_or_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo, int is_unadd)
{
        long pos;
        int nline;
        undostack_t *stack;
        highlighter_t *hl=(highlighter_t *) ((slot!=NULL)?(slot->userptr):NULL);
        stack=redata_getstack(redata,undo);
        if(redata==NULL || slot==NULL || hl==NULL || undo==NULL || stack==NULL || slot->active==0)
                return(-1); /* sanity check failed */
        /* get the first pos of the operation */
        pos=undo->posorig;
        if(undo->type=='D')
                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;
                }
        }
        /* invalidate from this line on */
        hl_invalidatefrom(redata,hl,nline);
        return(0);
}

static int
redata_highlighter_commit(redata_t *redata, redata_plugin_t *slot,char *filename)
{
        highlighter_t *hl=(highlighter_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || hl==NULL || filename==NULL)
                return(-1); /* sanity check failed */
        if(hl->flag_doneall)
                return(0);
        /* calc highlight of one additional line */
        hl_doline(redata, hl, hl->usedlines);
        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].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)
{
        /* NOTE: here comes the highlighter */
#warning XXX TODO: implement this
        return(-1);
}