#ifdef __VBCC__
#pragma amiga-align
#endif

#include <proto/exec.h>
#include <exec/memory.h> // Used by the dynamic buffer allocation routines

#include <proto/dos.h>
#include <dos/dos.h>

#include <clib/powerpc_protos.h>
#ifdef __VBCC__
#pragma default-align
#endif


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// For atexit()

#include "bladesys.h" // Needed for MAX_FILENAME

#include "natio.h"
#include "fix_name.h" // Hack to prevent ugly filenames like "ram:/<tune>.mp3"

extern ULONG __stack_usage;

// I feel so bohemian and I like you!

/* ANSI I/O emulation library usign twin syncronous buffers for Bladeenc/WarpOS */
/* To be used EXCLUSIVELY with Bladeenc ! */
/* NOTE: The resulting object file MUST be linked *BEFORE* the standard library! */

//ULONG memallocstat=0;

static BOOL Exit_Initialized = FALSE;

static struct List OpenFiles; // List of open files.
static unsigned Local_Open_Count=0;

static LONG internal_close(const struct myFILE * closeme)
{
LONG res=0; //Fake value for now.

if(closeme->w_buf.currentcount) // Some bytes left...
	{
	//printf("in fclose(), %lu bytes left\n", closeme->w_buf.currentcount);

	Write(closeme->filepointer, closeme->w_buf.buf, closeme->w_buf.currentcount);
	}

Close(closeme->filepointer);

Local_Open_Count--;

return res;
}

static void internal_addnode(struct myFILE * f)
{
struct natio_node * n;

//printf("ia1\n");

n=AllocVecPPC(sizeof(struct natio_node), MEMF_ANY, 0);

//printf("ia2\n");

f->backtrack=(struct Node *)n;
n->f=f;

AddHeadPPC(&OpenFiles, (struct Node *)n);

}

/*
#ifdef __VBCC__
unsigned long fpscr(void) = "\tmffs\tf1\n\tstfd\tf1,-8(r1)\n\tlwz\tr3,-4(r1)";
#elif defined __GCC__
extern long fpscr(void);
#endif
*/

static void CloseAll(void)
{
struct natio_node * n;

//printf("in CloseAll()), %u\n", Local_Open_Count);

//if(Local_Open_Count)
	if(!IsListEmpty(&OpenFiles))
		for(	n=(struct natio_node *)OpenFiles.lh_Head;
			((struct Node *)n)->ln_Succ;
			n=(struct natio_node *)n->node.ln_Succ)

			{
			//printf("Closing file at %p\n", (void *)n->f->filepointer);
			internal_close(n->f);
			RemovePPC((struct Node *)n);
			FreeVecPPC(n);
			}

if(Local_Open_Count) // Should never happen.
	fprintf(stderr, "INTERNAL ERROR: %u file(s) still open!\n", Local_Open_Count);
/*
#ifdef __VBCC__
printf("Stack usage %lu\n", __stack_usage);
#endif
*/
}

static void init_exit(void)
{
if(!Exit_Initialized)
	{
	NewListPPC(&OpenFiles);
	atexit(CloseAll);
	Exit_Initialized=TRUE;
	}
}

static ULONG get_r_bufsize(const char * name, const BOOL read, const BPTR testfile)
{
static ULONG totfast, result;
static LONG flen; // To use less stack
static BOOL success;
struct FileInfoBlock * fib;

totfast = AvailMem(MEMF_FAST|MEMF_LARGEST);

if(!read)
	return W_READ_BUFSIZE;

if(testfile)
	{
	fib=AllocDosObject(DOS_FIB,NULL);
	success = Examine(testfile, fib);

	FreeDosObject(DOS_FIB, (void *)fib);

	if(success)
		{
		flen = fib->fib_Size;

		totfast >>= 2; //

		result = (ULONG)min(totfast, flen);

		result = max(result, R_BUF_THRES);

		//printf("Read: with %lu bytes free and a file of %ld bytes, I choose %lu\n",
		//	totfast<<2, flen, result);
		return result;
		}
	}

return READ_BUFSIZE; // Default

}

static ULONG get_w_bufsize(const char * name, const BOOL read)
{
//static ULONG totfast; //Same reason as above.

if(read)
	return R_WRITE_BUFSIZE;
else	return WRITE_BUFSIZE;
}

static BOOL init_buf(struct CIOBuf * const c, const ULONG req_bufsize)
{
register APTR temp;

c->buf = temp = AllocVecPPC(req_bufsize, MEMF_ANY, 64);

if(temp == NULL)
	{
	return FALSE;
	}

c->size=req_bufsize;
c->currentcount=0;
c->current=temp;
c->eof_pos=0;
//c->tot_fetched=0;

return TRUE;
}

static void destroy_buf(struct CIOBuf * const b)
{
FreeVecPPC(b->buf);

b->size=0;
b->currentcount=0;
b->buf = b->current=NULL;

//printf("tot fetched %lu\n", b->tot_fetched);
}

FILE *fopen(const char * name,const char * mode)
{
struct myFILE * result;
BOOL read;
//register ULONG bufsize;
register LONG ret;
static char realname[MAX_NAMELEN];
BPTR tempfile;

init_exit();

if(strchr(mode, (int)'r'))
	read=TRUE;
else	read=FALSE;

fixfname((char *)name, realname);
//printf("Old name:%s, new name %s\n", name, realname);

if(read)
	{
	if(!(tempfile = Lock(realname, ACCESS_READ)))
		return NULL;
	}
else if(!(tempfile = Open(realname, MODE_NEWFILE)))
	return NULL;

result = AllocVecPPC(sizeof(struct myFILE), MEMF_ANY, 0);
if(result == NULL) // Out of ammo.
	return (FILE *)0;


if(!(init_buf(&(result->r_buf), get_r_bufsize(name, read, tempfile))))
	{
	FreeVecPPC(result);
	return (FILE *)0;
	}

if(!(init_buf(&(result->w_buf), get_w_bufsize(name, read))))
	{
	destroy_buf(&(result->r_buf));
	FreeVecPPC(result);
	return (FILE *)0;
	}

if(read)
  result->filepointer=OpenFromLock(tempfile);
else result->filepointer=tempfile;

if(result->filepointer) // OK
	{
	internal_addnode(result);
	Local_Open_Count++;

	if(read)
		{ // Some read-ahead cache.
		ret = Read(result->filepointer, result->r_buf.buf, result->r_buf.size);

		//printf("Readahead %ld from file\n", ret);

		result->r_buf.eof_pos = ret;
		//printf("Setting result->r_buf.eof_pos at %lu\n", result->r_buf.eof_pos);
		}
	//printf("fopen() successful for %s (mode %d)\n", realname, read);
	return (FILE *)result;
	}
else	{
	//printf("Failed opening %s (mode %d)\n", realname, read);

	if(read)
		UnLock(tempfile);

	destroy_buf(&(result->w_buf));
	destroy_buf(&(result->r_buf));
	FreeVecPPC(result);
	return NULL;
	}
}

int fclose(FILE * closeme)
{
struct myFILE * realfile = (struct myFILE *)closeme;

#ifdef __VBCC__
extern FILE *_firstfile,*_lastfile;
#endif

//printf("Called with %p\n", closeme);

if( (closeme == stdin) || (closeme == stdout) || (closeme == stderr) )
	{
#ifdef __VBCC__ // Special black-magic stuff. Check your compiler libraries !
	if (closeme->prev)
		closeme->prev->next = closeme->next;
	else	_firstfile = closeme->next;

	if (closeme->next)
		closeme->next->prev = closeme->prev;
	else	_lastfile = closeme->prev;

	//printf("Closing %p (%p)\n", closeme, ((long)fileno(closeme))<<2);
	fflush(closeme);
//	Close(fileno(closeme));
#endif
	return 0; // Buffers aren't deallocated. Oh god...
	}

internal_close(realfile);
//printf("Backtrack is %p\n", realfile->backtrack);
RemovePPC(realfile->backtrack);
FreeVecPPC(realfile->backtrack); // Frees the node.

destroy_buf(&(realfile->w_buf));
destroy_buf(&(realfile->r_buf));
FreeVecPPC(realfile);

/* printf("The situation is: %u open files, list %s empty\n", Local_Open_Count,
	IsListEmpty(&OpenFiles) ? "is" : "isn't");
*/
return 0; // Someday this might take into account real return codes.
}

size_t fread(void * buf, size_t a, size_t b, FILE * f)
{
register LONG ret;
register ULONG availspace, totrequest=(ULONG)a * (ULONG)b;
struct myFILE * rf=(struct myFILE *)f;
char * temp_pnt;
size_t accumulated=0;

//printf("READ: %lu bytes from %p\n", totrequest, f);

if(f == stdin)
	{
	//printf("stdin\n");
	return (size_t)FRead((BPTR)fileno(f), buf, a, b);
	}

availspace = AVAILSPACE(rf->r_buf);
//printf("Availspace is %lu\n", availspace);

if(totrequest > rf->r_buf.size) // A read so big it's bigger than our read buffer
	{
	//printf("Very big\n");
	for(	temp_pnt=buf;
		totrequest > rf->r_buf.size;
		temp_pnt+=rf->r_buf.size , totrequest -= rf->r_buf.size)
		{
		ret = fread((void *)temp_pnt, 1, rf->r_buf.size, f);

		accumulated += ret;

		if(ret < rf->r_buf.size) // Read shorter than requested
			return (size_t)accumulated/a;
		}
	// Last one.
	accumulated +=fread((void *)temp_pnt, 1, totrequest, f);
	return accumulated/a;
	}

if(availspace < totrequest)
	{ // Read is performed
	//printf("Quite big %lu\n", availspace);

	temp_pnt=buf;

	accumulated = fread((void *)buf, 1, availspace, f);

	if(rf->r_buf.eof_pos < rf->r_buf.size) // END OF FILE reached
		{
		//printf("End of file!\n");
		return totrequest/a;
		}

	RESET_BUF(rf->r_buf);

	temp_pnt += availspace;

	ret = Read(rf->filepointer, rf->r_buf.buf, rf->r_buf.size); // Fills buffer completely

	rf->r_buf.eof_pos = min(ret, rf->r_buf.size);

	ret = fread((void *)temp_pnt, 1, totrequest - availspace, f);

//	printf("Second read %ld %lu\n", ret, totrequest - availspace);

	ret += accumulated;

//	printf("Ret is %ld\n", ret);
	}
else	{
	totrequest = min(totrequest, rf->r_buf.eof_pos - rf->r_buf.currentcount);

	//printf("Small: Reading from buffer %lu bytes\n", totrequest);

	if(totrequest)
		{
		CopyMemPPC(rf->r_buf.current, buf, totrequest);
		//memcpy(buf, rf->r_buf.current, totrequest);
		ADVANCE_BUF(rf->r_buf, totrequest);

		//rf->r_buf.tot_fetched += totrequest;
		}
	//else	printf("Cazzo\n");

	ret = totrequest;
	}

if(ret == -1)
	{
	//printf("Bad return\n");
	return (size_t)0;
	}
else	{
	//printf("returning: %lu\n", (size_t)ret/a);
	return (size_t)ret/a;
	}
}

#ifdef __VBCC__
size_t fwrite(void * buf, size_t a, size_t b, FILE * f)
#else
size_t fwrite(const void * buf, size_t a, size_t b, FILE * f)
#endif
{
register LONG totrequest=a*b, ret;
register ULONG availspace;
struct myFILE * rf=(struct myFILE *)f;
register size_t accumulated;
register char * temp_pnt;

//fprintf(stderr, "WRITE: %ld bytes from %p\n", totrequest, buf);

if((f == stdout) || (f == stderr))
	return (size_t)FWrite((BPTR)fileno(f), (const APTR)buf, a, b);

availspace = AVAILSPACE_W(rf->w_buf);

if(totrequest > rf->w_buf.size) // A write so big it's bigger than our write buffer
	{
	//printf("write: big\n");

	for(	temp_pnt=(void *)buf;
		totrequest > rf->w_buf.size;
		temp_pnt+=rf->w_buf.size , totrequest -= rf->w_buf.size)
		{
		ret=fwrite((void *)temp_pnt, 1, rf->w_buf.size, f);

		accumulated += ret;

		if(ret < rf->w_buf.size) // Read shorter than requested
			return accumulated/a;
		}
	// Last one.
	accumulated += fwrite((void *)temp_pnt, 1, totrequest, f);
	return accumulated/a;
	}

if(availspace < totrequest)
	{ // Write is performed
	//printf("Forced write\n");

	temp_pnt=(void *)buf;

	if(availspace) // Caring if we're really with 0 bytes available.
		{
		//printf("Availsp %lu, totrequest %ld\n", availspace, totrequest);
		CopyMemPPC(temp_pnt, rf->w_buf.current, availspace);
    //memcpy(rf->w_buf.current, temp_pnt, availspace);
		temp_pnt += availspace;
		totrequest -= availspace;

		//ADVANCE_BUF(buf, availspace); // To the end.
		}
	ret = Write(rf->filepointer, rf->w_buf.buf, rf->w_buf.size); // Flushes buffer completely

	RESET_BUF(rf->w_buf);

	//printf("Done writing\n");

	CopyMemPPC(temp_pnt, rf->w_buf.current, totrequest); //
  //memcpy(rf->w_buf.current, temp_pnt, totrequest);

	//printf("Done re-copying\n");

	ADVANCE_BUF(rf->w_buf, totrequest);

	if(ret==rf->w_buf.size) // Complete buffer
		{
		ret = b;
		}
	else	{
		ret = -1; // Disk full.
		}
	}
else	{ // Buffering...
	CopyMemPPC((APTR)buf, rf->w_buf.current, totrequest);
  //memcpy(rf->w_buf.current, buf, totrequest);


	ADVANCE_BUF(rf->w_buf, totrequest);

	ret=totrequest;
	}

if(ret == -1)
	return (size_t)0;
else	return (size_t)ret/a;
}

/*
static inline BOOL is_eof(const BPTR f)
{
struct FileInfoBlock * fib;
LONG pos;

fib=AllocDosObject(DOS_FIB, NULL);

ExamineFH(f);

pos=Seek(f, SEEK_CUR, 0);

FreeDosObject(DOS_FIB, (void *)fib);

return (pos == fib->fib_Size);
}
*/

char * fgets(char * inbuf, int blen, FILE * f)
{
/*
	The fgets function gets a string from the specified file,
	which must have been previously fopen()ed for input.  Characters are
	copied from the file to the buffer until a new line (\n) has been
	copied, or length-1 characters have been copied, or the
	end-of-file is hit.  In any case, if the read succeeds, the buffer
        is terminated with a trailing null byte (\0).  If the read fails,
        the buffer will not be modified.
*/
// Oh shit....
struct myFILE * rf = (struct myFILE *)f;
//struct CIOBuf * rb;
LONG ret;
//unsigned slen;
char copyme, * destcopy = inbuf;
BOOL goodend = FALSE, eof = TRUE;
int totcopy=0;

if(f == NULL || blen == 0)
	return NULL;

while(totcopy < blen)
	{
	if(rf->r_buf.currentcount == rf->r_buf.eof_pos)
		{
		eof=TRUE;
		break;
		}

	if(rf->r_buf.currentcount == rf->r_buf.size)
		{ // Re-cycle buffer!
		RESET_BUF(rf->r_buf);

		ret = Read(rf->filepointer, rf->r_buf.buf, rf->r_buf.size);

		rf->r_buf.eof_pos = min(ret, rf->r_buf.size);
		}

	copyme = *rf->r_buf.current;

	if((copyme == '\n') || (copyme == 0))
		{
		if(copyme == '\n')
			{
			*destcopy++=copyme;
			totcopy++;
			rf->r_buf.currentcount++;
			rf->r_buf.current++;
			}
		*destcopy=0; // Terminates string.
		goodend=TRUE;
		break;
		}
	else	{
		*destcopy++=copyme;
		rf->r_buf.currentcount++;
		rf->r_buf.current++;
		totcopy++;
		}
	}

return (goodend ? inbuf : NULL);
}
