/*
 * 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 "recenteditor_data.h"

#define CHUNKSIZE 32768


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));
        redata->sizechunks=0;
        redata->chunks=NULL;
        redata->chunkdatasize=CHUNKSIZE;
        redata->available=0;
        return(redata);
}

void
redata_free(redata_t *redata)
{
        int i;
        if(redata==NULL)
                return; /* nothing to do */
        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;
        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 */
        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);
        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_load(redata_t *redata, char *filename)
{
        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 */
        }
        if(redata_preallocate(redata,statbuf.st_size)) {
                if(fd!=-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) {
                        if(fd!=-1)
                                close(fd),fd=-1;
                        fprintf(stderr,"redata_load: INTERNAL ERROR\n");
                        return(-2); /* internal error */
                }
                chunk=redata->chunks[chunkno];
                avail=redata->chunkdatasize-chunk->useddata;
                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) {
                        if(fd!=-1)
                                close(fd),fd=-1;
                        return(-1); /* short read */
                }
                chunk->useddata+=nread;
                redata->available-=nread;
                redata_fill_whatin(redata,chunkno);
        }
        return(0);
}

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