//========================================================================
//
// mystdio.cc
//
// Copyright 1999 Emmanuel Lesueur
//
//========================================================================

#define DB(x) //x

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <exec/memory.h>
#include <stdarg.h>
#include "mystdio.h"
#include "gmem.h"

#ifdef __SASC
#   define throw(x)
#endif

#ifdef __PPC__
#   include <powerup/ppcproto/dos.h>
#   include <powerup/gcclib/powerup_protos.h>

inline BPTR myOpen(const char* n,LONG m) {
    BPTR r=PPCOpen((char*)n,m);
    DB(printf("Open(%s)=%lx\n",n,r);)
    return r;
}
inline LONG myClose(BPTR f) {
    DB(printf("Close(%lx)\n",f);)
    return PPCClose(f);
}
inline LONG mySeek(BPTR f,LONG b,LONG m) {
    LONG r=PPCSeek(f,b,m);
    DB(printf("Seek(%lx,%ld,%ld)=%ld\n",f,b,m,r);)
    return r;
}
inline LONG myRead(BPTR f,void* b,size_t sz) {
    LONG r=PPCRead(f,b,sz);
    DB(printf("Read(%lx,%p,%u)=%ld\n",f,b,sz,r);)
    return r;
}
#else
#   include <proto/dos.h>

inline BPTR myOpen(const char* n,LONG m) {
    return Open((char*)n,m);
}
inline LONG myClose(BPTR f) {
    return Close(f);
}
inline LONG mySeek(BPTR f,LONG o,LONG m) {
    return Seek(f,o,m);
}
inline LONG myRead(BPTR f,void* b,size_t sz) {
    return Read(f,b,sz);
}
#endif

size_t myFILE::max_buf;
size_t myFILE::glb_chunk_size;

void myFILE::bufsizes(size_t sz1,size_t sz2) {
    if(sz1>1<<20)
	sz1=1<<20;
    else if(sz1==0)
	sz1=1;
    if(sz2>1<<20)
	sz2=1<<20;
    sz1*=1024;
    sz2*=1024;
    if(sz2==0)
	max_buf=0x7fffffff;
    else {
	if(sz1<4096)
	    sz1=4096;
	glb_chunk_size=sz1;
	if(sz2<2*sz1)
	    sz2=2*sz1;
	max_buf=sz2;
    }
}

myFILE::myFILE(const char* name) {
    if(file=myOpen(name,MODE_OLDFILE)) {
	BOOL ok=FALSE;
	if(FileInfoBlock* fib=(FileInfoBlock*)AllocDosObject(DOS_FIB,NULL)) {
	    if(ExamineFH(file,fib)) {
		total_size=fib->fib_Size;
		if(total_size<max_buf) {
		    chunk_size=total_size;
		    max_loaded_chunks=2;
		} else {
		    chunk_size=glb_chunk_size;
		    max_loaded_chunks=max_buf/chunk_size;
		}
		num_chunks=(total_size+chunk_size-1)/chunk_size;
		chunks=new chunk [num_chunks];
		pos=0;
		last_pos=0;
		num_loaded_chunks=0;
		loaded_chunks=NULL;
		oldest_chunk=NULL;
		ok=TRUE;
	    }
	    FreeDosObject(DOS_FIB,fib);
	}
	if(ok)
	    return;
    }
    throw("");
}

myFILE::~myFILE() {
    myClose(file);
    delete [] chunks;
}

size_t myFILE::read(void* p,size_t sz) {
    //printf("read(%p,%u)\n",p,sz);
    if(sz>total_size-pos)
	sz=total_size-pos;
    size_t r=sz;
    unsigned n=pos/chunk_size;
    unsigned offs=pos%chunk_size;
    unsigned char* q=static_cast<unsigned char*>(p);
    //bool xx=false;
    while(sz) {
	//printf("sz=%u pos=%u, last_pos=%u, chunk=%u, offs=%u\n",sz,pos,last_pos,n,offs);
	chunk* c=chunks+n;
	size_t sz1=chunk_size;
	if(!c->buf) {
	    /*if(!xx) {
		printf("read(%u)\n",sz);
		xx=true;
	    }*/
	    unsigned char* buf=NULL;
	    if(num_loaded_chunks==max_loaded_chunks) {
		buf=oldest_chunk->buf;
		oldest_chunk->buf=NULL;
		oldest_chunk=oldest_chunk->prev;
		oldest_chunk->next=NULL;
	    } else {
		++num_loaded_chunks;
		buf=new unsigned char [chunk_size];
	    }
	    c->buf=buf;
	    c->next=loaded_chunks;
	    c->prev=NULL;
	    if(loaded_chunks)
		loaded_chunks->prev=c;
	    else
		oldest_chunk=c;
	    loaded_chunks=c;
	    unsigned pos2=n*chunk_size;
	    if(last_pos!=pos2)
		mySeek(file,pos2,OFFSET_BEGINNING);
	    sz1=myRead(file,buf,chunk_size);/* error handling... */
	    if(sz1==-1) {
		r-=sz;
		break;
	    }
	    last_pos=pos2+sz1;
	} else if(pos-offs+chunk_size>total_size)
	    sz1=total_size-pos+offs;
	if(c->prev) {
	    c->prev->next=c->next;
	    if(c->next)
		c->next->prev=c->prev;
	    else {
		oldest_chunk=c->prev;
		oldest_chunk->next=NULL;
	    }
	    if(loaded_chunks)
		loaded_chunks->prev=c;
	    c->next=loaded_chunks;
	    c->prev=NULL;
	    loaded_chunks=c;
	}
	const unsigned char* s=c->buf+offs;
	if(sz1<=offs) {
	    r-=sz;
	    break;
	}
	size_t len=sz1-offs;
	size_t sz2=sz<=len?sz:len;
	memcpy(q,s,sz2);
	q+=sz2;
	sz-=sz2;
	pos+=sz2;
	++n;
	offs=0;
    }
    //printf("->read=%u\n",r);
    return r;
}


myFILE* myfopen(const char* name,const char* mode) {
    if(*mode=='w')
	throw("invalid open mode.");
    myFILE* f=NULL;
    try {
	f=new myFILE(name);
    }
    catch(...) {
    }
    return f;
}

int myFILE::seek(long p,int mode) {
    switch(mode) {
      case SEEK_SET:
	pos=p;
	break;
      case SEEK_CUR:
	pos+=p;
	break;
      case SEEK_END:
	pos=total_size+p;
	break;
      default:
	return -1;
    }
    if(pos>total_size) {
	pos=total_size;
	return -1;
    } else
	return 0;
}

