/*
 * re_plugin_prototypes.c
 *
 * A programmers editor
 *
 * re_data plugin to support showing common prototypes hints.
 *
 * 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 "re_data.h"
#include "re_plugin_prototypes.h"
#include "prototypes_c89_posix.h"

#define PLUGINNAME "prototypes"
#define MAXINDEXDATALEN 1024


typedef struct cproto_t {
        int nentries;
        const char **index;
        const char **values;
        long lastpos;
        int lastindex;
        int sizebuffer;
        int usedbuffer;
        char buffer[MAXINDEXDATALEN];
} cproto_t;

static int redata_prototypes_add(redata_t *redata, redata_plugin_t *slot, undo_t *undo);
static int redata_prototypes_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo);
static int redata_prototypes_add_or_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo, int is_unadd);
static int redata_prototypes_postload(redata_t *redata, redata_plugin_t *slot,char *filename);
static cproto_t *cproto_getplugin(redata_t *redata);
static int cproto_buffer_inv(cproto_t *cproto);
static int cproto_buffer_getindex(cproto_t *cproto);

int
redata_prototypes_register(redata_t *redata, redata_plugin_t *slot)
{
        cproto_t *cproto;
        const char *ptr;
        int n;
        if(redata==NULL || slot==NULL)
                return(-1);
        if((cproto=malloc(sizeof(cproto_t)))==NULL)
                return(-1); /* insufficient memory */
        memset(cproto,0,sizeof(cproto_t));
        /* by default, we use C89/POSIX hints */
        cproto->nentries=SIZE_PROTOTYPES_C89_POSIX;
        if((cproto->index=malloc(sizeof(char *)*cproto->nentries))==NULL
          || (cproto->values=malloc(sizeof(char *)*cproto->nentries))==NULL) {
                if(cproto->index!=NULL)
                        free(cproto->index),cproto->index=NULL;
                return(-1); /* insufficient memory */
        }
        memset(cproto->index,0,sizeof(char *)*cproto->nentries);
        memset(cproto->values,0,sizeof(char *)*cproto->nentries);
        for(n=0,ptr=index_prototypes_c89_posix
          ;n<cproto->nentries
          ;n++,ptr+=strlen(ptr)+1) {
                cproto->index[n]=ptr;
        }
        for(n=0,ptr=values_prototypes_c89_posix
          ;n<cproto->nentries
          ;n++,ptr+=strlen(ptr)+1) {
                cproto->values[n]=ptr;
        }
        cproto->lastpos=-1;
        cproto->lastindex=-1;
        cproto->usedbuffer=0;
        cproto->sizebuffer=sizeof(cproto->buffer);
        strncpy(slot->name,PLUGINNAME,sizeof(slot->name));
        slot->name[sizeof(slot->name)-1]='\0';
        slot->unregister=redata_prototypes_unregister;
        slot->postload=redata_prototypes_postload;
        slot->add=redata_prototypes_add;
        slot->unadd=redata_prototypes_unadd;
        slot->userptr=cproto;
        return(0);
}

int
redata_prototypes_unregister(redata_t *redata, redata_plugin_t *slot,char *filename)
{
        cproto_t *cproto=(cproto_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || cproto==NULL)
                return(-1);
        if(cproto->index!=NULL)
               free(cproto->index),cproto->index=NULL;
        if(cproto->values!=NULL)
               free(cproto->values),cproto->values=NULL;
        if(slot->userptr!=NULL)
                free(slot->userptr),slot->userptr=NULL;
        return(0);

}

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

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


static int
redata_prototypes_add_or_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo, int is_unadd)
{
        /* reset the cache */
        cproto_t *cproto=(cproto_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || cproto==NULL || undo==NULL || slot->active==0)
                return(-1); /* sanity check failed */
        cproto->lastpos=-1;
        cproto->lastindex=-1;
        return(0);
}

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

static cproto_t *
cproto_getplugin(redata_t *redata)
{
        cproto_t *cproto;
        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 */
        cproto=(cproto_t *) (redata->plugins[i].userptr);
        if(cproto==NULL)
                return(NULL); /* internal error */
        return(cproto);
}


const char *
redata_prototypes_get(redata_t *redata, long pos)
{
        int numchunk,savednumchunk;
        int offset,savedoffset;
        long chunkstartpos;
        rechunk_t *chunk;
        cproto_t *cproto;
        char c;
        int ntry;
        enum {
                searching_forward,
                searching_paren,
                searching_name,
                searching_nameend,
                searching_ended,
        } status;
        if(redata==NULL || pos<0 || (cproto=cproto_getplugin(redata))==NULL)
                return(NULL); /* sanity check failed */
        if(pos==cproto->lastpos)
                return((cproto->lastindex!=-1)?(cproto->values[cproto->lastindex]):NULL);
        if(redata_getposptr(redata,pos,&savednumchunk,&savedoffset)!=0)
                return(NULL); /* couldn't get pos */
        cproto->lastpos=pos;
        cproto->lastindex=-1;
        /* FIRST  TRY: check if current word is in index */
        /* SECOND TRY: check if word before the last '( is in index */
        for(ntry=0;ntry<2;ntry++) {
                numchunk=savednumchunk;
                offset=savedoffset;
                chunkstartpos=pos-offset;
                status=(ntry==0)?searching_forward:searching_paren;
                cproto->usedbuffer=0;
                /* search forward for current word end */
                if(ntry==0) {
                        for(;status==searching_forward && numchunk<redata->sizechunks
                          ;chunkstartpos+=chunk->useddata,numchunk++,offset=0) {
                                chunk=redata->chunks[numchunk];
                                for(;offset<chunk->useddata;offset++) {
                                        c=chunk->data[offset];
                                        if(!((c>='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9') || c=='_')) {
                                                status=searching_name;
                                                break;
                                        }
                                }
                                if(status!=searching_forward)
                                        break;
                        }
                }
                if(ntry==0 && status!=searching_name)
                        continue;
                /* search backwards for prototype info */
                for(;numchunk>=0 && status!=searching_ended
                  ;numchunk--
                  ,offset=(numchunk>=0)?redata->chunks[numchunk]->useddata-1:0
                  ,chunkstartpos-=(numchunk>=0)?redata->chunks[numchunk]->useddata:0) {
                        chunk=redata->chunks[numchunk];
                        for(;offset>=0;offset--) {
                                c=chunk->data[offset];
                                if(c=='\n') {
                                        status=searching_ended;
                                        break;
                                }
                                if(status==searching_paren) {
                                        if(c=='(') {
                                                status=searching_name;
                                                continue;

                                        }
                                } else if(status==searching_name) {
                                        if(c==')') {
                                                status=searching_paren;
                                                continue;
                                        }
                                        if((c>='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9') || c=='_') {
                                                cproto->usedbuffer=0;
                                                cproto->buffer[(cproto->usedbuffer++)]=c;
                                                status=searching_nameend;
                                        }
                                } else { /* status==searching_nameend */
                                        if(c=='(') {
                                                if(cproto->usedbuffer>0) {
                                                        /* if we had a hit, return that */
                                                        cproto_buffer_inv(cproto);
                                                        /* search string */
                                                        if((cproto->lastindex=cproto_buffer_getindex(cproto))!=-1)
                                                                return(cproto->values[cproto->lastindex]);
                                                }
                                                cproto->usedbuffer=0;
                                                status=searching_nameend;
                                                continue;
                                        } else if(!((c>='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9') || c=='_')) {
                                                status=searching_ended;
                                                break;
                                        } else {
                                                if((cproto->usedbuffer+1)<cproto->sizebuffer)
                                                        cproto->buffer[(cproto->usedbuffer++)]=c;
                                        }
                                }
                        }
                }
                if((status==searching_nameend || status==searching_ended) && cproto->usedbuffer>0) {
                        cproto_buffer_inv(cproto);
                        /* search string */
                        if((cproto->lastindex=cproto_buffer_getindex(cproto))!=-1)
                                return(cproto->values[cproto->lastindex]);
                }
        }
        return(NULL);
}




static int
cproto_buffer_inv(cproto_t *cproto)
{
        int i,l,l2;
        char c;
        if(cproto==NULL)
                return(-1);
        /* terminate string */
        cproto->buffer[cproto->usedbuffer]='\0';
        /* invert string */
        l=cproto->usedbuffer;
        l2=l>>1;
        for(i=0;i<l2;i++) {
                c=cproto->buffer[i];
                cproto->buffer[i]=cproto->buffer[l-1-i];
                cproto->buffer[l-1-i]=c;
        }
        return(0);
}

static int
cproto_buffer_getindex(cproto_t *cproto)
{
#warning TODO use qsearch()
        int i;
        if(cproto==NULL)
                return(-1);
        cproto->buffer[cproto->usedbuffer]='\0';
        for(i=0;i<cproto->nentries;i++) {
                if(strcmp(cproto->index[i],cproto->buffer)==0) {
                        return(i);
                }
        }
        return(-1);
}