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

#define PLUGINNAME "prototypes"
#define MAXINDEXDATALEN 1024

typedef struct protoindex_t {
        int nentries;
        const char **index;
        const char **values;
} protoindex_t;

typedef struct cproto_t {
        int sizeprotoindexes;
        protoindex_t *protoindexes;
        long lastpos;
        int lastindex;
        protolang_t lastprotolang;
        int sizebuffer;
        int usedbuffer;
        char buffer[MAXINDEXDATALEN];
} cproto_t;

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_init(void);
static void cproto_free(cproto_t *cproto);
static cproto_t *cproto_getplugin(redata_t *redata);
static int cproto_buffer_inv(cproto_t *cproto);
const char *mystrstr(char *heystack, char *needle);
static int cproto_buffer_getindex(cproto_t *cproto);

int
redata_prototypes_register(redata_t *redata, redata_plugin_t *slot)
{
        cproto_t *cproto;
        if(redata==NULL || slot==NULL)
                return(-1);
        if((cproto=cproto_init())==NULL)
                return(-1); /* insufficient memory */
        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_or_unadd=redata_prototypes_add_or_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);
        cproto_free(cproto),cproto=NULL,slot->userptr=NULL;
        return(0);

}

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;
        cproto->lastprotolang=protolang_undefined;
        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_init(void)
{
        int i;
        cproto_t *cproto;
        protoindex_t *protoindex;
        const char *ptrindex,*ptrvalues;
        int n;
        if((cproto=malloc(sizeof(cproto_t)))==NULL)
                return(NULL); /* insuf. mem. */
        memset(cproto,0,sizeof(cproto_t));
        /* initialize protoindexes */
        if((cproto->protoindexes=malloc(sizeof(protoindex_t)*protolang_end))==NULL) {
                cproto_free(cproto),cproto=NULL;
                return(NULL); /* insuf. mem. */
        }
        memset(cproto->protoindexes,0,sizeof(protoindex_t)*protolang_end);
        cproto->sizeprotoindexes=protolang_end;
        for(i=0;i<protolang_end;i++) {
                protoindex=cproto->protoindexes+i;
                if(i==protolang_c) {
                        protoindex->nentries=SIZE_PROTOTYPES_C89_POSIX;
                        ptrindex=index_prototypes_c89_posix;
                        ptrvalues=values_prototypes_c89_posix;
                } else if(i==protolang_tcl) {
                        protoindex->nentries=SIZE_PROTOTYPES_TCL;
                        ptrindex=index_prototypes_tcl;
                        ptrvalues=values_prototypes_tcl;
                } else {
                        cproto_free(cproto),cproto=NULL;
                        return(NULL); /* internal error, unknown protolang */
                }
                if((protoindex->index=malloc(sizeof(char *)*protoindex->nentries))==NULL
                  || (protoindex->values=malloc(sizeof(char *)*protoindex->nentries))==NULL) {
                        cproto_free(cproto),cproto=NULL;
                        return(NULL); /* insufficient memory */
                }
                memset(protoindex->index,0,sizeof(char *)*protoindex->nentries);
                memset(protoindex->values,0,sizeof(char *)*protoindex->nentries);
                for(n=0;n<protoindex->nentries
                  ;n++,ptrindex+=strlen(ptrindex)+1,ptrvalues+=strlen(ptrvalues)+1) {
                        protoindex->index[n]=ptrindex;
                        protoindex->values[n]=ptrvalues;
                }
        }
        /* init caches */
        cproto->lastpos=-1;
        cproto->lastindex=-1;
        cproto->lastprotolang=protolang_undefined;
        cproto->usedbuffer=0;
        cproto->sizebuffer=sizeof(cproto->buffer);
        return(cproto);
}

static void
cproto_free(cproto_t *cproto)
{
        int i;
        if(cproto==NULL)
                return;
        if(cproto->protoindexes!=NULL) {
                protoindex_t *protoindex;
                for(i=0;i<cproto->sizeprotoindexes;i++) {
                        protoindex=cproto->protoindexes+i;
                        if(protoindex->index!=NULL)
                                free(protoindex->index),protoindex->index=NULL;
                        if(protoindex->values!=NULL)
                                free(protoindex->values),protoindex->values=NULL;
                }
                free(cproto->protoindexes),cproto->protoindexes=NULL;
        }
        free(cproto),cproto=NULL;
        return;
}

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 *
mystrstr(char *haystack, char *needle)
{
        char *ptr;
        int l;
        if(needle[0]=='\0')
                return(haystack);
        l=strlen(needle);
        for(ptr=strchr(haystack,needle[0]);ptr!=NULL;ptr=strchr(ptr+1,needle[0])) {
                if(memcmp(ptr,needle,l)==0)
                        return(ptr);
        }
        return(NULL);
}

const char *
redata_prototypes_get(redata_t *redata, long pos)
{
        int numchunk,savednumchunk;
        int offset,savedoffset;
        long chunkstartpos;
        rechunk_t *chunk;
        cproto_t *cproto;
        protoindex_t *protoindex;
        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(cproto->lastprotolang==protolang_undefined)
                redata_prototypes_detectlang(redata);
        protoindex=cproto->protoindexes+cproto->lastprotolang;
        if(pos==cproto->lastpos)
                return((cproto->lastindex!=-1)?(protoindex->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' && !(numchunk==savednumchunk && offset==savedoffset)) {
                                        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(protoindex->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=='_' || (cproto->lastprotolang==protolang_tcl && 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) {
                        /* trim spaces leading to word (for tcl) */
                        while(cproto->usedbuffer>0 && cproto->buffer[cproto->usedbuffer-1]==' ')
                                cproto->usedbuffer--;
                        /* put the string in the correct order */
                        cproto_buffer_inv(cproto);
                        /* search string */
                        if((cproto->lastindex=cproto_buffer_getindex(cproto))!=-1)
                                return(protoindex->values[cproto->lastindex]);
                }
        }
        return(NULL);
}

protolang_t
redata_prototypes_detectlang(redata_t *redata)
{
        cproto_t *cproto;
        char linebuf[1024];
        if(redata==NULL || (cproto=cproto_getplugin(redata))==NULL)
                return(protolang_undefined); /* sanity check failed */
        if(redata_line_getstartstr(redata,0,linebuf,sizeof(linebuf))!=0)
                return(protolang_undefined); /* couldn't detect file type using first line */
        if(linebuf[0]=='#' && (mystrstr(linebuf,"tclsh")!=NULL || mystrstr(linebuf,"wish")!=NULL || mystrstr(linebuf,"expect")!=NULL))
                cproto->lastprotolang=protolang_tcl;
        else
                cproto->lastprotolang=protolang_c;
        cproto->lastpos=-1;
        return(cproto->lastprotolang);
}

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;
        protoindex_t *protoindex;
        if(cproto==NULL)
                return(-1);
        cproto->buffer[cproto->usedbuffer]='\0';
        protoindex=cproto->protoindexes+cproto->lastprotolang;
        for(i=0;i<protoindex->nentries;i++) {
                if(strcmp(protoindex->index[i],cproto->buffer)==0)
                        return(i);
        }
        return(-1);
}