/* SDB - relation file I/O routines */

#include <stdio.h>
#include <ctype.h>
#include "sdbio.h"

/* global error code variable */
int dbv_errcode;

/* list of currently loaded relation definitions */
static struct relation *relations = NULL;

/* rfind - find the specified relation */
static struct relation *rfind(rname)
  char *rname;
{
    int fd;
    char filename[RNSIZE+5];
    struct relation *rptr;

    /* look for relation in list currently loaded */
    for (rptr = relations; rptr != NULL; rptr = rptr->rl_next)
	if (db_sncmp(rname,rptr->rl_name,RNSIZE) == 0)
	    return (rptr);

    /* create a file name */
    make_fname(filename,rname);

    /* lookup the relation file */
    if ((fd = open(filename,0)) == -1)
	return (db_nerror(RELFNF));

    /* allocate a new relation structure */
    if ((rptr = malloc(sizeof(struct relation))) == NULL) {
    	close(fd);
    	return (db_nerror(INSMEM));
    }

    /* initialize the relation structure */
    rptr->rl_scnref = 0;

    /* read the header block */
    if (read(fd,&rptr->rl_header,512) != 512) {
    	free(rptr);
    	close(fd);
    	return (db_nerror(BADHDR));
    }

    /* close the relation file */
    close(fd);

    /* extract header information */
    rptr->rl_tcnt = db_cvword(rptr->rl_header.hd_tcnt);
    rptr->rl_tmax = db_cvword(rptr->rl_header.hd_tmax);
    rptr->rl_data = db_cvword(rptr->rl_header.hd_data);
    rptr->rl_size = db_cvword(rptr->rl_header.hd_size);

    /* store the relation name */
    strncpy(rptr->rl_name,rname,RNSIZE);

    /* link new relation into relation list */
    rptr->rl_next = relations;
    relations = rptr;

    /* return the new relation structure pointer */
    return (rptr);
}

/* db_ropen - open a relation file */
struct scan *db_ropen(rname)
  char *rname;
{
    struct relation *rptr;
    struct scan *sptr;
    char filename[RNSIZE+5];

    /* find the relation definition */
    if ((rptr = rfind(rname)) == NULL)
    	return (NULL);

    /* allocate a new scan structure */
    if ((sptr = malloc(sizeof(struct scan))) == NULL)
    	return (db_nerror(INSMEM));

    /* allocate a tuple buffer */
    if ((sptr->sc_tuple = malloc(rptr->rl_size)) == NULL) {
    	free(sptr);
    	return (db_nerror(INSMEM));
    }

    /* initialize the scan structure */
    sptr->sc_relation = rptr;		/* store the relation struct addrs */
    sptr->sc_dtnum = 0;			/* desired tuple (non-existant) */
    sptr->sc_atnum = 0;			/* actual tuple (non-existant) */
    sptr->sc_store = FALSE;		/* no store done since open */

    /* open relation file if necessary */
    if (rptr->rl_scnref++ == 0) {

    	/* create the relation file name */
    	make_fname(filename,rname);

    	/* open the relation file */
    	if ((rptr->rl_fd = open(filename,2)) == -1) {
    	    rptr->rl_scnref--;
    	    free(sptr->sc_tuple); free(sptr);
    	    return (db_nerror(RELFNF));
    	}
    }

    /* return the new scan structure pointer */
    return (sptr);
}

/* db_rclose - close the relation file */
int db_rclose(sptr)
  struct scan *sptr;
{
    struct relation *rptr,*lastrptr;

    /* close relation file if this is the last reference */
    if (--sptr->sc_relation->rl_scnref == 0) {

    	/* rewrite header if any stores took place */
    	if (sptr->sc_store) {

    	    /* store the tuple count back in the header */
    	    db_cvbytes(sptr->sc_relation->rl_tcnt,
		       sptr->sc_relation->rl_header.hd_tcnt);

    	    /* write the header block */
    	    lseek(sptr->sc_relation->rl_fd,0L,0);
    	    if (write(sptr->sc_relation->rl_fd,
    		      &sptr->sc_relation->rl_header,512) != 512) {
    		close(sptr->sc_relation->rl_fd);
    		free(sptr->sc_tuple); free(sptr);
    		return (db_ferror(BADHDR));
    	    }
    	}

    	/* close the relation file */
    	close(sptr->sc_relation->rl_fd);

    	/* free the relation header */
    	lastrptr = NULL;
    	for (rptr = relations; rptr != NULL; rptr = rptr->rl_next) {
    	    if (rptr == sptr->sc_relation) {
    		if (lastrptr == NULL)
    		    relations = rptr->rl_next;
    		else
    		    lastrptr->rl_next = rptr->rl_next;
 	    }
    	    lastrptr = rptr;
    	}
    	free(sptr->sc_relation);
    }

    /* free the scan structure */
    free(sptr->sc_tuple); free(sptr);

    /* return successfully */
    return (TRUE);
}

/* db_rcompress - compress a relation file */
int db_rcompress(sptr)
  struct scan *sptr;
{
    unsigned int next,nextfree,tcnt;

    /* get the last used tuple */
    tcnt = sptr->sc_relation->rl_tcnt;

    /* loop through all of the tuples */
    for (next = nextfree = 1; next <= tcnt; next++) {

    	/* read the tuple */
    	seek(sptr,next);
    	if (read(sptr->sc_relation->rl_fd,
    		 sptr->sc_tuple,sptr->sc_relation->rl_size)
    			!= sptr->sc_relation->rl_size)
    	    return (db_ferror(TUPINP));

	/* rewrite the tuple if it is active */
    	if (sptr->sc_tuple[0] == ACTIVE) {

	    /* rewrite it only if it must move */
	    if (next != nextfree) {

		/* write the tuple */
		seek(sptr,nextfree);
		if (write(sptr->sc_relation->rl_fd,
			  sptr->sc_tuple,sptr->sc_relation->rl_size)
				!= sptr->sc_relation->rl_size)
		    return (db_ferror(TUPOUT));
	    }

	    /* update the next free tuple number */
	    nextfree += 1;
	}
    }

    /* update the tuple count */
    sptr->sc_relation->rl_tcnt = nextfree - 1;

    /* remember which tuple is in the buffer */
    sptr->sc_atnum = sptr->sc_relation->rl_tcnt;

    /* reset the desired tuple */
    sptr->sc_dtnum = 0;

    /* remember that the index needs rewriting */
    sptr->sc_store = TRUE;

    /* return successfully */
    return (TRUE);
}

/* db_rbegin - begin scan at first tuple in relation */
db_rbegin(sptr)
  struct scan *sptr;
{
    /* begin with the first tuple in the file */
    sptr->sc_dtnum = 0;
}

/* db_rfetch - fetch the next tuple from the relation file */
int db_rfetch(sptr)
  struct scan *sptr;
{
    /* look for an active tuple */
    while (TRUE) {

	/* get the tuple if its not already in the buffer */
	if (sptr->sc_dtnum+1 != sptr->sc_atnum)
	    if (!db_rget(sptr,sptr->sc_dtnum+1,sptr->sc_tuple))
		return (FALSE);

    	/* increment the tuple number */
    	sptr->sc_dtnum += 1;

	/* remember which tuple is in the buffer */
	sptr->sc_atnum = sptr->sc_dtnum;

    	/* return if the tuple found is active */
    	if (sptr->sc_tuple[0] == ACTIVE)
    	    return (TRUE);
    }
}

/* db_rupdate - update the current tuple */
int db_rupdate(sptr)
  struct scan *sptr;
{
    /* make sure the status byte indicates an active tuple */
    sptr->sc_tuple[0] = ACTIVE;

    /* write the tuple */
    return (db_rput(sptr,sptr->sc_atnum,sptr->sc_tuple));
}

/* db_rdelete - delete the current tuple */
int db_rdelete(sptr)
  struct scan *sptr;
{
    /* make sure the status byte indicates a deleted tuple */
    sptr->sc_tuple[0] = DELETED;

    /* write the tuple */
    return (db_rput(sptr,sptr->sc_atnum,sptr->sc_tuple));
}

/* db_rstore - store a new tuple */
int db_rstore(sptr)
  struct scan *sptr;
{
    /* make sure there's room for this tuple */
    if (sptr->sc_relation->rl_tcnt == sptr->sc_relation->rl_tmax)
	return (db_ferror(RELFUL));

    /* make sure the status byte indicates an active tuple */
    sptr->sc_tuple[0] = ACTIVE;

    /* write the tuple */
    if (!db_rput(sptr,sptr->sc_relation->rl_tcnt + 1,sptr->sc_tuple))
    	return (FALSE);

    /* update the tuple count */
    sptr->sc_relation->rl_tcnt += 1;

    /* remember which tuple is in the buffer */
    sptr->sc_atnum = sptr->sc_relation->rl_tcnt;

    /* remember that a tuple was stored */
    sptr->sc_store = TRUE;

    /* return successfully */
    return (TRUE);
}

/* db_rget - get a tuple from the relation file */
int db_rget(sptr,tnum,buf)
  struct scan *sptr; unsigned int tnum; char *buf;
{
    /* check for this being beyond the last tuple */
    if (tnum > sptr->sc_relation->rl_tcnt)
    	return (db_ferror(TUPINP));

    /* read the tuple */
    seek(sptr,tnum);
    if (read(sptr->sc_relation->rl_fd,buf,sptr->sc_relation->rl_size)
    		!= sptr->sc_relation->rl_size)
    	return (db_ferror(TUPINP));

    /* return successfully */
    return (TRUE);
}

/* db_rput - put a tuple to a relation file */
int db_rput(sptr,tnum,buf)
  struct scan *sptr; unsigned int tnum; char *buf;
{
    /* check for this being beyond the maximum tuple */
    if (tnum > sptr->sc_relation->rl_tmax)
    	return (db_ferror(TUPOUT));

    /* write the tuple */
    seek(sptr,tnum);
    if (write(sptr->sc_relation->rl_fd,buf,sptr->sc_relation->rl_size)
		!= sptr->sc_relation->rl_size)
	return (db_ferror(TUPOUT));

    /* return successfully */
    return (TRUE);
}

/* seek - seek a tuple in a relation file */
static seek(sptr,tnum)
  struct scan *sptr; unsigned int tnum;
{
    long offset;

    offset = (long) sptr->sc_relation->rl_data +
		((long) (tnum - 1) * (long) sptr->sc_relation->rl_size);
    lseek(sptr->sc_relation->rl_fd,offset,0);
}

/* make_fname - make a relation name into a file name */
static make_fname(fname,rname)
  char *fname,*rname;
{
    strncpy(fname,rname,RNSIZE); fname[RNSIZE] = 0;
    strcat(fname,".sdb");
}

/* db_nerror - store the error code and return NULL */
int db_nerror(errcode)
  int errcode;
{
    dbv_errcode = errcode;
    return (NULL);
}

/* db_ferror - store the error code and return FALSE */
int db_ferror(errcode)
  int errcode;
{
    dbv_errcode = errcode;
    return (FALSE);
}

/* db_cvword - convert 2 bytes to a word */
int db_cvword(bytes)
  char bytes[2];
{
    return (((bytes[1] & 0377) << 8) + (bytes[0] & 0377));
}

/* db_cvbytes - convert a word to 2 bytes */
db_cvbytes(word,bytes)
  int word; char bytes[2];
{
    bytes[0] = word;
    bytes[1] = word >> 8;
}
