/*
 * re_data.c
 *
 * A programmers editor
 *
 * Structures to hold the current file contents.
 *
 * 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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <inttypes.h>
#include <stdarg.h>

#include "re_data.h"
#include "sha3/sha3.h"

/* DEFAULT CHUNKSIZE: 1024 */
//#define CHUNKSIZE 65536
//#define CHUNKSIZE 32768
//#define CHUNKSIZE 4096
//#define CHUNKSIZE 1024
#define CHUNKSIZE 160
//#define CHUNKSIZE 16
//#define CHUNKSIZE 3
#warning XXX: TODO: CHANGE CHUNKSIZE TO A SANE VALUE (not 1!), THIS IS ONLY FOR TESTING UTF-8 multi-byte CHARS

#define UNDOBLOCK 1024
#define ADDNBLOCK 1024
#define UNDOGROWSIZE (256*1024)
#define SECURESAVEPREFIX "."
#define SECURESAVEPOSTFIX ".saving"

#define UTF8_IS_ASCII_OR_START(c) (((c)&0xc0)!=0x80)
#define UTF8_IS_ASCII(c) (((c)&0x80)==0)
#define UTF8_IS_MULTIBYTESTART(c) ((((c)&0x80)==0x80)&&(((c)&(0x80|0x40))!=0x80))
#define UTF8_IS_MULTIBYTECONT(c) (((c)&(0x80|0x40))==0x80)

#define UTF8_MULTIBYTESTART2LEN(c) ((((c)&0xe0)==0xc0)?2: \
                                    (((c)&0xf0)==0xe0)?3: \
                                    (((c)&0xf8)==0xf0)?4: \
                                    (((c)&0xfc)==0xf8)?5: \
                                    (((c)&0xfe)==0xfc)?6: \
                                    1)

static int redata_hash_gen(redata_t *redata, char *filename, char *buf, long buflen, char *resbuf129bytes);
static char *securesave_genname(char *filename, char *buf, int bufsize);
static void *mymemrchr(const void *s, int c, size_t n);
static void meminvert(void *start, void *end);
static size_t memrchroffset(char *ptr, int c, size_t n);

#if 0
static void
redata_debug_chunkdump(redata_t *redata, char *title)
{
        int m,k;
        char c;
        title=(title==NULL)?"":title;
        fprintf(stderr,"%s:CHUNKDUMP (sizechunks:%i)\n",title,redata->sizechunks);
        for(m=0;m<redata->sizechunks;m++) {
                fprintf(stderr,"%s:chunk[%i] len:%-5i data:\"",title,m,redata->chunks[m]->useddata);
                for(k=0;k<redata->chunks[m]->useddata;k++) {
                        c=redata->chunks[m]->data[k];
                        if(c=='\n' || c=='\0')
                                fprintf(stderr,"\\%c",(c=='\n')?'n':'0');
                        else if(c<' ' || c>'~' || c=='\\')
                                fprintf(stderr,"\\x%02X",((unsigned char *)redata->chunks[m]->data)[k]);
                        else
                                fprintf(stderr,"%c",c);
                }
                fprintf(stderr,"\"\n");
        }
}
#define CHUNKDEBUG(a) redata_debug_chunkdump a
#else
#define CHUNKDEBUG(a)
#endif

redata_t *
redata_init(int (*pluginregisterfn)(redata_t *redata, redata_plugin_t *slot), ...)
{
        redata_t *redata;
        va_list args;
        int nargs;
        int res;
        int (*fn)(redata_t *redata, redata_plugin_t *slot);
        /* count number of plugins */
        va_start(args,pluginregisterfn);
        for(nargs=0,fn=pluginregisterfn;fn!=NULL;fn=va_arg(args,int (*)(redata_t *redata, redata_plugin_t *slot)))
                nargs++;
        va_end(args);
        /* get memory */
        if((redata=malloc(sizeof(redata_t)+sizeof(redata_plugin_t)*(nargs-1)))==NULL)
                return(NULL); /* sanity check failed */
        memset(redata,0,sizeof(redata_t)+sizeof(redata_plugin_t)*(nargs-1));
        redata->sizeplugins=nargs;
        /* data */
        redata->sizechunks=0;
        redata->chunks=NULL;
        redata->chunkdatasize=CHUNKSIZE;
        redata->available=0;
        /* undo */
        redata->undostack.sizeundo=0;
        redata->undostack.usedundo=0;
        redata->undostack.undo=NULL;
        redata->undostack.buf=NULL;
        /* plugins */
        va_start(args,pluginregisterfn);
        for(nargs=0,fn=pluginregisterfn;fn!=NULL;fn=va_arg(args,int (*)(redata_t *redata,redata_plugin_t *slot))) {
                res=fn(redata,redata->plugins+nargs);
                redata->plugins[nargs].active=(res==0)?1:0;
                nargs++;
        }
        va_end(args);
        /* filename */
        redata->filename[0]='\0';
        /* all done */
        return(redata);
}

void
redata_free(redata_t *redata)
{
        int i;
        if(redata==NULL)
                return; /* nothing to do */
        /* plugins */
        for(i=0;i<redata->sizeplugins;i++) {
                if(redata->plugins[i].unregister!=NULL)
                        redata->plugins[i].unregister(redata,redata->plugins+i,redata->filename);
                memset(redata->plugins+i,0,sizeof(redata_plugin_t));
        }
        /* data */
        for(i=0;i<redata->sizechunks;i++) {
                if(redata->chunks[i]!=NULL)
                        free(redata->chunks[i]),redata->chunks[i]=NULL;
        }
        redata->sizechunks=0;
        if(redata->chunks!=NULL)
                free(redata->chunks),redata->chunks=NULL;
        if(redata->tmpchunk!=NULL)
                free(redata->tmpchunk),redata->tmpchunk=NULL;
        /* undo */
        if(redata->undostack.undo!=NULL)
                free(redata->undostack.undo),redata->undostack.undo=NULL;
        if(redata->undostack.buf!=NULL)
                free(redata->undostack.buf),redata->undostack.buf=NULL;
        /* redo */
        if(redata->redostack.undo!=NULL)
                free(redata->redostack.undo),redata->redostack.undo=NULL;
        if(redata->redostack.buf!=NULL)
                free(redata->redostack.buf),redata->redostack.buf=NULL;
        /* addnbuf */
        if(redata->addnbuf!=NULL)
                free(redata->addnbuf),redata->addnbuf=NULL,redata->sizeaddnbuf=0;
        /* free main struct */
        free(redata),redata=NULL;
        return;
}

void
redata_idleproc(redata_t *redata, char *filename)
{
        int i;
        if(redata==NULL)
                return;
        for(i=0;i<redata->sizeplugins;i++) {
                if(redata->plugins[i].commit!=NULL)
                        redata->plugins[i].commit(redata,redata->plugins+i,filename);
        }
        return;
}


int
redata_config_chunkdatasize(redata_t *redata, int chunkdatasize)
{
        /* NOTE: this can only be configured immediately after doing redata_init() */
        if(redata==NULL || chunkdatasize<=0 || redata->sizechunks!=0)
                return(-1); /* sanity check failed */
        redata->chunkdatasize=chunkdatasize;
        return(0);
}

long
redata_getsize(redata_t *redata)
{
        if(redata==NULL)
                return(0); /* sanity check failed */
        return(redata->chunkdatasize*redata->sizechunks);
}

long
redata_getavailable(redata_t *redata)
{
        if(redata==NULL)
                return(0); /* sanity check failed */
        return(redata->available);
}

long
redata_getused(redata_t *redata)
{
        long used;
        if(redata==NULL)
                return(0); /* sanity check failed */
        used=redata->chunkdatasize*redata->sizechunks-redata->available;
        return(used);
}

int
redata_getposptr(redata_t *redata, long pos, int *numchunk, int *offset)
{
        long used;
        int i;
        long ipos;
        used=redata_getused(redata);
        if(redata==NULL || pos<0 || pos>used || numchunk==NULL || offset==NULL)
                return(-1); /* sanity check failed */
        for(ipos=0,i=0;i<redata->sizechunks;ipos+=redata->chunks[i]->useddata,i++) {
                if(pos>(ipos+redata->chunks[i]->useddata))
                        continue;
                /* found */
                *numchunk=i;
                *offset=pos-ipos;
                return(0);
        }
        return(-1);
}

int
redata_getchar(redata_t *redata, long pos)
{
        int numchunk;
        int offset;
        int avail;
        rechunk_t *chunk;
        if(redata==NULL || pos<0 || pos>=redata_getsize(redata))
                return(-1); /* sanity check failed */
        if(redata_getposptr(redata,pos,&numchunk,&offset)!=0)
                return(-1); /* couldn't get pos */
        /* search for data starting at that pos */
        for(;numchunk<redata->sizechunks;numchunk++,offset=0) {
                chunk=redata->chunks[numchunk];
                avail=chunk->useddata-offset;
                if(avail>0) {
                        return((int) (((unsigned char *)chunk->data)[offset]));
                }
        }
        return(-1); /* not found */
}

int
redata_getdata(redata_t *redata, long frompos, long size, char *dest)
{
        int nchunk,off,avail;
        long n;
        if(redata==NULL || frompos<0 || size<0 || dest==NULL || (frompos+size)>redata_getused(redata) || redata_getposptr(redata,frompos,&nchunk,&off)!=0)
                return(-1); /* sanity check failed */
        for(n=0;n<size && nchunk<redata->sizechunks;nchunk++,off=0) {
                avail=redata->chunks[nchunk]->useddata-off;
                if(avail<=0)
                        continue;
                if(avail>(size-n))
                        avail=size-n;
                memcpy(dest+n,redata->chunks[nchunk]->data+off,avail);
                n+=avail;
        }
        return(0);
}


int
redata_wipe(redata_t *redata)
{
        int i;
        if(redata==NULL)
                return(-1); /* sanity check failed */
        /* data */
        for(i=0;i<redata->sizechunks;i++) {
                redata->chunks[i]->useddata=0;
                memset(&(redata->chunks[i]->whatin),0,sizeof(whatin_t));
        }
        redata->available=redata_getsize(redata);
        /* plugins */
        for(i=0;i<redata->sizeplugins;i++) {
                if(redata->plugins[i].wipe!=NULL)
                        redata->plugins[i].wipe(redata,redata->plugins+i,redata->filename);
        }
        redata->filename[0]='\0';
        /* all done */
        return(0);
}

int
redata_chunk_insertnew(redata_t *redata, int afterthischunk)
{
        rechunk_t *newchunk;
        int i;
        if(redata==NULL || afterthischunk<(-1) || afterthischunk>=redata->sizechunks)
                return(-1);
        if(redata->chunks[redata->sizechunks-1]->useddata!=0) {
                if(redata_preallocate(redata,redata_getsize(redata)+redata->chunkdatasize)==-1
                  || redata->chunks[redata->sizechunks-1]->useddata!=0)
                        return(-1);
        }
        newchunk=redata->chunks[redata->sizechunks-1];
        /* move the emtpy chunk to after chunkno */
        for(i=redata->sizechunks-1;i>(afterthischunk+1);i--)
                redata->chunks[i]=redata->chunks[i-1];
        redata->chunks[afterthischunk+1]=newchunk;
        return(0);
}

int
redata_chunk_deletechunk(redata_t *redata, int chunkno)
{
        rechunk_t *chunk;
        int i;
        if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks)
                return(-1);
        chunk=redata->chunks[chunkno];
        if(chunk->useddata>0) {
                redata->available+=chunk->useddata;
                chunk->useddata=0;
                chunk->whatin_fresh=0;
                redata_chunk_unfreshnext(redata, chunkno);
        }
        for(i=chunkno;(i+1)<redata->sizechunks;i++)
                redata->chunks[i]=redata->chunks[i+1];
        redata->chunks[redata->sizechunks-1]=chunk;
        return(0);
}


int
redata_preallocate(redata_t *redata, long newsize)
{
        long cursize;
        int nchunks;
        int i;
        long rechunksize;
        rechunk_t **newchunks,*chunk;
        int oldsizechunks;
        if(redata==NULL || redata->chunkdatasize==0 || newsize<0)
                return(-1); /* sanity check failed */
        rechunksize=sizeof(rechunk_t)-1+redata->chunkdatasize;
        /* before doing any chunk alloc, we setup tmpchunk (used for moves) */
        if(redata->tmpchunk==NULL) {
                if((redata->tmpchunk=malloc(rechunksize))==NULL)
                        return(-1);  /* insuf. mem. */
                memset(redata->tmpchunk,0,rechunksize);
        }
        /* new chunk processing */
        if((cursize=redata_getsize(redata))>=newsize)
                return(0); /* all done */
        nchunks=(newsize-cursize+redata->chunkdatasize-1)/redata->chunkdatasize;
        if((newchunks=realloc(redata->chunks,sizeof(rechunk_t *)*(redata->sizechunks+nchunks)))==NULL)
                return(-1); /* insuf. mem. */
        redata->chunks=newchunks;
        memset(redata->chunks+redata->sizechunks,0,sizeof(rechunk_t *)*nchunks);
        oldsizechunks=redata->sizechunks;
        for(i=0;i<nchunks;i++) {
                if((chunk=malloc(rechunksize))==NULL)
                        return(-1);  /* insuf. mem. */
                memset(chunk,0,rechunksize);
                chunk->useddata=0;
                redata->chunks[oldsizechunks+i]=chunk;
                redata->sizechunks++;
                redata->available+=redata->chunkdatasize;
        }
        return(0);
}

int
redata_chunk_movedata(redata_t *redata, int chunkfrom, long posfrom, int chunkto, long posto, long size)
{
        if(redata==NULL || size<0
          || chunkfrom<0 || chunkfrom>=redata->sizechunks
          || posfrom<0 || (posfrom+size)>redata->chunks[chunkfrom]->useddata
          || chunkto<0 || chunkto>=redata->sizechunks
          || posto<0 || (posto)>redata->chunks[chunkto]->useddata
          || (chunkfrom!=chunkto && (redata->chunkdatasize-redata->chunks[chunkto]->useddata)<size)
          || (chunkfrom==chunkto && posfrom<posto && posfrom+size>posto)
          )
                return(-1); /* sanity check failed */
        if(size==0
          || (chunkfrom==chunkto && posfrom==posto)
          || (chunkfrom==chunkto && (posfrom+size)==posto)
          )
                return(0); /* all done */
        if(chunkfrom!=chunkto) {
                rechunk_t *from,*to;
                from=redata->chunks[chunkfrom];
                to=redata->chunks[chunkto];
                memmove(to->data+posto+size,to->data+posto,to->useddata-posto);
                memcpy(to->data+posto,from->data+posfrom,size);
                memmove(from->data+posfrom,from->data+posfrom+size,from->useddata-posfrom-size);
                from->useddata-=size;
                to->useddata+=size;
                redata_chunk_unfreshrange(redata,chunkfrom,chunkto);
                redata_chunk_unfreshnext(redata,chunkfrom);
                redata_chunk_unfreshnext(redata,chunkto);
        } else {
                /* from==to */
                rechunk_t *chunk=redata->chunks[chunkfrom];
                memcpy(redata->tmpchunk->data,chunk->data+posfrom,size);
                if(posfrom>posto) {
                        memmove(chunk->data+posto+size,chunk->data+posto,posfrom-posto);
                        memcpy(chunk->data+posto,redata->tmpchunk->data,size);
                } else {
                        memmove(chunk->data+posfrom,chunk->data+posfrom+size,posto-posfrom-size);
                        memcpy(chunk->data+posto-size,redata->tmpchunk->data,size);
                }
                chunk->whatin_fresh=0;
                redata_chunk_unfreshnext(redata,chunkfrom);
        }
        return(0);
}

int
redata_chunk_insertdata(redata_t *redata, int chunkto, long posto, char *buf, long buflen)
{
        rechunk_t *chunk;
        if(redata==NULL || buflen<0 || buf==NULL
          || chunkto<0 || chunkto>=redata->sizechunks
          || posto<0 || posto>redata->chunks[chunkto]->useddata
          || (redata->chunkdatasize-redata->chunks[chunkto]->useddata)<buflen)
                return(-1); /* sanity check failed */
        chunk=redata->chunks[chunkto];
        memmove(chunk->data+posto+buflen,chunk->data+posto,chunk->useddata-posto);
        memcpy(chunk->data+posto,buf,buflen);
        chunk->useddata+=buflen;
        redata->available-=buflen;
        chunk->whatin_fresh=0;
        redata_chunk_unfreshnext(redata,chunkto);
        return(0);
}

int
redata_chunk_deletedata(redata_t *redata, int chunkno, long pos, long n)
{
        rechunk_t *chunk;
        if(redata==NULL || n<0
          || chunkno<0 || chunkno>=redata->sizechunks
          || pos<0 || pos>redata->chunks[chunkno]->useddata
          || (redata->chunks[chunkno]->useddata-pos)<n)
                return(-1); /* sanity check failed */
        if(n==0)
                return(0); /* all done */
        chunk=redata->chunks[chunkno];
        memmove(chunk->data+pos,chunk->data+pos+n,chunk->useddata-pos-n);
        chunk->useddata-=n;
        redata->available+=n;
        chunk->whatin_fresh=0;
        redata_chunk_unfreshnext(redata,chunkno);
        return(0);
}

int
redata_chunk_splithere(redata_t *redata, int chunkno, int pos)
{
        rechunk_t *chunk;
        int size;
        if(redata==NULL
          || chunkno<0 || chunkno>=redata->sizechunks
          || pos<0 || pos>redata->chunks[chunkno]->useddata)
                return(-1); /* sanity check failed */
        chunk=redata->chunks[chunkno];
        if(chunk->useddata==pos)
                return(0); /* all done: already splitted */
        size=redata->chunks[chunkno]->useddata-pos;
        if((chunkno+1)<redata->sizechunks
          && (redata->chunkdatasize-redata->chunks[chunkno+1]->useddata)>=size) {
                redata_chunk_movedata(redata,chunkno,pos,chunkno+1,0,size);
                return(0); /* all done: moved data cleanly to next chunk */
        }
        if(redata_chunk_insertnew(redata,chunkno)!=0)
                return(-1); /* insuf. mem. */
        redata_chunk_movedata(redata,chunkno,pos,chunkno+1,0,size);
        return(0);
}

int
redata_chunk_fillfromnext(redata_t *redata, int chunkno, int n)
{
        if(redata==NULL || chunkno<0 || (chunkno+1)>=redata->sizechunks
          || (redata->chunkdatasize-redata->chunks[chunkno]->useddata)<n
          || (redata->chunks[chunkno+1]->useddata)<n)
                return(-1); /* sanity check failed */
        return(redata_chunk_movedata(redata,chunkno+1,0,chunkno,redata->chunks[chunkno]->useddata,n));
}

int
redata_chunk_unfreshnext(redata_t *redata, int chunkno)
{
        int next;
        if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks)
                return(-1); /* sanity check failed */
        /* invalidate whatin for next chunks until one not empty */
        for(next=chunkno+1;next<redata->sizechunks;next++) {
                redata->chunks[next]->whatin_fresh=0;
                if(redata->chunks[next]->useddata!=0)
                        break;
        }
        return(0);
}

int
redata_chunk_unfreshrange(redata_t *redata, int fromchunkno, int tochunkno)
{
        int chunkno;
        if(redata==NULL || fromchunkno<0 || fromchunkno>=redata->sizechunks || tochunkno<0 || tochunkno>=redata->sizechunks)
                return(-1); /* sanity check failed */
        if(fromchunkno>tochunkno) {
                chunkno=fromchunkno;
                fromchunkno=tochunkno;
                tochunkno=chunkno;
        }
        /* invalidate whatin in range */
        for(chunkno=fromchunkno;chunkno<=tochunkno;chunkno++)
                redata->chunks[chunkno]->whatin_fresh=0;
        return(0);
}


int
redata_whatin_refresh(redata_t *redata, int chunkno)
{
        rechunk_t *chunk;
        int nlcount;
        int i,lim;
        if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks || redata->chunks[chunkno]==NULL)
                return(-1); /* sanity check failed */
        chunk=redata->chunks[chunkno];
        if(chunk->whatin_fresh)
                return(0);
        memset(&(chunk->whatin),0,sizeof(whatin_t));
        nlcount=0;
        for(i=0,lim=chunk->useddata;i<lim;i++)
                nlcount+=(chunk->data[i]=='\n')?1:0;
        chunk->whatin.nlcount=nlcount;
        if(chunkno>0) {
                int prev;
                for(prev=chunkno-1;prev>0 && redata->chunks[prev]->useddata==0;prev--)
                        ;
                if(redata->chunks[prev]->useddata>0 && redata->chunks[prev]->data[redata->chunks[prev]->useddata-1]!='\n')
                        chunk->whatin.iscontinuation=1;
        }
        chunk->whatin_fresh=1;
        return(0);
}


int
redata_fix_nl(redata_t *redata, int chunkno)
{
        rechunk_t *chunk,*nextchunk;
        int linesize, nextlinesize, avail, nextavail;
        unsigned char *ptr,*nextptr;
        /* make sure a line of len<chunksize is entirely inside one chunk (to simplify the syntax highlighting code) */
        if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks || redata->chunks[chunkno]==NULL)
                return(-1); /* sanity check failed */
        if(chunkno==(redata->sizechunks-1) || redata->chunks[chunkno]->useddata==0 || redata->chunks[chunkno]->data[redata->chunks[chunkno]->useddata-1]=='\n')
                return(0); /* Nothing to do (last chunk, chunk empty or already fixed) */
        chunk=redata->chunks[chunkno];
        nextchunk=redata->chunks[chunkno+1];
        ptr=mymemrchr(chunk->data,'\n',chunk->useddata);
        nextptr=memchr(nextchunk->data,'\n',nextchunk->useddata);
        linesize=(ptr==NULL)?chunk->useddata:(chunk->useddata-(ptr-chunk->data)-1);
        nextlinesize=(nextptr==NULL)?nextchunk->useddata:((nextptr-nextchunk->data)+1);
        avail=redata->chunkdatasize-chunk->useddata;
        nextavail=redata->chunkdatasize-nextchunk->useddata;
        if(avail>=nextlinesize) {
                /* move remaining bytes of line to current chunk */
                redata_chunk_movedata(redata, chunkno+1, 0, chunkno, chunk->useddata, nextlinesize);
        } else if(nextavail>=linesize) {
                /* move incomplete line to next chunk */
                redata_chunk_movedata(redata, chunkno, chunk->useddata-linesize, chunkno+1, 0, linesize);
        } else if(ptr!=NULL) {
                /* only if we have more data in chunkno before this long line */
                int newavail;
                /* create a new empty chunk in-between and put the line there */
                if(redata_chunk_insertnew(redata,chunkno)==-1)
                        return(-1);
                /* move the data; if it doesn't fit, move from the beginning until it is full */
                redata_chunk_movedata(redata, chunkno, chunk->useddata-linesize, chunkno+1, 0, linesize);
                newavail=redata->sizechunks-linesize;
                redata_chunk_movedata(redata, chunkno+2, 0, chunkno+1, linesize, (newavail<nextlinesize)?newavail:nextlinesize);
        }
        return(0);
}

int
redata_loadquestions_setup(redata_t *redata, char *filename)
{
        int i;
        if(redata==NULL || filename==NULL)
                return(-1); /* sanity check failed */
        redata_loadquestions_wipe(redata);
        /* unsaved and other plugins: loadquestion (pre-to-load) */
        for(i=0;i<redata->sizeplugins;i++) {
                if(!redata->plugins[i].active)
                        continue;
                if(redata->plugins[i].loadquestion!=NULL)
                        redata->plugins[i].loadquestion(redata,redata->plugins+i,filename);
        }
        return(0);

}

question_t *
redata_loadquestions_getnext(redata_t *redata)
{
        int i;
        if(redata==NULL)
                return(NULL); /* sanity check failed */
        for(i=0;i<redata->sizeplugins;i++) {
                if(!redata->plugins[i].active || redata->plugins[i].question.active==0)
                        continue;
                if(redata->plugins[i].question.selectedoption==-1)
                        return(&(redata->plugins[i].question));
        }
        return(NULL); /* no more questions */
}

int
redata_loadquestions_reply(redata_t *redata, question_t *question, int selectedoption)
{
        if(redata==NULL || question==NULL || selectedoption<-1 || selectedoption>=question->nopts)
                return(-1);
        question->selectedoption=(selectedoption==-1)?question->defaultoption:selectedoption;
        return(0);
}

int
redata_loadquestions_wipe(redata_t *redata)
{
        int i;
        if(redata==NULL)
                return(-1);
        for(i=0;i<redata->sizeplugins;i++) {
                if(!redata->plugins[i].active)
                        continue;
                memset(redata->plugins[i].questionfilename,0,sizeof(redata->plugins[i].questionfilename));
                memset(&(redata->plugins[i].question),0,sizeof(redata->plugins[i].question));
                redata->plugins[i].question.active=0;
                redata->plugins[i].question.selectedoption=-1;
        }
        return(0);
}

int
redata_load(redata_t *redata, char *filename, char **errordesc)
{
#warning TODO: what to do when there plugins with questions AFTER loading instead of before loading...
        int fd,nread,totalread;
        int chunkno, avail;
        struct stat statbuf;
        rechunk_t *chunk;
        int i;
        if(redata==NULL || filename==NULL) {
                if(errordesc!=NULL)
                        *errordesc="Internal error";
                return(-1); /* sanity check failed */
        }
        redata_wipe(redata);
        strncpy(redata->filename,filename,sizeof(redata->filename));
        redata->filename[sizeof(redata->filename)-1]='\0';
        if((fd=open(filename,O_RDONLY))==-1 || fstat(fd,&statbuf)!=0 || !S_ISREG(statbuf.st_mode)) {
                if(fd!=-1)
                        close(fd),fd=-1;
                if(errordesc!=NULL)
                        *errordesc="File not found, couldn't query size or not a regular file";
                return(-1); /* file not found, couldn't query size or not a regular file */
        }
        /* preallocate 10% more than needed */
        if(redata_preallocate(redata,statbuf.st_size+(statbuf.st_size/10)+1 )) {
                close(fd),fd=-1;
                if(errordesc!=NULL)
                        *errordesc="Insufficient memory";
                return(-1); /* insuf. mem. */
        }
        for(totalread=0,chunkno=0,nread=0;totalread<statbuf.st_size;totalread+=nread,nread=0) {
                if(chunkno>=redata->sizechunks || redata->chunks[chunkno]==NULL) {
                        /* alloc another chunk */
                        if(redata_preallocate(redata,redata_getsize(redata)+redata->chunkdatasize)==-1 ||
                                chunkno>=redata->sizechunks || redata->chunks[chunkno]==NULL) {
                                close(fd),fd=-1;
                                fprintf(stderr,"redata_load: INTERNAL ERROR\n");
                                return(-2); /* internal error */
                        }
                }
                chunk=redata->chunks[chunkno];
                /* leave 10% free on each chunk */
                avail=(redata->chunkdatasize-redata->chunkdatasize/10)-chunk->useddata;
                avail=(avail<0)?0:avail;
                avail=((totalread+avail)>statbuf.st_size)?statbuf.st_size-totalread:avail;
                if(avail==0) {
                        chunkno++; /* full, try next */
                        continue;
                }
                if((nread=read(fd,chunk->data+chunk->useddata,avail))<=0) {
                        close(fd),fd=-1;
                        if(errordesc!=NULL)
                                *errordesc="Short read loading file";
                        return(-1); /* short read */
                }
                chunk->useddata+=nread;
                redata->available-=nread;
        }
        close(fd),fd=-1;
        for(chunkno=0;chunkno<redata->sizechunks;chunkno++)
                redata_fix_nl(redata,chunkno);
        for(chunkno=0;chunkno<redata->sizechunks;chunkno++)
                redata_whatin_refresh(redata,chunkno);
        /* unsaved and other plugins: postload */
        for(i=0;i<redata->sizeplugins;i++) {
                if(!redata->plugins[i].active)
                        continue;
                if(redata->plugins[i].postload!=NULL)
                        redata->plugins[i].postload(redata,redata->plugins+i,filename);
        }
        redata_loadquestions_wipe(redata);
#warning TODO: IMPLEMENT POSTLOADQUESTIONS (call now loadquestion_postload).
        /* mark as not modified */
        redata->needs_saving=0;
        /* all done */
        return(0);
}

int
redata_save(redata_t *redata, char *paramfilename, char **errordesc)
{
        int fd;
        int i,n;
        char tmplinkfilename[PATH_MAX+1];
        char tmpfile[PATH_MAX+1];
        struct stat statbuf;
        char *filename;
        if(redata==NULL || paramfilename==NULL) {
                if(errordesc!=NULL)
                        *errordesc="Internal error";
                return(-1); /* sanity check failed */
        }
        filename=paramfilename;
        memset(tmpfile,0,sizeof(tmpfile));
        if(stat(paramfilename,&statbuf)==0
          && (statbuf.st_mode & S_IFLNK)!=0
          && readlink(paramfilename,tmpfile,sizeof(tmpfile))>0) {
                tmpfile[sizeof(tmpfile)-1]='\0';
                if(tmpfile[0]=='/') {
                        /* absolute symlink, just copy it */
                        strncpy(tmplinkfilename,tmpfile,sizeof(tmplinkfilename));
                        tmplinkfilename[sizeof(tmplinkfilename)-1]='\0';
                } else {
                        char *aux;
                        int off;
                        /* relative symlink, put it after the path of paramfilename */
                        off=0;
                        if((aux=mymemrchr(paramfilename,'/',strlen(paramfilename)))!=NULL)
                                off=(aux-paramfilename+1);
                        memcpy(tmplinkfilename,paramfilename,off);
                        strncpy(tmplinkfilename+off,tmpfile,sizeof(tmplinkfilename)-off);
                        tmplinkfilename[sizeof(tmplinkfilename)-1]='\0';
                }
                filename=tmplinkfilename;
        }
        if((securesave_genname(filename,tmpfile,sizeof(tmpfile)))==NULL) {
                if(errordesc!=NULL)
                        *errordesc="Malformed filename";
                return(-1); /* malformed filename */
        }
        memset(&statbuf,0,sizeof(statbuf));
        if(stat(filename,&statbuf)!=0)
                statbuf.st_mode=0644;
        if((fd=open(tmpfile,O_WRONLY|O_TRUNC|O_CREAT,statbuf.st_mode))==-1) {
                if(errordesc!=NULL)
                        *errordesc="Couldn't open file for writing";
                return(-1); /* couldn't open file for writing */
        }
        for(i=0;i<redata->sizechunks;i++) {
                if(redata->chunks[i]->useddata==0)
                        continue;
                if((n=write(fd,redata->chunks[i]->data,redata->chunks[i]->useddata))==-1 || n!=redata->chunks[i]->useddata) {
                        close(fd),fd=-1;
                        unlink(tmpfile);
                        if(errordesc!=NULL)
                                *errordesc="Short write saving file";
                        return(-1); /* short write */
                }
        }
        close(fd),fd=-1;
        if(rename(tmpfile,filename)!=0) {
                unlink(tmpfile);
                if(errordesc!=NULL)
                        *errordesc="Couldn't overwrite old file";
                return(-1); /* couldn't overwrite old file */
        }
        for(i=0;i<redata->sizeplugins;i++) {
                if(redata->plugins[i].postsave!=NULL)
                        redata->plugins[i].postsave(redata,redata->plugins+i,redata->filename,paramfilename);
        }
        strncpy(redata->filename,paramfilename,sizeof(redata->filename));
        redata->filename[sizeof(redata->filename)-1]='\0';
        /* mark as not modified */
        redata->needs_saving=0;
        return(0);
}

int
redata_needssaving(redata_t *redata)
{
        if(redata==NULL)
                return(0);
        return( (redata->needs_saving)?1:0 );
}

int
redata_needssaving_reset(redata_t *redata)
{
        if(redata==NULL)
                return(-1);
        redata->needs_saving=0;
        return(0);
}

int
redata_undobuf_reserve(redata_t *redata, undostack_t *stack, int minavail)
{
        int unused;
        int newsize;
        char *newptr;
        if(redata==NULL || minavail<0
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack)))
                return(-1); /* sanity check failed */
        unused=stack->sizebuf-stack->usedbuf;
        if(unused<minavail) {
                newsize=(stack->sizebuf+minavail+UNDOGROWSIZE-1)/UNDOGROWSIZE;
                newsize*=UNDOGROWSIZE;
                if((newptr=realloc(stack->buf,newsize))==NULL)
                        return(-1); /* insuf. mem. */
                stack->buf=newptr;
                stack->sizebuf=newsize;
        }
        return(0);
}

undo_t *
redata_undo_new(redata_t *redata, undostack_t *stack, char type)
{
        int newsize;
        undo_t *newptr,*undo;
        if(redata==NULL
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack)))
                return(NULL); /* sanity check failed */
        if(stack->sizeundo==stack->usedundo) {
                newsize=(stack->sizeundo+1+UNDOBLOCK-1)/UNDOBLOCK;
                newsize*=UNDOBLOCK;
                if((newptr=realloc(stack->undo,sizeof(undo_t)*newsize))==NULL)
                        return(NULL);
                stack->undo=newptr;
                memset(stack->undo+stack->sizeundo,0,sizeof(undo_t)*(newsize-stack->sizeundo));
                stack->sizeundo=newsize;
        }
        undo=stack->undo+stack->usedundo;
        stack->usedundo++;
        undo->type=type;
        undo->off=stack->usedbuf;
        undo->len=0;
        return(undo);
}

undo_t *
redata_undo_newfromchunks(redata_t *redata, undostack_t *stack, char type, int pos1, int len)
{
        int startchunkno,endchunkno;
        int startoff,endoff;
        undo_t *undo;
        int k;
        long used;
        long copyfrom,copysize;
        if(redata==NULL || len<=0
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack)))
                return(NULL); /* sanity check failed */
        if(redata_getposptr(redata,pos1,&startchunkno,&startoff)==-1 ||
          redata_getposptr(redata,pos1+len,&endchunkno,&endoff)==-1) {
                return(NULL); /* chunk data out of bounds */
        }
        if(redata_undobuf_reserve(redata,stack,len)!=0)
                return(NULL); /* insuf. mem. */
        if((undo=redata_undo_new(redata,stack,type))==NULL)
                return(NULL); /* insuf. mem. */
        undo->off=stack->usedbuf;
        /* copy contents */
        for(k=startchunkno,used=0;k<=endchunkno;k++) {
                if(k==startchunkno && k==endchunkno) {
                        copyfrom=startoff;
                        copysize=endoff-startoff;
                } else if(k==startchunkno) {
                        copyfrom=startoff;
                        copysize=redata->chunks[k]->useddata-startoff;
                } else if(k==endchunkno) {
                        copyfrom=0;
                        copysize=endoff;
                } else {
                        copyfrom=0;
                        copysize=redata->chunks[k]->useddata;
                }
                memcpy(stack->buf+stack->usedbuf,redata->chunks[k]->data+copyfrom,copysize);
                stack->usedbuf+=copysize;
                used+=copysize;
        }
        undo->len=used;
        return(undo);
}

undo_t *
redata_undo_newfrombuf(redata_t *redata, undostack_t *stack, char type, char *buf, int buflen)
{
        undo_t *undo;
        if(redata==NULL || buflen<=0
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack)))
                return(NULL); /* sanity check failed */
        if(redata_undobuf_reserve(redata,stack, buflen)!=0)
                return(NULL); /* insuf. mem. */
        if((undo=redata_undo_new(redata,stack,  type))==NULL)
                return(NULL); /* insuf. mem. */
        undo->off=stack->usedbuf;
        memcpy(stack->buf+stack->usedbuf,buf,buflen);
        stack->usedbuf+=buflen;
        undo->len=buflen;
        return(undo);
}

int
redata_undo_movelast(redata_t *redata, undostack_t *from, undostack_t *to)
{
        undo_t *undofrom;
        undo_t *undoto;
        if(redata==NULL || from==to
          || (from!=&(redata->undostack) && from!=&(redata->redostack))
          || (to!=&(redata->undostack) && to!=&(redata->redostack))
          || from->usedundo<1)
                return(-1); /* sanity check failed */
        undofrom=from->undo+from->usedundo-1;
        if((undoto=redata_undo_newfrombuf(redata,to,undofrom->type,from->buf+undofrom->off,undofrom->len))==NULL)
                return(-1); /* insuf. mem. */
        undoto->posorig=undofrom->posorig;
        undoto->posdest=undofrom->posdest;
#ifdef REDATA_HASHUNDO
        memcpy(undoto->prehash,undofrom->prehash,sizeof(undoto->prehash));
        memcpy(undoto->posthash,undofrom->posthash,sizeof(undoto->posthash));
#endif
        redata_undo_removelast(redata, from);
        return(0);
}

int
redata_undo_removelast(redata_t *redata, undostack_t *stack)
{
        undo_t *undo;
        if(redata==NULL
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack))
          || stack->usedundo<1)
                return(-1); /* sanity check failed */
        undo=stack->undo+stack->usedundo-1;
        stack->usedbuf-=undo->len;
        memset(undo,0,sizeof(undo_t));
        stack->usedundo--;
        return(0);
}

int
redata_undo_wipe(redata_t *redata, undostack_t *stack)
{
        if(redata==NULL
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack)))
                return(-1); /* sanity check failed */
        memset(stack->undo,0,sizeof(undo_t)*stack->usedundo);
        stack->usedundo=0;
        memset(stack->buf,0,stack->usedbuf);
        stack->usedbuf=0;
        return(0);
}

int
redata_undo_inactivatelast(redata_t *redata, undostack_t *stack)
{
        if(redata==NULL
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack)))
                return(-1); /* sanity check failed */
        if(stack->usedundo==0)
                return(-1);
        stack->usedundo--;
        stack->usedbuf-=stack->undo[stack->usedundo].len;
        return(0);
}

int
redata_undo_reactivatelast(redata_t *redata, undostack_t *stack)
{
        if(redata==NULL
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack)))
                return(-1); /* sanity check failed */
        if(stack->usedundo==stack->sizeundo)
                return(-1);
        stack->usedbuf+=stack->undo[stack->usedundo].len;
        stack->usedundo++;
        return(0);
}

int
redata_undo_groupinit(redata_t *redata, undostack_t *stack)
{
        /* stores the current position in (undostack).groupinit */
#warning TODO
        return(-1);
}

int redata_undo_groupcommit(redata_t *redata, undostack_t *stack)
{
        /* marks as hint_groupedwithnext from stored groupinit position in undostack to last-1 position (if groupinit pos is the last pos, do nothing)*/
#warning TODO
        return(-1);
}

int
redata_op_add(redata_t *redata, long insertpos, char *buf, long buflen, undostack_t *fromhere)
{
        int chunkno;
        int offset;
        undo_t *undo;
        rechunk_t *chunk,*nextchunk;
        long avail,nextavail;
        int i;
        int needed;
        char *ptr;
        if(redata==NULL || buf==NULL || buflen<0 || insertpos<0
          || insertpos>redata_getused(redata)
          || (fromhere!=NULL && (fromhere!=&(redata->undostack) && fromhere!=&(redata->redostack))))
                return(-1); /* sanity check failed */
        if(insertpos==0 && redata->sizechunks==0) {
                /* add the first chunk */
                redata_preallocate(redata,buflen);
        }
        if(redata_getposptr(redata,insertpos,&chunkno,&offset)==-1)
                return(-1); /* invalid pos */
        if(fromhere!=&(redata->undostack)) {
                if((undo=redata_undo_newfrombuf(redata,&(redata->undostack),'A',buf,buflen))==NULL)
                        return(-1); /* couldn't create undo struct */
#ifdef REDATA_HASHUNDO
                redata_hash(redata,undo->prehash);
#endif
        } else {
                undo=NULL;
        }
        chunk=redata->chunks[chunkno];
        avail=redata->chunkdatasize-chunk->useddata;
        nextchunk=((chunkno+1)<redata->sizechunks)?redata->chunks[chunkno+1]:NULL;
        nextavail=(nextchunk==NULL)?0:redata->chunkdatasize-nextchunk->useddata;
        if(avail>=buflen) {
                /* fits in current chunk */
                redata_chunk_insertdata(redata,chunkno,offset,buf,buflen);
                redata_fix_nl(redata,chunkno);
                nextchunk=chunk;
        } else if((avail+nextavail)>=buflen) {
                /* part fits in current chunk, part in next chunk */
                int bothering;
                bothering=chunk->useddata-offset;
                bothering=(bothering>nextavail)?nextavail:bothering;
                redata_chunk_movedata(redata,chunkno,chunk->useddata-bothering,chunkno+1,0,bothering);
                avail=redata->chunkdatasize-chunk->useddata;
                avail=(avail>buflen)?buflen:avail;
                redata_chunk_insertdata(redata,chunkno,offset,buf,avail);
                redata_chunk_insertdata(redata,chunkno+1,0,buf+avail,buflen-avail);
        } else {
                /* will need to add more chunks */
                needed=(buflen+redata->chunkdatasize-1)/redata->chunkdatasize;
                needed*=redata->chunkdatasize;
                needed+=(chunk->useddata-offset);
                if(redata_preallocate(redata,redata_getsize(redata)+needed)!=0) {
                        if(undo!=NULL) {
                                redata->undostack.usedundo--;
                                redata->undostack.usedbuf-=buflen;
                                memset(undo,0,sizeof(undo_t));
                        }
                        return(-1); /* insuf. mem. */
                }
                redata_chunk_insertnew(redata,chunkno);
                redata_chunk_movedata(redata,chunkno,offset,chunkno+1,0,chunk->useddata-offset);
                nextchunk=redata->chunks[chunkno+1];
                avail=redata->chunkdatasize-chunk->useddata;
                avail=(avail>buflen)?buflen:avail;
                redata_chunk_insertdata(redata,chunkno,chunk->useddata,buf,avail);
                for(ptr=buf+avail,i=chunkno;(ptr-buf)<buflen;i++,ptr+=avail) {
                        redata_chunk_insertnew(redata,i);
                        avail=redata->chunkdatasize-redata->chunks[i+1]->useddata;
                        avail=(avail>(buflen-(ptr-buf)))?(buflen-(ptr-buf)):avail;
                        redata_chunk_insertdata(redata,i+1,0,ptr,avail);

                }
        }
        /* fix nl until nextchunk (...fix_nl can insert chunks) */
        for(i=chunkno;i<redata->sizechunks;i++) {
                redata_fix_nl(redata,i);
                if(redata->chunks[i]==nextchunk)
                        break;
        }
        /* activate undo */
        if(undo!=NULL) {
                /* new or from redo stack */
                undo->posorig=undo->posdest=insertpos;
#ifdef REDATA_HASHUNDO
                redata_hash(redata,undo->posthash);
#endif
                if(fromhere==&(redata->redostack))
                        redata_undo_removelast(redata,&(redata->redostack));
                else
                        redata_undo_wipe(redata,&(redata->redostack));
        } else {
                /* from undo stack */
                redata_undo_movelast(redata,&(redata->undostack),&(redata->redostack));
        }
        /* add to plugins (unsaved,...) */
        for(i=0;i<redata->sizeplugins;i++) {
                if(redata->plugins[i].add_or_unadd!=NULL) {
                        redata->plugins[i].add_or_unadd(redata,redata->plugins+i
                          ,(undo!=NULL)?redata->undostack.undo+redata->undostack.usedundo-1
                                       :redata->redostack.undo+redata->redostack.usedundo-1
                          ,(undo!=NULL)?0:1);
                }
        }
        /* compact if needed */
        if(redata_getsize(redata)>(redata->chunkdatasize) && redata_getavailable(redata)>(redata_getsize(redata)/2))
                redata_compact(redata);
        /* mark as modified */
        redata->needs_saving=1;
        return(0);
}

int
redata_op_addn(redata_t *redata, long pos, char character, long n, undostack_t *fromhere)
{
        if(redata==NULL || n<0)
                return(-1); /* sanity check failed */
        if(n==0)
                return(0); /* nothing to do */
        if(redata->sizeaddnbuf<n) {
                char *newptr;
                long newsize;
                newsize=(n+1+ADDNBLOCK)/ADDNBLOCK;
                newsize*=ADDNBLOCK;
                if((newptr=realloc(redata->addnbuf,newsize))==NULL)
                        return(-1); /* insuf. mem. */
                redata->addnbuf=newptr;
                redata->sizeaddnbuf=newsize;
        }
        memset(redata->addnbuf,character,n);
        redata->addnbuf[n]='\0';
        return(redata_op_add(redata, pos, redata->addnbuf, n, fromhere));
}

int
redata_op_del(redata_t *redata, long delpos, long size, undostack_t *fromhere)
{
        int chunkno,curchunk;
        int offset;
        undo_t *undo;
        rechunk_t *chunk;
        long ndel;
        long curpos,curdel;
        int i;
        if(redata==NULL || size<0
          || delpos<0
          || (delpos+size)>redata_getused(redata)
          || (fromhere!=NULL && (fromhere!=&(redata->undostack) && fromhere!=&(redata->redostack))))
                return(-1); /* sanity check failed */
        if(redata_getposptr(redata,delpos,&chunkno,&offset)==-1)
                return(-1); /* invalid pos */
        if(fromhere!=&(redata->undostack)) {
                if((undo=redata_undo_newfromchunks(redata,&(redata->undostack),'D',delpos,size))==NULL)
                        return(-1); /* couldn't create undo struct */
#ifdef REDATA_HASHUNDO
                redata_hash(redata,undo->prehash);
#endif
        } else {
                undo=NULL;
        }
        for(curchunk=chunkno,ndel=0;ndel<size;curchunk++) {
                curpos=(curchunk==chunkno)?offset:0;
                curdel=redata->chunks[curchunk]->useddata-curpos;
                curdel=(curdel>(size-ndel))?(size-ndel):curdel;
                redata_chunk_deletedata(redata,curchunk,curpos,curdel);
                ndel+=curdel;
        }
        /* fix nl and delete unused chunks */
        for(curchunk--;curchunk>=chunkno;curchunk--) {
                chunk=redata->chunks[curchunk];
                if(chunk->useddata==0) {
                        /* move this chunk to the end */
                        redata_chunk_deletechunk(redata,curchunk);
                } else
                        redata_fix_nl(redata,curchunk);
        }
        /* activate undo */
        if(undo!=NULL) {
                /* new or from redo stack */
                undo->posorig=undo->posdest=delpos;
#ifdef REDATA_HASHUNDO
                redata_hash(redata,undo->posthash);
#endif
                if(fromhere==&(redata->redostack))
                        redata_undo_removelast(redata,&(redata->redostack));
                else
                        redata_undo_wipe(redata,&(redata->redostack));
        } else {
                /* from undo stack */
                redata_undo_movelast(redata,&(redata->undostack),&(redata->redostack));
        }
        /* add to plugins (unsaved,...) */
        for(i=0;i<redata->sizeplugins;i++) {
                if(redata->plugins[i].add_or_unadd!=NULL) {
                        redata->plugins[i].add_or_unadd(redata,redata->plugins+i
                          ,(undo!=NULL)?redata->undostack.undo+redata->undostack.usedundo-1
                                       :redata->redostack.undo+redata->redostack.usedundo-1
                          ,(undo!=NULL)?0:1);
                }
        }
        /* compact if needed */
        if(redata_getsize(redata)>(redata->chunkdatasize) && redata_getavailable(redata)>(redata_getsize(redata)/2))
                redata_compact(redata);
        /* mark as modified */
        redata->needs_saving=1;
        return(0);
}

int
redata_op_move(redata_t *redata, long posorig, long size, long posdest, undostack_t *fromhere)
{
        int chunkno,dchunkno,curchunk;
        int offset,doffset;
        undo_t *undo;
        rechunk_t *chunk;
        int i;
        if(redata==NULL || size<0
          || posorig<0
          || (posorig+size)>redata_getused(redata)
          || posdest<0
          || (posdest>=posorig && posdest<(posorig+size))
          || (fromhere!=NULL && (fromhere!=&(redata->undostack) && fromhere!=&(redata->redostack))))
                return(-1); /* sanity check failed */
        if(redata_getposptr(redata,posorig,&chunkno,&offset)==-1)
                return(-1); /* invalid pos */
        if(redata_getposptr(redata,posdest,&dchunkno,&doffset)==-1)
                return(-1); /* invalid pos */
        if(fromhere!=&(redata->undostack)) {
                if((undo=redata_undo_newfromchunks(redata,&(redata->undostack),'M',posorig,size))==NULL)
                        return(-1); /* couldn't create undo struct */
                /* inactivate the undo so we are able to use return(-1) without removing it */
                redata_undo_inactivatelast(redata,&(redata->undostack));
#ifdef REDATA_HASHUNDO
                redata_hash(redata,undo->prehash);
#endif
        } else {
                undo=NULL;
        }
        if((offset+size)<=redata->chunks[chunkno]->useddata
          && (chunkno==dchunkno || (redata->chunkdatasize-redata->chunks[dchunkno]->useddata)>=size)) {
                /* trivial case: (all the data is in the same chunk) AND (it is intra-chunk move or destination chunk has enough avail. space) */
                redata_chunk_movedata(redata, chunkno, offset, dchunkno, doffset, size);
        } else {
                /* data spans several chunks, no space on dest, etc: do it the hard way */
                /* separate the selected data into its own chunk(s) */
                /* lower positions have to make the boundary first (or risk undoing it with next create boundary) */
                if(posdest<posorig) {
                        /* make a chunk boundary in posdest */
                        if(redata_getposptr(redata,posdest,&chunkno,&offset)==-1
                          || redata_chunk_splithere(redata,chunkno,offset)!=0)
                                return(-1); /* invalid pos or insuf. mem */
                }
                /* make a chunk boundary in posorig and in posorig+size */
                if(redata_getposptr(redata,posorig,&chunkno,&offset)==-1
                  || redata_chunk_splithere(redata,chunkno,offset)!=0)
                        return(-1); /* invalid pos or insuf. mem */
                if(redata_getposptr(redata,posorig+size,&chunkno,&offset)==-1
                  || redata_chunk_splithere(redata,chunkno,offset)!=0)
                        return(-1); /* invalid pos or insuf. mem */
                if(posdest>posorig) {
                        /* make a chunk boundary in posdest */
                        if(redata_getposptr(redata,posdest,&chunkno,&offset)==-1
                          || redata_chunk_splithere(redata,chunkno,offset)!=0)
                                return(-1); /* invalid pos or insuf. mem */
                }
                /* reorder the chunks */
                {
                        int schunkno;
                        int soffset;
                        if(redata_getposptr(redata,posorig,&chunkno,&offset)==-1)
                                return(-1); /* invalid pos or insuf. mem */
                        if(redata_getposptr(redata,posorig+size,&schunkno,&soffset)==-1)
                                return(-1); /* invalid pos or insuf. mem */
                        if(redata_getposptr(redata,posdest,&dchunkno,&doffset)==-1)
                                return(-1); /* invalid pos or insuf. mem */
                        if(offset!=0)
                                chunkno++;
                        if(soffset!=0)
                                schunkno++;
                        if(doffset!=0)
                                dchunkno++;
                        if(chunkno<0 || schunkno<0 || dchunkno<0
                          || chunkno>=redata->sizechunks || schunkno>=redata->sizechunks || dchunkno>=redata->sizechunks)
                                return(-1); /* ERROR: INTERNAL ERROR */
                        /* reorder inplace inverting the bytes (as in flipping a image) */
                        if(chunkno<dchunkno) {
                                meminvert(redata->chunks+chunkno,redata->chunks+dchunkno);
                                meminvert(redata->chunks+dchunkno-(schunkno-chunkno),redata->chunks+dchunkno);
                                meminvert(redata->chunks+chunkno,redata->chunks+dchunkno-(schunkno-chunkno));
                        } else {
                                meminvert(redata->chunks+dchunkno,redata->chunks+schunkno);
                                meminvert(redata->chunks+dchunkno,redata->chunks+dchunkno+(schunkno-chunkno));
                                meminvert(redata->chunks+dchunkno+(schunkno-chunkno),redata->chunks+schunkno);
                        }
                }
        }
        /* fix nl and delete unused chunks */
        if(posorig<posdest) {
              if(redata_getposptr(redata,posorig,&chunkno,&offset)==-1)
                      return(-1); /* invalid pos */
              if(redata_getposptr(redata,posdest,&dchunkno,&doffset)==-1)
                      return(-1); /* invalid pos */
        } else { /* posorig>posdest */
              if(redata_getposptr(redata,posdest,&chunkno,&offset)==-1)
                      return(-1); /* invalid pos */
              if(redata_getposptr(redata,posorig+size,&dchunkno,&doffset)==-1)
                      return(-1); /* invalid pos */
        }
        for(curchunk=dchunkno;curchunk>=chunkno;curchunk--) {
                chunk=redata->chunks[curchunk];
                if(chunk->useddata==0) {
                        /* move this chunk to the end */
                        redata_chunk_deletechunk(redata,curchunk);
                } else
                        redata_fix_nl(redata,curchunk);
        }
        /* activate undo */
        if(fromhere!=&(redata->undostack)) {
                /* reactivate the undo, now it is fine */
                redata_undo_reactivatelast(redata,&(redata->undostack));
        }
        if(undo!=NULL) {
                /* new or from redo stack */
                undo->posorig=posorig;
                undo->posdest=posdest;
#ifdef REDATA_HASHUNDO
                redata_hash(redata,undo->posthash);
#endif
                if(fromhere==&(redata->redostack))
                        redata_undo_removelast(redata,&(redata->redostack));
                else
                        redata_undo_wipe(redata,&(redata->redostack));
        } else {
                /* from undo stack */
                redata_undo_movelast(redata,&(redata->undostack),&(redata->redostack));
        }
        /* add to plugins (unsaved,...) */
        for(i=0;i<redata->sizeplugins;i++) {
                if(redata->plugins[i].add_or_unadd!=NULL) {
                        redata->plugins[i].add_or_unadd(redata,redata->plugins+i
                          ,(undo!=NULL)?redata->undostack.undo+redata->undostack.usedundo-1
                                       :redata->redostack.undo+redata->redostack.usedundo-1
                          ,(undo!=NULL)?0:1);
                }
        }
        /* compact if needed */
        if(redata_getsize(redata)>(redata->chunkdatasize) && redata_getavailable(redata)>(redata_getsize(redata)/2))
                redata_compact(redata);
        /* mark as modified */
        redata->needs_saving=1;
        return(0);
}

int
redata_op_undo(redata_t *redata, long *newcursorpos)
{
        undo_t *undo;
        long newpos=0;
        if(redata==NULL
          || redata->undostack.usedundo<1)
                return(-1); /* sanity check failed */
        undo=redata->undostack.undo+redata->undostack.usedundo-1;
        if(undo->type=='A') { /* ADD */
                newpos=undo->posorig;
                redata_op_del(redata,undo->posorig,undo->len,&(redata->undostack));
        } else if(undo->type=='D') { /* DEL */
                newpos=undo->posorig+undo->len;
                redata_op_add(redata,undo->posorig,redata->undostack.buf+undo->off,undo->len,&(redata->undostack));
        } else if(undo->type=='M') { /* MOVE */
                newpos=undo->posorig+undo->len;
                if(undo->posorig<undo->posdest)
                        redata_op_move(redata,undo->posdest-undo->len, undo->len, undo->posorig,&(redata->undostack));
                else
                        redata_op_move(redata,undo->posdest, undo->len, undo->posorig+undo->len,&(redata->undostack));
        } else
                return(-1); /* unknown operation */
        if(newcursorpos!=NULL)
                *newcursorpos=newpos;
        /* mark as modified */
        redata->needs_saving=1;
        return(0);
}

int
redata_op_redo(redata_t *redata, long *newcursorpos)
{
        undo_t *undo;
        long newpos;
        if(redata==NULL
          || redata->redostack.usedundo<1)
                return(-1); /* sanity check failed */
        undo=redata->redostack.undo+redata->redostack.usedundo-1;
        if(undo->type=='A') { /* ADD */
                newpos=undo->posorig+undo->len;
                redata_op_add(redata,undo->posorig,redata->redostack.buf+undo->off,undo->len,&(redata->redostack));
        } else if(undo->type=='D') { /* DEL */
                newpos=undo->posorig;
                redata_op_del(redata,undo->posorig,undo->len,&(redata->redostack));
        } else if(undo->type=='M') { /* MOVE */
                newpos=undo->posorig+undo->len;
                redata_op_move(redata,undo->posorig, undo->len, undo->posdest,&(redata->redostack));
        } else
                return(-1); /* unknown operation */
#warning TODO: Is it neccessary to do an unadd to the plugins?
        if(newcursorpos!=NULL)
                *newcursorpos=newpos;
        /* mark as modified */
        redata->needs_saving=1;
        return(0);
}

int
redata_data_compare(redata_t *redata, long cmppos, char *buf, long buflen)
{
        int chunkno;
        int offset;
        long compared;
        long n;
        int res;
        if(redata==NULL || cmppos<0 || buf==NULL || buflen<0
          || cmppos>redata_getused(redata)
          || (cmppos+buflen)>redata_getused(redata))
                return(-1); /* sanity check failed */
        if(redata_getposptr(redata,cmppos,&chunkno,&offset)==-1)
                return(-1); /* invalid pos */
        for(compared=0,n=0;compared<buflen && chunkno<redata->sizechunks;chunkno++,offset=0,compared+=n) {
                n=redata->chunks[chunkno]->useddata-offset;
                n=(n<0)?0:((compared+n)>buflen)?(buflen-compared):n;
                if((res=memcmp(redata->chunks[chunkno]->data+offset,buf+compared,n))!=0)
                        return(res);
        }
        if(compared<buflen)
                return(1);
        return(0);
}



int
redata_compact(redata_t *redata)
{
        /* compact and free surplus chunks */
        /* criterion: */
        /* 1. if two neighbouring chunks could join with 10% free in result chunk, do it */
        /* 2. if there are more than 2 unused chunks free at the end, free all unused chunks except two */
        int i,l;
        rechunk_t *chunk,**newchunks;
        if(redata==NULL)
                return(-1); /* sanity check failed */
        /* skip free chunks at end */
        for(i=redata->sizechunks-1;i>=0;i--) {
                if(redata->chunks[i]->useddata!=0)
                        break;
        }
        /* join neighbouring chunks where appropiate */
        l=redata->chunkdatasize-(redata->chunkdatasize)/10;
        for(;i>0;i--) {
                if((redata->chunks[i]->useddata+redata->chunks[i-1]->useddata)>=l)
                        continue;
                /* move data to prev. chunk, move chunk to end */
                redata_chunk_fillfromnext(redata,i-1,redata->chunks[i]->useddata);
                if(redata->chunks[i]->useddata>0)
                        continue; /* couldn't move data */
                chunk=redata->chunks[i];
                memmove(redata->chunks+i,redata->chunks+i+1,sizeof(rechunk_t *)*(redata->sizechunks-i-1));
                redata->chunks[redata->sizechunks-1]=chunk;
        }
        /* free unused chunks at end (leave two empty chunks, free the rest) */
        for(i=redata->sizechunks-1;i>=0;i--) {
                if(redata->chunks[i]->useddata!=0)
                        break;
        }
        l=i+3;
        if(l<redata->sizechunks) {
                for(i=l;i<redata->sizechunks;i++) {
                        free(redata->chunks[i]),redata->chunks[i]=NULL;
                        redata->available-=redata->chunkdatasize;
                }
                if((newchunks=realloc(redata->chunks,l*sizeof(rechunk_t *)))!=NULL)
                        redata->chunks=newchunks;
                redata->sizechunks=l;
        }
        return(0);
}

int
redata_hash(redata_t *redata, char *resbuf129bytes)
{
        return(redata_hash_gen(redata,NULL,NULL,0,resbuf129bytes));
}

int
redata_filehash(redata_t *redata, char *filename, char *resbuf129bytes)
{
        if(resbuf129bytes!=NULL)
                *resbuf129bytes='\0';
        if(filename==NULL)
                return(-1);
        return(redata_hash_gen(NULL,filename,NULL,0,resbuf129bytes));
}

int
redata_memhash(redata_t *redata, char *buf, long buflen, char *resbuf129bytes)
{
        if(resbuf129bytes!=NULL)
                *resbuf129bytes='\0';
        if(buf==NULL || buflen<0)
                return(-1);
        return(redata_hash_gen(NULL,NULL,buf,buflen,resbuf129bytes));
}

undostack_t *
redata_getstack(redata_t *redata, undo_t *undo)
{
        if(redata==NULL || undo==NULL)
                return(NULL); /* sanity check failed */
        if(undo>=redata->undostack.undo && undo<(redata->undostack.undo+redata->undostack.sizeundo))
                return(&(redata->undostack));
        if(undo>=redata->redostack.undo && undo<(redata->redostack.undo+redata->redostack.sizeundo))
                return(&(redata->redostack));
        return(NULL); /* unknown stack */
}

char *
redata_generic_genname(char *filename,char *prefix, char *postfix, char *buf, int bufsize)
{
        char *name,*ptr;
        int filenamelen;
        int prelen,postlen,finallen,off;
        if(filename==NULL || prefix==NULL || postfix==NULL)
                return(NULL);
        filenamelen=strlen(filename);
        prelen=strlen(prefix);
        postlen=strlen(postfix);
        finallen=filenamelen+prelen+postlen+1;
        if(buf==NULL) {
                if((name=malloc(finallen))==NULL)
                        return(NULL);
        } else {
                if(bufsize<filenamelen)
                        return(NULL);
                name=buf;
        }
        for(ptr=filename+strlen(filename);ptr>filename && ptr[-1]!='/';ptr--)
                ;
        off=0;
        memcpy(name+off,filename,ptr-filename);
        off+=ptr-filename;
        memcpy(name+off,prefix,prelen);
        off+=prelen;
        memcpy(name+off,ptr,filenamelen-(ptr-filename));
        off+=filenamelen-(ptr-filename);
        memcpy(name+off,postfix,postlen);
        off+=postlen;
        name[off]='\0';
        return(name);
}

long
redata_searchforward(redata_t *redata, long posini, char *str, int len)
{
        int numchunk;
        int offset;
        int avail;
        long chunkstartpos;
        rechunk_t *chunk;
        char *ptr;
        if(redata==NULL || posini<0 || (posini+len)>redata_getsize(redata) || len<0 || (str==NULL && len>0))
                return(-1); /* sanity check failed */
        if(len==0)
                return(posini); /* nothing to do, empty string is always equal */
        if(redata_getposptr(redata,posini,&numchunk,&offset)!=0)
                return(-1); /* couldn't get pos */
        chunkstartpos=posini-offset;
        for(;numchunk<redata->sizechunks
          ;chunkstartpos+=chunk->useddata,numchunk++,offset=0) {
                chunk=redata->chunks[numchunk];
                avail=chunk->useddata-offset;
                while(avail>0 && (ptr=memchr(chunk->data+offset,str[0],avail))!=NULL) {
                        offset=ptr-((char *)(chunk->data));
                        if(redata_memcmp(redata,chunkstartpos+offset,str,len)==0)
                                return(chunkstartpos+offset);
                        offset++;
                        avail=chunk->useddata-offset;
                }
        }
        return(-1); /* not found */
}

long
redata_searchbackwards(redata_t *redata, long posini, char *str, int len)
{
        int numchunk;
        int offset;
        long chunkstartpos;
        rechunk_t *chunk;
        char *ptr;
        if(redata==NULL || posini<0 || (posini+len)>redata_getsize(redata) || len<0 || (str==NULL && len>0))
                return(-1); /* sanity check failed */
        if(len==0)
                return(posini); /* nothing to do, empty string is always equal */
        if(redata_getposptr(redata,posini,&numchunk,&offset)!=0)
                return(-1); /* couldn't get pos */
        while((numchunk+1)<redata->sizechunks && offset==redata->chunks[numchunk]->useddata) {
                numchunk++;
                offset=0;
        }
        chunkstartpos=posini-offset;
        for(offset++;numchunk>=0
          ;numchunk--
          ,offset=(numchunk>=0)?redata->chunks[numchunk]->useddata:0
          ,chunkstartpos-=(numchunk>=0)?redata->chunks[numchunk]->useddata:0) {
                chunk=redata->chunks[numchunk];
                while(offset>0 && (ptr=mymemrchr(chunk->data,str[0],offset))!=NULL) {
                        offset=ptr-((char *)(chunk->data));
                        if(redata_memcmp(redata,chunkstartpos+offset,str,len)==0)
                                return(chunkstartpos+offset);
                }
        }
        return(-1); /* not found */
}

int
redata_memcmp(redata_t *redata, long pos, char *str, int len)
{
        int numchunk;
        int offset;
        int avail;
        int ncompared;
        rechunk_t *chunk;
        int res;
        if(redata==NULL || pos<0 || (pos+len)>redata_getsize(redata) || len<0 || (str==NULL && len>0))
                return(-1); /* sanity check failed, return "this is smaller" */
        if(len==0)
                return(0); /* nothing to do, empty string is always equal */
        if(redata_getposptr(redata,pos,&numchunk,&offset)!=0)
                return(-1); /* couldn't get pos */
        for(ncompared=0;numchunk<redata->sizechunks;numchunk++,offset=0) {
                chunk=redata->chunks[numchunk];
                avail=chunk->useddata-offset;
                avail=(avail>(len-ncompared))?(len-ncompared):avail;
                if((res=memcmp(chunk->data+offset,str+ncompared,avail))!=0 || (ncompared+avail)==len)
                        return(res); /* comparison finished */
                ncompared+=avail;
        }
        return(-1); /* not enough data in chunks */

}

int
redata_getutf8char(redata_t *redata, long pos, char *buf, int len, int *usedbuf)
{
        int numchunk;
        int offset;
        rechunk_t *chunk;
        int ooff;
        int req;
        int c;
        if(redata==NULL || pos<0 || pos>redata_getsize(redata) || buf==NULL || len<1 || usedbuf==NULL)
                return(-1); /* sanity check failed */
        ooff=0;
        if(redata_getposptr(redata,pos,&numchunk,&offset)!=0)
                return(-1); /* couldn't get pos */
        while(numchunk<redata->sizechunks && redata->chunks[numchunk]!=NULL && offset>=redata->chunks[numchunk]->useddata) {
                numchunk++;
                offset=0;
        }
        if(numchunk>=redata->sizechunks)
                return(-1); /* at end; no data */
        chunk=redata->chunks[numchunk];
        c=chunk->data[offset];
        ((unsigned char *)buf)[ooff++]=c;
        if(UTF8_IS_ASCII(c) || UTF8_IS_MULTIBYTECONT(c))
                req=1;
        else
                req=UTF8_MULTIBYTESTART2LEN(c);
        offset++;
        for(;ooff<len && ooff<req && numchunk<redata->sizechunks
            ;numchunk++,chunk=redata->chunks[numchunk],offset=0) {
                if(offset>=chunk->useddata)
                        continue;
                c=0x80|0x40; /* initialize c to whatever multibycont for the check after the while */
                while(ooff<len && ooff<req
                  && (c=chunk->data[offset])!=0 && UTF8_IS_MULTIBYTECONT(c)) {
                        ((unsigned char *)buf)[ooff++]=c;
                }
                if(!UTF8_IS_MULTIBYTECONT(c))
                        break;
        }
        *usedbuf=ooff;
        return(0);
}

int
redata_getprevutf8char(redata_t *redata, long pos, char *buf, int len, int *usedbuf)
{
        int numchunk;
        int offset;
        int ooff;
        int c;
        int n;
        char tmpchar;
        if(redata==NULL || pos<=0 || pos>redata_getsize(redata) || buf==NULL || len<1 || usedbuf==NULL)
                return(-1); /* sanity check failed */
        if(redata_getposptr(redata,pos,&numchunk,&offset)!=0)
                return(-1); /* couldn't get pos */
        while(numchunk>0 && offset==0) {
                numchunk--;
                offset=redata->chunks[numchunk]->useddata;
        }
        if(offset==0)
                return(-1); /* at start, no data */
        ooff=0;
        c=redata->chunks[numchunk]->data[offset-1];
        offset--;
        ((unsigned char *)buf)[ooff++]=c;
        while(ooff<len && (!UTF8_IS_ASCII(c) || UTF8_IS_MULTIBYTECONT(c))) {
                while(numchunk>0 && offset==0) {
                        numchunk--;
                        offset=redata->chunks[numchunk]->useddata;
                }
                if(numchunk==0 && offset==0)
                        break;
                c=redata->chunks[numchunk]->data[offset-1];
                offset--;
                ((unsigned char *)buf)[ooff++]=c;
        }
        *usedbuf=ooff;
        /* invert the data in buf */
        for(n=0;(ooff-1-n)>n;n++) {
                tmpchar=buf[n];
                buf[n]=buf[ooff-1-n];
                buf[ooff-1-n]=tmpchar;
        }
        return(0);
}

int
redata_getsubstr(redata_t *redata, long posini, long posend, char *buf, int len, int *usedbuf)
{
        int numchunk;
        int offset;
        int ooff;
        long pos;
        if(redata==NULL || posini<0 || posini>redata_getsize(redata) || posend<=0 || posend>redata_getsize(redata) || buf==NULL || len<1 || usedbuf==NULL)
                return(-1); /* sanity check failed */
        if(redata_getposptr(redata,posini,&numchunk,&offset)!=0)
                return(-1); /* couldn't get pos */
        ooff=0;
        pos=posini;
        while(ooff<len && pos<posend) {
                while(numchunk<redata->sizechunks && redata->chunks[numchunk]!=NULL && offset>=redata->chunks[numchunk]->useddata) {
                        numchunk++;
                        offset=0;
                }
                if(numchunk>=redata->sizechunks)
                        return(-1); /* at end; no data */
                buf[ooff++]=redata->chunks[numchunk]->data[offset++];
                pos++;
        }
        *usedbuf=ooff;
        return(0);
}

int
redata_generic_utf8len(char *ptr, int size)
{
        int len,i;
        /* calculate the number of utf8-charaters in buffer */
        if(size<0 || (ptr==NULL && size!=0))
                return(-1);
        /* for now we only count the number of code points */
        /* in UTF8: 0x00-0x7f single byte chars
         *          0xc0-0xff leading bytes
         *          0x80-0xbf continuation bytes (ignore these for len)*/
/*#warning TODO: XXX support combining code points (at least U+0300 - U+036F ( https://en.wikipedia.org/wiki/Combining_character ) */
        for(len=0,i=0;i<size;i++)
                len+=((ptr[i]&0xc0)!=0x80)?1:0;
        return(len);
/*#warning TODO: XXX Also consider tabs*/
}

int
redata_generic_utf8lenincomplete(char *ptr, int size, int *nstartincomplete,int *nendincomplete, int *nendrequired)
{
        int len,i;
        int lastistart,lastclen;
/*#warning TODO: XXX support combining code points (at least U+0300 - U+036F ( https://en.wikipedia.org/wiki/Combining_character ) */
        if(size<0 || (ptr==NULL && size!=0) || nstartincomplete==NULL || nendincomplete==NULL)
                return(-1);
        /* from RFC2279/RFC3629, one character is up to 6 bytes:
           first    last     Byte1      Byte2      Byte3      Byte4
           U+0000   U+007F   0xxxxxxx
           U+0080   U+07FF   110xxxxx   10xxxxxx
           U+0800   U+FFFF   1110xxxx   10xxxxxx   10xxxxxx
          U+10000 U+10FFFF   11110xxx   10xxxxxx   10xxxxxx   10xxxxxx
            ...
         */   
        i=0;
        len=0;
        /* nstartincomplete */
        *nstartincomplete=0;
        while(i<size && (ptr[i]&(0x80|0x40))==0x80) {
                (*nstartincomplete)++;
                i++;
        }
        /* len */
        lastistart=-1;
        lastclen=0;
        for(;i<size;i++) { 
                len+=((ptr[i]&0xc0)!=0x80)?1:0;
                if((ptr[i]&0x80)==0x00) {
                        /* ASCII */
                        lastistart=i;
                        lastclen=1;
                } else if((ptr[i]&0xc0)!=0x80) {
                        /* multibytechar start (as ASCII case has already been processed) */
                        lastistart=i;
                        lastclen=((ptr[i]&0xe0)==0xc0)?2:
                                 ((ptr[i]&0xf0)==0xe0)?3:
                                 ((ptr[i]&0xf8)==0xf0)?4:
                                 ((ptr[i]&0xfc)==0xf8)?5:
                                 ((ptr[i]&0xfe)==0xfc)?6:
                                 1; /* unknown type of multibytechar */
                }
        }
        /* nendincomplete */
        *nendincomplete=0;
        *nendrequired=0;
        if(lastistart!=-1 && (lastistart+lastclen)>size) {
                *nendrequired=lastclen;
                *nendincomplete=(lastistart+lastclen-size);
                len--;
        }
        /* all done */
        return(len);
/*#warning TODO: XXX Also consider tabs*/
}

char *
redata_generic_utf8col(char *ptr, int size, int col)
{
        int len,i;
        /* return a pointer to the "n"th ("col"th) utf8-character in buffer */
        if(size<0 || (ptr==NULL && size!=0))
                return(NULL); /* sanity check failed */
        /* see reui_utf8len() for explanation of algorithm */
/*#warning TODO: support combining code points (at least U+0300 - U+036F ( https://en.wikipedia.org/wiki/Combining_character ) */
        if(col>=size)
                return(NULL);/* col greater than maximum possible char. count */
        /* skip "col" amount of single byte chars and leading bytes */
        for(len=0,i=0;len<col && i<size;i++)
                len+=((ptr[i]&0xc0)!=0x80)?1:0;
        /* if we landed in a continuation byte, advance until next single byte chars or leading byte */
        while(i<size && (ptr[i]&0xc0)==0x80)
                i++;
        if(i>=size)
                return(NULL); /* col is beyond end of string */
        return(ptr+i);
/*#warning TODO: XXX Also consider tabs*/
}

int
redata_generic_utf8charlen(char *ptr, int maxsize)
{
        int i;
        /* returns the len in bytes of the character starting at ptr (zero on error)*/
        if(ptr==NULL || maxsize<1)
                return(0); /* sanity check failed */
/*#warning TODO: support combining code points (at least U+0300 - U+036F ( https://en.wikipedia.org/wiki/Combining_character ) */
        if(((unsigned char *)ptr)[0]<0x80)
                return(1); /* single byte char */
        if((ptr[0]&0xc0)==0x80)
                return(0); /* error: this is continuation, not leading byte */
        for(i=1;i<maxsize && (ptr[i]&0xc0)==0x80;i++)
                ;
        return(i);
}

inline int
redata_generic_utf8isstartbyte(int candidate)
{
        if((candidate&0xc0)!=0x80)
                return(1);
        return(0);
}

static int
redata_hash_gen(redata_t *redata, char *filename, char *buf, long buflen, char *resbuf129bytes)
{
        static char conv[]={"0123456789ABCDEF"};
        sha3_context sha3;
        unsigned char *hash;
        int i,c;
        int fd=-1;
        struct stat statbuf;
        if(resbuf129bytes==NULL)
                return(-1);  /* sanity check failed */
        if(resbuf129bytes!=NULL)
                *resbuf129bytes='\0';
        if((redata==NULL && filename==NULL && buf==NULL)
           || (redata!=NULL && (filename!=NULL || buf!=NULL))
           || (filename!=NULL && (redata!=NULL || buf!=NULL))
           || (buf!=NULL && (redata!=NULL || filename!=NULL))) {
                return(-1); /* sanity check failed */
        }
        if(filename!=NULL) {
                if((fd=open(filename,O_RDONLY))==-1 || fstat(fd,&statbuf)!=0 || !S_ISREG(statbuf.st_mode)) {
                       if(fd!=-1)
                               close(fd),fd=-1;
                       return(-1); /* file not found, couldn't query size or not regular file */
                }
        }
        sha3_Init512(&sha3);
        if(redata!=NULL) {
                for(i=0;i<redata->sizechunks;i++)
                        sha3_Update(&sha3,(void *) redata->chunks[i]->data,redata->chunks[i]->useddata);
        } else if(filename!=NULL) {
                char buf[16384];
                long totalread,nread;
                for(totalread=0,nread=0;totalread<statbuf.st_size;totalread+=nread,nread=0) {
                        if((nread=read(fd,buf,sizeof(buf)))<=0) {
                                close(fd),fd=-1;
                                return(-1); /* short read */
                        }
                        sha3_Update(&sha3,(void *) buf,nread);
                }
                close(fd),fd=-1;
        } else if(buf!=NULL) {
                sha3_Update(&sha3,(void *) buf,buflen);
        }
        hash=(unsigned char *)sha3_Finalize(&sha3);
        for(i=0;i<64;i++) {
                c=hash[i];
                resbuf129bytes[i<<1]=conv[((c>>4)&0xf)];
                resbuf129bytes[(i<<1)+1]=conv[(c&0xf)];
        }
        resbuf129bytes[128]='\0';
        /* NOTE this is SHA3-512, not keccak (empty result is a69f..cd26) */
        return(0);
}

int
redata_line_rawinfo(redata_t *redata, long pos, long *startpos, char **startptr, int *len, int *is_continuation)
{
        long chunkpos,newpos,endpos;
        int nchunk;
        rechunk_t *chunk;
        if(redata==NULL || pos<0 || pos>=redata_getused(redata))
                return(-1);
        for(nchunk=0,chunkpos=0
          ;nchunk<redata->sizechunks
          ;chunkpos+=(chunk!=NULL)?chunk->useddata:0,nchunk++) {
                if((chunk=redata->chunks[nchunk])==NULL)
                        continue;
                if(pos>=chunkpos && pos<(chunkpos+chunk->useddata))
                        break;
        }
        if(nchunk>=redata->sizechunks)
                return(-1); /* pos not found */
        for(newpos=pos;newpos>chunkpos && chunk->data[newpos-chunkpos-1]!='\n';newpos--)
                ;
        for(endpos=pos;endpos<(chunkpos+chunk->useddata) && chunk->data[endpos-chunkpos]!='\n';endpos++)
                ;
        if(endpos==(chunkpos+chunk->useddata))
                endpos--;
        if(startpos!=NULL)
                *startpos=newpos;
        if(startptr!=NULL)
                *startptr=(char *) (chunk->data+(newpos-chunkpos));
        if(len!=NULL)
                *len=endpos-newpos+1;
        if(is_continuation!=NULL) {
                if(!(chunk->whatin_fresh))
                        redata_whatin_refresh(redata,nchunk);
                *is_continuation=(newpos==chunkpos && chunk->whatin.iscontinuation)?1:0;
        }
        return(0);
}

int
redata_line_realstart(redata_t *redata, long pos, long *startpos)
{
        int is_continuation;
        long nextpos,newpos;
        if(redata==NULL || pos<0)
                return(-1); /* sanity check failed */
        nextpos=pos;
        do {
                if(redata_line_rawinfo(redata,nextpos,&newpos,NULL,NULL,&is_continuation)==-1)
                        return(-1);
                nextpos=newpos-1;
        } while(is_continuation && newpos>0);
        if(startpos!=NULL)
                *startpos=newpos;
        return(0);
}

int
redata_line_realend(redata_t *redata, long pos, long *endpos)
{
        long nextpos,newpos;
        long datasize;
        char *ptr;
        int len;
        int has_nl;
        if(redata==NULL || pos<0)
                return(-1); /* sanity check failed */
        nextpos=pos;
        if((datasize=redata_getused(redata))<=0)
                return(-1); /* couldn't get last pos or there is no data */
        do {
                if(redata_line_rawinfo(redata,nextpos,&newpos,&ptr,&len,NULL)==-1 || len==0)
                        return(-1);
                nextpos=newpos+len;
                has_nl=(len>0 && ptr[len-1]=='\n')?1:0;
        } while(!has_nl && nextpos<datasize);
        if(endpos!=NULL)
                *endpos=newpos+len-1;
        return(0);
}

int
redata_line_prevrealstart(redata_t *redata, long pos, long *startpos)
{
        long newpos,prevpos;
        if(redata==NULL || pos<0)
                return(-1);
        if(redata_line_realstart(redata,pos,&newpos)==-1)
                return(-1); /* couldn't get start of line */
        if(redata_line_realstart(redata,newpos-1,&prevpos)==-1)
                return(-1); /* couldn't get start of line */
        if(startpos!=NULL)
                *startpos=prevpos;
        return(0);
}

int
redata_line_nextrealstart(redata_t *redata, long pos, long *startpos)
{
        long newpos,nextpos;
        if(redata==NULL || pos<0)
                return(-1);
        if(redata_line_realend(redata,pos,&newpos)==-1)
                return(-1); /* couldn't get end of line */
        if(redata_line_realstart(redata,newpos+1,&nextpos)==-1)
                return(-1); /* couldn't get start of line */
        if(startpos!=NULL)
                *startpos=nextpos;
        return(0);
}

int
redata_line_inccol(redata_t *redata, long pos, int ncolrequest, long *newpos, int *ncoldone)
{
        long curpos,startpos;
        char *ptr;
        int len;
        int n,i;
        int done;
        if(redata==NULL || pos<0 || ncolrequest<0)
                return(-1); /* sanity check failed */
        if(ncolrequest==0) {
                if(newpos!=NULL)
                        *newpos=pos;
                if(ncoldone!=NULL)
                        *ncoldone=0;
                return(0); /* nothing to do */
        }
        for(n=0,curpos=pos;n<=ncolrequest;) {
                if(redata_line_rawinfo(redata,curpos,&startpos,&ptr,&len,NULL)==-1 || len==0)
                        return(-1); /* couldn't get current line data */
                done=0;
                /* advance until we are positioned on the next char to the requested one */
                for(i=startpos-curpos;i<len;i++) {
                        if(redata_generic_utf8isstartbyte(ptr[i]))
                                n++;
                        if(ptr[i]=='\n' || n>ncolrequest) {
                                done=1;
                                break;
                        }
                }
                curpos=startpos+i;
                if(done)
                        break;
        }
        if(newpos!=NULL)
                *newpos=curpos;
        if(ncoldone!=NULL)
                *ncoldone=n-1;
        return(0);
}


int
redata_pos2linecol(redata_t *redata, long pos, int *resline, int *rescol)
{
        if(redata==NULL || pos<0 || pos>=redata_getused(redata))
                return(-1);
        /* calculate line */
        if(resline!=NULL) {
                long chunkpos,newpos;
                int startline;
                int nchunk;
                rechunk_t *chunk;
                int i;
                for(nchunk=0,chunkpos=0,startline=0
                  ;nchunk<redata->sizechunks
                  ;chunkpos+=(chunk!=NULL)?chunk->useddata:0
                    ,startline+=(chunk!=NULL)?chunk->whatin.nlcount:0,nchunk++) {
                        if((chunk=redata->chunks[nchunk])==NULL)
                                continue;
                        if(!(chunk->whatin_fresh))
                                redata_whatin_refresh(redata,nchunk);
                        if(pos>=chunkpos && pos<(chunkpos+chunk->useddata))
                                break;
                }
                if(nchunk>=redata->sizechunks)
                        return(-1); /* pos not found */
                for(newpos=chunkpos,i=0;newpos<pos && i<chunk->useddata;newpos++,i++) {
                        if(chunk->data[i]=='\n')
                                startline++;
                }
                *resline=startline;
        }
        /* calculate col */
        if(rescol!=NULL) {
                long realstart,curpos,startpos;
                char *ptr;
                int len;
                int n,i;
                int done;
                if(redata_line_realstart(redata,pos,&realstart)==-1)
                        return(-1); /* startpos for pos not found */
                for(n=0,curpos=realstart;curpos<pos;) {
                        if(redata_line_rawinfo(redata,curpos,&startpos,&ptr,&len,NULL)==-1 || len==0)
                                return(-1); /* couldn't get current line data */
                        done=0;
                        for(i=0;i<len && (startpos+i)<pos;i++) {
                                if(redata_generic_utf8isstartbyte(ptr[i]))
                                        n++;
                                if(ptr[i]=='\n') {
                                        done=1;
                                        break;
                                }
                        }
                        curpos=startpos+i;
                        if(done)
                                break;
                }
                *rescol=n;
        }
        return(0);
}

int
redata_linecol2pos(redata_t *redata, int line, int colrequest, long *pos, int *coldone)
{
        long chunkpos,realstart,curpos,startpos;
        int startline;
        int nchunk;
        rechunk_t *chunk;
        int i,n;
        char *ptr;
        int len;
        int done;                       
        if(redata==NULL || line<0 || colrequest<0)
                return(-1);
        /* find line */
        for(nchunk=0,chunkpos=0,startline=0
          ;nchunk<redata->sizechunks
          ;chunkpos+=(chunk!=NULL)?chunk->useddata:0
            ,startline+=(chunk!=NULL)?chunk->whatin.nlcount:0,nchunk++) {
                if((chunk=redata->chunks[nchunk])==NULL)
                        continue;
                if(!(chunk->whatin_fresh))
                        redata_whatin_refresh(redata,nchunk);
                if(line>=startline && line<=(startline+chunk->whatin.nlcount))
                        break;
        }
        if(nchunk>=redata->sizechunks)
                return(-1); /* line not found */
        for(i=0;line!=startline && i<chunk->useddata;i++) {
                if(chunk->data[i]=='\n')
                        startline++;
        }
        if(i>chunk->useddata)
                return(-1); /* line not found */
        realstart=chunkpos+i;
        /* trivial case: col 0 */
        if(colrequest==0) {
                if(coldone!=NULL)
                        *coldone=0;
                if(pos!=NULL)
                        *pos=realstart;
                return(0);
        }
        /* find col, stopping at the start of next char */
        for(n=-1,curpos=realstart;n<colrequest;) {
                if(redata_line_rawinfo(redata,curpos,&startpos,&ptr,&len,NULL)==-1 || len==0)
                        return(-1); /* couldn't get current line data */
                done=0;
                for(i=0;i<len && n<colrequest;i++,curpos++) {
                        if(ptr[i]=='\n') {
                                n++;
                                done=1;
                                break;
                        } else if(UTF8_IS_ASCII_OR_START(ptr[i])) {                    
                                n++;
                                if(n==colrequest)
                                        break;
                        }
                }
                if(done)
                        break;
        }
        if(coldone!=NULL)
                *coldone=n;
        if(pos!=NULL)
                *pos=curpos;
        return(0);
}

int
redata_line_total(redata_t *redata)
{
        long chunkpos;
        int nchunk;
        int startline;
        rechunk_t *chunk,*lastchunkwithdata;
        if(redata==NULL)
                return(-1);
        /* find line */
        lastchunkwithdata=NULL;
        for(nchunk=0,chunkpos=0,startline=0
          ;nchunk<redata->sizechunks
          ;chunkpos+=(chunk!=NULL)?chunk->useddata:0
            ,startline+=(chunk!=NULL)?chunk->whatin.nlcount:0,nchunk++) {
                if((chunk=redata->chunks[nchunk])==NULL)
                        continue;
                if(!(chunk->whatin_fresh))
                        redata_whatin_refresh(redata,nchunk);
                if(chunk->useddata>0)
                        lastchunkwithdata=chunk;
        }
        if(lastchunkwithdata!=NULL && lastchunkwithdata->data[lastchunkwithdata->useddata-1]!='\n')
                startline++;
        return(startline);
}

int
redata_line_getendstr(redata_t *redata, int line, char *buf, int sizebuf)
{
        return(redata_line_getendstrtrimmed(redata,line,buf,sizebuf,NULL));
}

int
redata_line_getendstrtrimmed(redata_t *redata, int line, char *buf, int sizebuf, char *trimchars)
{
        long startpos,endpos;
        int coldone;
        if(redata==NULL || line<0 || buf==NULL || sizebuf<1)
                return(-1); /* sanity check error */
        if(redata_linecol2pos(redata,line,0,&startpos,&coldone)!=0
          || redata_line_realend(redata,startpos,&endpos)!=0) {
                return(-1); /* line not found */
        }
        if(endpos>startpos && redata_getchar(redata,endpos-1)=='\n')
                endpos--;
        if(trimchars!=NULL) {
                while(endpos>startpos && strchr(trimchars,redata_getchar(redata,endpos))!=NULL)
                        endpos--;
        }
        if((endpos-startpos)>(sizebuf-1))
                startpos=endpos-(sizebuf-1);
        redata_getdata(redata,startpos,endpos-startpos,buf);
        buf[endpos-startpos]='\0';
        return(0);
}

int
redata_line_getsize(redata_t *redata, int line) /* includes the \n */
{
        long startpos,endpos;
        int coldone;
        if(redata==NULL || line<0)
                return(-1); /* sanity check error */
        if(redata_linecol2pos(redata,line,0,&startpos,&coldone)!=0
          || redata_line_realend(redata,startpos,&endpos)!=0) {
                return(-1); /* line not found */
        }
        return(endpos-startpos);
}

int
redata_line_getstartstr(redata_t *redata, int line, char *buf, int sizebuf)
{
        return(redata_line_getstartstrtrimmed(redata,line,buf,sizebuf,NULL));
}

int
redata_line_getstartstrtrimmed(redata_t *redata, int line, char *buf, int sizebuf, char *trimchars)
{
        long startpos,endpos;
        int coldone;
        if(redata==NULL || line<0 || buf==NULL || sizebuf<1)
                return(-1); /* sanity check error */
        if(redata_linecol2pos(redata,line,0,&startpos,&coldone)!=0
          || redata_line_realend(redata,startpos,&endpos)!=0) {
                return(-1); /* line not found */
        }
        if(endpos>startpos && redata_getchar(redata,endpos-1)=='\n')
                endpos--;
        if(trimchars!=NULL) {
                while(startpos<endpos && strchr(trimchars,redata_getchar(redata,startpos))!=NULL)
                        startpos++;
        }
        if((endpos-startpos)>(sizebuf-1))
                endpos=startpos+(sizebuf-1);
        redata_getdata(redata,startpos,endpos-startpos,buf);
        buf[endpos-startpos]='\0';
        return(0);
}

static char *
securesave_genname(char *filename, char *buf, int bufsize)
{
        static char pre[]={SECURESAVEPREFIX};
        static char post[]={SECURESAVEPOSTFIX};
        return(redata_generic_genname(filename,pre,post,buf,bufsize));
}

static void *
mymemrchr(const void *s, int c, size_t n)
{
        long i;
        void *res=NULL;
        unsigned char b;
        b=(*((unsigned int *)(&c)))&0xff;
        for(i=0;i<n;i++) {
                if(((unsigned char *)s)[i]==b)
                        res=(((unsigned char *)s)+i);
        }
        return(res);
}

static void
meminvert(void *start, void *end)
{
        unsigned char *a=(unsigned char *)start;
        unsigned char *b=(unsigned char *)end;
        unsigned char t;
        for(b=b-1;a<b;a++,b--) {
                t=*a;
                *a=*b;
                *b=t;
        }
}

static size_t
memrchroffset(char *ptr, int c, size_t n)
{
        size_t i;
        for(i=n-1;i>=0;i--) {
                if(((unsigned char *)ptr)[i]==c)
                        return(i);
        }
        return(-1);
}