/*****************************************************************************

				RBFILE.C
				Buffered I/O routines.

Copyright (C) 2002 Greg Kochanski, gpk@pribble.com .

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*****************************************************************************/
#include "rbfile.h"
#include "gpk_std.h"
#include <stdlib.h>
/* #include <stdio.h> */
/* #include <assert.h> */
#define assert(x) /**/


#define BS 32768

void rbfile::remove_buf()
{
 assert(eof==0 || end<=eof);
 delete [] buf;
 eof = endalloc = end = ptr = buf = 0;
 delayed_remove = FALSE;
 fg = &rbfile::fgetc_direct;
}


void rbfile::commit()
{
 assert(eof==0 || end<=eof);
 if(buf && ptr==end)
	remove_buf();
 else
	delayed_remove = TRUE;
}


void rbfile::rewind()
{
 assert(eof==0 || end<=eof);
 if(buf)	{
 	ptr = buf;

	if(ptr == eof)
		fg = &rbfile::fgetc_eof;
	else if(ptr == end)
		fg = &rbfile::fgetc_end;
	else
		fg = &rbfile::fgetc_buf;
	}
 else	{
	fseek(fp, 0L, 0);
	fg = &rbfile::fgetc_direct;
	}
 assert(eof==0 || end<=eof);
}


rbfile::rbfile(FILE *fp_)
: buf(new char[BS]), ptr(buf), end(buf), endalloc(buf+BS),
	eof(0), fp(fp_), delayed_remove(FALSE), fg(&rbfile::fgetc_end)
{
	assert(buf);
	/* ptr = end = buf;
	endalloc = buf+BS; */
	assert(eof==0 || end<=eof);
}


long rbfile::tell() C
{
 if(buf)
	return ptr-buf;

 return ::ftell(fp);
}


#define PR(x,s)	(x)




void rbfile::grow(int n)
{
 assert(eof==0 || end<=eof);
 assert(buf);
 if(endalloc-ptr < n)	{
	char *C obuf = buf;
	C int sz = (endalloc-buf) + (endalloc-buf)/2 + n + BS;
	buf = (char *)realloc(buf, sz);
	assert(buf);
	ptr = buf + (ptr-obuf);
	end = buf + (end-obuf);
	if(eof)
		eof = buf + (eof-obuf);
	endalloc = buf + sz;
	}
}


char *rbfile::fgets(char *b, int sz)
{
 assert(b && sz>0);
 char *C b0 = b;
 char *C e = b + sz - 1;
 int c;
 while(b<e && (c = fgetc())!=EOF)	{
	*b++ = c;
	if(c == '\n' || c=='\0')
		break;
	}

 *b = '\0';

 return b0;
}



int rbfile::fread(void *pv, size_t sz, int n)
{
 char *p0 = (char *)pv;
 if(!buf)
	return ::fread(p0, sz, n, fp);

 if(delayed_remove && ptr==end)	{
	remove_buf();
	return ::fread(p0, sz, n, fp);
	}

 char *p = p0;
 C char *pe = p0 + sz * n;
 int c;
 while(p<pe && (c = fgetc()) != EOF)
	*(p++) = c;

 return (p-p0)/sz;
}



int rbfile::ungetc(int c)
{
 assert(eof==0 || end<=eof);
 if(buf)	{
	if(ptr>buf && c==ptr[-1])	{
		PR(c, "U");
		ptr--;
		fg = &rbfile::fgetc_buf;
		return 0;
		}
	else
		return EOF;
	}

 PR(c, "u");
 return ::ungetc(c, fp);
}


int rbfile::fgetc_eof()
{
 assert(buf);
 assert(ptr == eof);
 assert(ptr == end);
 assert(eof==0 || end<=eof);
 PR('!', "E");
 return EOF;
}


int rbfile::fgetc_direct()
{
 assert(!buf);
 assert(eof==0 || end<=eof);
 return PR(getc(fp), "D");
}


int rbfile::fgetc_end()
{
 assert(buf);
 assert(ptr == end);
 assert(ptr != eof);
 assert(eof==0 || end<=eof);
 C int c = getc(fp);
 if(c == EOF)	{
	eof = end = ptr;
	fg = &rbfile::fgetc_eof;
	}
 else	{
	if(end >= endalloc)
		grow(1);
	*(ptr++) = c;
	end = ptr;
	}

 /* assert(tell() == ::ftell(fp)); */
 return PR(c, "z");
}



int rbfile::fgetc_buf()
{
 C int rv = 0377&(*ptr++);

 assert(buf);
 assert(ptr <= end);
 assert(ptr <= eof);
 assert(eof==0 || end<=eof);
 if(ptr == end)	{
	if(delayed_remove)
		remove_buf();
	else if(ptr == eof)
		fg = &rbfile::fgetc_eof;
	else
		fg = &rbfile::fgetc_end;
	}

 return PR(rv, "");
}



int rbfile::feof() C
{
 if(fg == &rbfile::fgetc_eof)
	return 1;
 else if(fg == &rbfile::fgetc_direct)
	return ::_stdio_feof(fp);
 else if(fg == &rbfile::fgetc_buf)
	return 0;
 /* fgetc_end */
 return 0;
}
