/*
 * recenteditor_data.c
 *
 * A programmers editor
 *
 * Structures to hold the current file contents.
 *
 * Author: Dario Rodriguez dario@softhome.net
 * This program is licensed under the terms of GNU GPL v2.1+
 */

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

#include "recenteditor_data.h"
#include "sha3/sha3.h"

#define CHUNKSIZE 32768
#define UNDOBLOCK 1024
#define UNDOGROWSIZE (256*1024)
#define UNSAVEDPREFIX "."
#define UNSAVEDPOSTFIX ".reu"
#define SECURESAVEPREFIX "."
#define SECURESAVEPOSTFIX ".saving"
#define UNSAVEDHEADER "REUNSAV\n"
#define UNSAVEDVERSION "\n000001\0"

static int redata_hash_gen(redata_t *redata, char *filename, char *buf, long buflen, char *resbuf129bytes);
static char *unsaved_genname(char *filename, char *buf, int bufsize);
static char *securesave_genname(char *filename, char *buf, int bufsize);
static char *genname(char *filename,char *prefix, char *postfix, char *buf, int bufsize);

redata_t *
redata_init(void)
{
        redata_t *redata;
        if((redata=malloc(sizeof(redata_t)))==NULL)
                return(NULL); /* sanity check failed */
        memset(redata,0,sizeof(redata_t));
        /* 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;
        /* unsaved */
        redata->filename[0]='\0';
        redata->unsavedfd=-1;
        redata->flag_unsaveddata=0;
        /* all done */
        return(redata);
}

void
redata_free(redata_t *redata)
{
        int i;
        char unsname[PATH_MAX];
        if(redata==NULL)
                return; /* nothing to do */
        /* 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;
        /* unsaved */
        if(redata->unsavedfd!=-1) {
                close(redata->unsavedfd),redata->unsavedfd=-1;
                if(unsaved_genname(redata->filename,unsname,sizeof(unsname))!=NULL)
                        unlink(unsname);
        }
        redata->flag_unsaveddata=0;
        /* free main struct */
        free(redata),redata=NULL;
        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, long *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_wipe(redata_t *redata)
{
        int i;
        char unsname[PATH_MAX];
        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);
        /* unsaved */
        if(redata->unsavedfd!=-1) {
                close(redata->unsavedfd),redata->unsavedfd=-1;
                if(unsaved_genname(redata->filename,unsname,sizeof(unsname))!=NULL)
                        unlink(unsname);
        }
        redata->flag_unsaveddata=0;
        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_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;
                from->whatin_fresh=0;
                to->whatin_fresh=0;
        } else {
                /* from==to */
                rechunk_t *chunk=redata->chunks[chunkfrom];
                memcpy(redata->tmpchunk->data,chunk->data+posfrom,size);
                if(posto>posfrom) {
                        int inside=(posto-posfrom)-size;
                        memmove(chunk->data+posfrom,chunk->data+posto-inside,inside);
                        memcpy(chunk->data+posfrom+inside,redata->tmpchunk->data,size);
                } else {
                        int inside=(posto-posfrom);
                        memmove(chunk->data+posfrom+size-inside,chunk->data+posto,inside);
                        memcpy(chunk->data+posto,redata->tmpchunk->data,size);
                }
                chunk->whatin_fresh=0;
        }
        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;
        chunk->whatin_fresh=0;
        return(0);
}

int
redata_chunk_deletedata(redata_t *redata, int chunkno, long pos, long n)
{
        rechunk_t *chunk;
        if(redata==NULL || n<0
          || chunk<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;
        chunk->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;
        chunk->whatin_fresh=1;
        return(0);
}

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);
}

int
redata_fix_nl(redata_t *redata, int chunkno)
{
        /* 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) */
        rechunk_t *chunk,*nextchunk;
        int linesize, nextlinesize, avail, nextavail;
        unsigned char *ptr,*nextptr;
        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_unsaved_exists(redata_t *redata, char *filename)
{
        char unsname[PATH_MAX+1];
        int fd;
        if(redata==NULL || filename==NULL)
                return(-1); /* sanity check failed */
        if((unsaved_genname(filename,unsname,sizeof(unsname)))==NULL)
                return(-1); /* malformed filename */
        if((fd=open(unsname,O_RDONLY))==-1)
                return(-1);
        close(fd),fd=-1;
        return(0);
}

int
redata_unsaved_check(redata_t *redata, char *filename)
{
        char unsname[PATH_MAX+1];
        int fd,nread;
        static char header[9]={UNSAVEDHEADER};
        static char version[9]={UNSAVEDVERSION};
        char filehash[129],undohash[129],buf[64];
        if(redata==NULL || filename==NULL)
                return(-1); /* sanity check failed */
        if((unsaved_genname(filename,unsname,sizeof(unsname)))==NULL)
                return(-1); /* malformed filename */
        if(redata_filehash(redata,filename,filehash)!=0)
                return(-1);
        if((fd=open(unsname,O_RDONLY))==-1)
                return(-1);
        if((nread=read(fd,buf,8))==-1 || nread!=8 || memcmp(buf,header,8)!=0) {
                close(fd),fd=-1;
                return(-1); /* corrupted header */
        }
        if((nread=read(fd,undohash,128))==-1 || nread!=128 || memcmp(undohash,filehash,128)!=0) {
                close(fd),fd=-1;
                return(-1); /* wrong hash */
        }
        if((nread=read(fd,buf,8))==-1 || nread!=8 || memcmp(buf,version,8)!=0) {
                close(fd),fd=-1;
                return(-1); /* wrong version */
        }
        close(fd),fd=-1;
        return(0);
}


int
redata_unsaved_unlink(redata_t *redata)
{
        char unsname[PATH_MAX+1];
        if(redata==NULL || redata->filename[0]=='\0')
                return(-1); /* sanity check failed */
        if(redata_unsaved_exists(redata,redata->filename)==-1)
                return(0); /* file not found, nothing to unlink */
        if((unsaved_genname(redata->filename,unsname,sizeof(unsname)))==NULL)
                return(-1); /* malformed filename */
        unlink(unsname);
        return(0);
}



int
redata_unsaved_trunc(redata_t *redata)
{
        char unsname[PATH_MAX+1];
        static char header[9]={UNSAVEDHEADER};
        static char version[9]={UNSAVEDVERSION};
        int n;
        if(redata==NULL || redata->filename[0]=='\0')
                return(-1); /* sanity check failed */
        redata_unsaved_unlink(redata);
        if((unsaved_genname(redata->filename,unsname,sizeof(unsname)))==NULL)
                return(-1); /* malformed filename */
        if((redata->unsavedfd=open(unsname,O_WRONLY|O_TRUNC|O_CREAT,0644))==-1)
                return(-1); /* couldn't open file for writing */
        if((n=write(redata->unsavedfd,header,8))==-1 || n!=8 ||
          (n=write(redata->unsavedfd,redata->initialhash,128))==-1 || n!=128 ||
          (n=write(redata->unsavedfd,version,8))==-1 || n!=8) {
                close(redata->unsavedfd),redata->unsavedfd=-1;
                unlink(unsname);
                return(-1); /* couldn't write header/hash/version */
        }
        return(0);
}

int
redata_unsaved_loadappend(redata_t *redata)
{
#warning TODO
        return(-1);
}


int
redata_unsaved_add(redata_t *redata, undo_t *undo)
{
#warning TODO
        return(-1);
}

int
redata_unsaved_unadd(redata_t *redata, undo_t *undo)
{
#warning TODO
        return(-1);
}

int
redata_load(redata_t *redata, char *filename, int use_unsaved)
{
        int fd,nread,totalread;
        int chunkno, avail;
        struct stat statbuf;
        rechunk_t *chunk;
        if(redata==NULL || filename==NULL)
                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;
                return(-1); /* file not found, couldn't query size or not regular file */
        }
        /* preallocate 10% more than needed */
        if(redata_preallocate(redata,statbuf.st_size+(statbuf.st_size/10)+1 )) {
                close(fd),fd=-1;
                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;
                        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 */
        redata_hash(redata,redata->initialhash);
        if(use_unsaved) {
                /* apply missing changes and append new changes to unsaved */
                redata_unsaved_loadappend(redata);
        } else {
                /* nuke existing unsaved (if exists) and prepare new unsaved */
                redata_unsaved_trunc(redata);
        }
        /* all done */
        return(0);
}

int
redata_save(redata_t *redata, char *filename)
{
        int fd;
        int i,n;
        char tmpfile[PATH_MAX+1];
        if(redata==NULL || filename==NULL)
                return(-1); /* sanity check failed */
        if((securesave_genname(redata->filename,tmpfile,sizeof(tmpfile)))==NULL)
                return(-1); /* malformed filename */
        if((fd=open(tmpfile,O_WRONLY|O_TRUNC|O_CREAT,0644))==-1)
                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);
                        return(-1); /* short write */
                }
        }
        close(fd),fd=-1;
        if(rename(tmpfile,filename)!=0) {
                unlink(tmpfile);
                return(-1); /* couldn't overwrite old file */
        }
        redata_unsaved_unlink(redata);
        redata->flag_unsaveddata=0;
        redata_hash(redata,redata->initialhash);
        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 || type==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++;
        strncpy(undo->type,type,sizeof(undo->type));
        undo->type[sizeof(undo->type)-1]='\0';
        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 startpos,endpos;
        long startoff,endoff;
        undo_t *undo;
        int k;
        long used;
        long copyfrom,copysize;
        if(redata==NULL || type==NULL || len<=0
          || (stack!=&(redata->undostack) && stack!=&(redata->redostack)))
                return(NULL); /* sanity check failed */
        if(redata_getposptr(redata,pos1,&startpos,&startoff)==-1 ||
          redata_getposptr(redata,pos1+len,&endpos,&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=startpos,used=0;k<=endpos;k++) {
                if(k==startpos && k==endpos) {
                        copyfrom=startoff;
                        copysize=endoff-startoff;
                } else if(k==startpos) {
                        copyfrom=startoff;
                        copysize=redata->chunks[k]->useddata-startoff;
                } else if(k==endpos) {
                        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 || type==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;
        memcpy(undoto->prehash,undofrom->prehash,sizeof(undoto->prehash));
        memcpy(undoto->posthash,undofrom->posthash,sizeof(undoto->posthash));
        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_op_add(redata_t *redata, int insertpos, char *buf, int buflen, undostack_t *fromhere)
{
        int chunkno;
        long pos;
        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(redata_getposptr(redata,insertpos,&chunkno,&pos)==-1)
                return(-1); /* invalid pos */
        if(fromhere!=&(redata->undostack)) {
                if((undo=redata_undo_newfrombuf(redata,&(redata->undostack),"ADD",buf,buflen))==NULL)
                        return(-1); /* couldn't create undo struct */
                redata_hash(redata,undo->prehash);
        } 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,pos,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-pos;
                bothering=(bothering>nextavail)?nextavail:bothering;
                redata_chunk_movedata(redata,chunkno,chunk->useddata-bothering,chunkno+1,0,bothering);
                redata_chunk_insertdata(redata,chunkno,pos,buf,buflen);
        } else {
                /* will need to add more chunks */
                needed=(buflen+redata->chunkdatasize-1)/redata->chunkdatasize;
                needed*=redata->chunkdatasize;
                needed+=(chunk->useddata-pos);
                if(redata_preallocate(redata,redata_getsize(redata)+needed)!=0) {
                        if(undo!=NULL) {
                                redata->undostack.usedbuf-=buflen;
                                memset(undo,0,sizeof(undo_t));
                        }
                        return(-1); /* insuf. mem. */
                }
                redata_chunk_insertnew(redata,chunkno);
                redata_chunk_movedata(redata,chunkno,pos,chunkno+1,0,chunk->useddata-pos);
                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;
                redata_hash(redata,undo->posthash);
                redata->undostack.usedundo++;
                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));
        }
        return(0);
}

int
redata_op_del(redata_t *redata, int pos, int size, undostack_t *fromhere)
{
#warning TODO
        return(-1);
}

int
redata_op_move(redata_t *redata, int posorig, int size, int posdest, undostack_t *fromhere)
{
#warning TODO
        return(-1);
}

int
redata_op_undo(redata_t *redata)
{
        undo_t *undo;
        if(redata==NULL
          || redata->undostack.usedundo<1)
                return(-1); /* sanity check failed */
        undo=redata->undostack.undo+redata->undostack.usedundo-1;
        if(strcmp(undo->type,"ADD")==0) {
                redata_op_del(redata,undo->posorig,undo->len,&(redata->undostack));
        } else if(strcmp(undo->type,"DEL")==0) {
                redata_op_add(redata,undo->posorig,redata->undostack.buf+undo->off,undo->len,&(redata->undostack));
        } else if(strcmp(undo->type,"MOV")==0) {
                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 */
        return(-1);
}

int
redata_op_redo(redata_t *redata)
{
        undo_t *undo;
        if(redata==NULL
          || redata->redostack.usedundo<1)
                return(-1); /* sanity check failed */
        undo=redata->redostack.undo+redata->redostack.usedundo-1;
        if(strcmp(undo->type,"ADD")==0) {
                redata_op_add(redata,undo->posorig,redata->redostack.buf+undo->off,undo->len,&(redata->redostack));
        } else if(strcmp(undo->type,"DEL")==0) {
                redata_op_del(redata,undo->posorig,undo->len,&(redata->redostack));
        } else if(strcmp(undo->type,"MOV")==0) {
                redata_op_move(redata,undo->posorig, undo->len, undo->posdest,&(redata->redostack));
        } else
                return(-1); /* unknown operation */
        return(-1);
}

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));
}

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);
}


static char *
unsaved_genname(char *filename, char *buf, int bufsize)
{
        static char pre[]={UNSAVEDPREFIX};
        static char post[]={UNSAVEDPOSTFIX};
        return(genname(filename,pre,post,buf,bufsize));
}

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


static char *
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);
}