/*
 * 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 UNSAVEDPREFIX "."
#define UNSAVEDPOSTFIX ".reu"


static int redata_hash_gen(redata_t *redata, char *filename, char *resbuf129bytes);
static char *unsaved_genname(char *filename, 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->sizeundo=0;
        redata->usedundo=0;
        redata->curundo=0;
        redata->undo=NULL;
        redata->undobuf=NULL;
        /* unsaved */
        redata->unsavedfd=-1;
        redata->flag_unsaveddata=0;
        /* all done */
        return(redata);
}

void
redata_free(redata_t *redata)
{
        int i;
        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;
        /* undo */
        if(redata->undo!=NULL)
                free(redata->undo),redata->undo=NULL;
        if(redata->undobuf!=NULL)
                free(redata->undobuf),redata->undobuf=NULL;
        /* unsaved */
        if(redata->unsavedfd!=-1)
                close(redata->unsavedfd),redata->unsavedfd=-1;
        if(redata->unsavedfilename!=NULL) {
                if(redata->unsavedfilename[0]!='\0') {
                        /* remove the file, if here, the user has validated exiting without saving */
                        unlink(redata->unsavedfilename);
                }
                free(redata->unsavedfilename),redata->unsavedfilename=NULL;
        }
        /* free main struct */
        free(redata),redata=NULL;
        return;
}

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

int
redata_getused(redata_t *redata)
{
        if(redata==NULL)
                return(0); /* sanity check failed */
        return(redata_getsize(redata)-redata_getavailable(redata));
}

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

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);
        /* unsaved */
        if(redata->unsavedfd!=-1)
                close(redata->unsavedfd),redata->unsavedfd=-1;
        if(redata->unsavedfilename!=NULL) {
                if(redata->unsavedfilename[0]!='\0') {
                        /* remove the file, if here, the user has validated exiting without saving */
                        unlink(redata->unsavedfilename);
                }
                free(redata->unsavedfilename),redata->unsavedfilename=NULL;
        }
        redata->flag_unsaveddata=0;
        /* all done */
        return(0);
}

int
redata_preallocate(redata_t *redata, int newsize)
{
        int cursize;
        int nchunks;
        int i;
        int rechunksize;
        rechunk_t **newchunks,*chunk;
        int oldsizechunks;
        if(redata==NULL || redata->chunkdatasize==0 || newsize<0)
                return(-1); /* sanity check failed */
        if((cursize=redata_getsize(redata))>=newsize)
                return(0); /* all done */
        nchunks=(newsize-cursize+redata->chunkdatasize-1)/redata->chunkdatasize;
        rechunksize=sizeof(rechunk_t)-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_fill_whatin(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];
        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;
        return(0);
}

int
redata_unsaved_exists(redata_t *redata, char *filename)
{
        char unsname[PATH_MAX+1];
        int fd;
        if((unsaved_genname(filename,unsname,sizeof(unsname)))==NULL)
                return(-1);
        if((fd=open(unsname,O_RDONLY))==-1)
                return(-1);
        close(fd),fd=-1;
        return(0);
}

int
redata_unsaved_check(redata_t *redata, char *filename)
{

#warning TODO
        return(-1);
}

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

int
redata_unsaved_trunc(redata_t *redata, char *filename)
{
#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)
{
#warning TODO: use_unsaved (apply unsaved changes after load)
        int fd,nread,totalread;
        int chunkno, avail;
        struct stat statbuf;
        rechunk_t *chunk;
        redata_wipe(redata);
        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->sizechunks+1)*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;
                redata_fill_whatin(redata,chunkno);
        }
        close(fd),fd=-1;
        /* unsaved */
        if(use_unsaved) {
                /* apply missing changes and append new changes to unsaved */
                redata_unsaved_loadappend(redata,filename);
        } else {
                /* nuke existing unsaved (if exists) and prepare new unsaved */
                redata_unsaved_trunc(redata,filename);
        }
        /* all done */
        return(0);
}

int
redata_save(redata_t *redata, char *filename)
{
#warning TODO
        return(-1);
}

int
redata_op_add(redata_t *redata, char *buf, int sizebuf, int pos)
{
#warning TODO
        return(-1);
}

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

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

int
redata_hash(redata_t *redata, char *resbuf129bytes)
{
        return(redata_hash_gen(redata,NULL,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(redata,filename,resbuf129bytes));
}


static int
redata_hash_gen(redata_t *redata, char *filename, char *resbuf129bytes)
{
        static char conv[]={"0123456789ABCDEF"};
        sha3_context sha3;
        unsigned char *hash;
        int i,c;
        int fd=-1;
        struct stat statbuf;
        if(resbuf129bytes!=NULL)
                *resbuf129bytes='\0';
        if(redata==NULL || resbuf129bytes==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(filename==NULL) {
                for(i=0;i<redata->sizechunks;i++)
                        sha3_Update(&sha3,(void *) redata->chunks[i]->data,redata->chunks[i]->useddata);
        } else {
                char buf[16384];
                int 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;
        }
        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)
{
        char *name,*ptr;
        int filenamelen;
        int len,off;
        static char pre[]={UNSAVEDPREFIX};
        static char post[]={UNSAVEDPOSTFIX};
        if(filename==NULL)
                return(NULL);
        filenamelen=strlen(filename);
        len=filenamelen+sizeof(pre)-1+sizeof(post)-1+1;
        if(buf==NULL) {
                if((name=malloc(len))==NULL)
                        return(NULL);
        } else {
                if(bufsize<filenamelen)
                        return(NULL);
                name=buf;
        }
        for(ptr=filename+strlen(filename);ptr>0 && ptr[-1]!='/';ptr--)
                ;
        off=0;
        memcpy(name+off,filename,ptr-filename);
        off+=ptr-filename;
        memcpy(name+off,pre,sizeof(pre)-1);
        off+=sizeof(pre)-1;
        memcpy(name+off,ptr,filenamelen-(ptr-filename));
        off+=filenamelen-(ptr-filename);
        memcpy(name+off,post,sizeof(post)-1);
        off+=sizeof(post)-1;
        name[off]='\0';
        return(name);
}