/* OXPORT.DJC -- for go32 dos extender djgpp and gcc */
/* PORTABILITY FOR OXBOW STUFF -- MODIFY TO SUIT THE COMPILER/OS IN USE */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <direct.h>
#include <errno.h>
#include <io.h>
#include <time.h>
#include <dirent.h>
#include <dos.h>
#include <pc.h>
#include <mousex.h>
#include <stdarg.h>
#include <grx.h>
#include "../cff.h"

# define disable()  asm volatile("cli");
# define enable()   asm volatile("sti");

extern void *mymemmove(void *, void *, long);

#define PCDOS 1

#define NIBBLE 128
#define CHUNK_SIZE 8192

#ifndef HEAPSTART
#define HEAPSTART 0x500000
#endif

unsigned long heapcreep;
void *heapstart = (void *)HEAPSTART;


extern volatile int errno;

static STOR BIGZERO;


#if 0
/* 
	THIS CODE IS FOR SAMPLE PURPOSES ONLY -- THE REAL CODE IS IN THE LIBRARY
	PROGRAMMERS WHO WISH TO CREATE CUSTOM COMPARISON ROUTINES CAN USE THESE
	FUNCTIONS AS MODELS.
*/
/*
	default_keycmp - system key comparison

	Return:	LESS	if keya <  keyb
			EQUAL	if keya == keyb
			GREATER	if keya >  keyb
*/
static __inline__ int
mymemcmp(unsigned char *a, unsigned char *b, int len)
{
int result = 0;
	do {
		if((result = ((int)*a++) - ((int)*b++)))
			return result;
	} while(--len);
	return result;
}
int
default_keycmp (void *keya, int	lena, void *keyb, int lenb)
{
int	result;

	if (lena == 0)
		return ( (lenb == 0) ? EQUAL : LESS );
	else if (lenb == 0)
		return (GREATER);


	result = mymemcmp (keya, keyb, ((lena<lenb)?lena:lenb));

	if (result < 0)
		return LESS;
	if (result > 0)
		return GREATER;

	return ((lena == lenb) ? EQUAL : ((lena < lenb) ? LESS : GREATER));

}
/*
	default_itemcmp - system item comparison

	Return:	LESS-1		if itema <  itemb
			EQUAL+1		if itema == itemb
			GREATER+1	if itema >  itemb
*/

int
default_itemcmp (Item *a, Item *b)
{
	return ( (a->item == b->item) ? 
				EQUAL+1 : ((a->item < b->item) ? LESS-1 : GREATER+1) );
}

#endif

/* DUMMY ROUTINES TO PREVENT LINKING UNWANTED MALLOC LIKE STUFF */
int _heapchk(void)
{
	return 0;
}
int _heapset(unsigned a)
{
	return 0;
}
void *_expand (void *mem, size_t new_size)
{
	return NULL;
}
size_t _msize (const void *mem)
{
	return mallocsize(mem);
}
unsigned malloc_usable_size (void *mem)
{
	return mallocsize((const void *)mem);
}

/* ---------------- END OF MALLOC DUMMYS ------------- */

static __inline__ unsigned long
round_up(long size, long amt)
{
	return (size&(amt-1)) ? size+(amt-(size&(amt-1))) : size;
}

static int testflags = 0;
void
cfport_settestflags(int flags)
{
	testflags |= flags;
}
void
cfport_clrtestflags(int flags)
{
	testflags &= ~flags;
}

static void
fix_statbuf(struct stat *stat, CFSTAT *sbuf)
{
	sbuf->st_atime = stat->st_atime;
	sbuf->st_mtime = stat->st_mtime;
	sbuf->st_ctime = stat->st_ctime;
	sbuf->st_size = stat->st_size;
	sbuf->st_alloc = stat->st_size;
	sbuf->st_filesize = stat->st_size;
	sbuf->st_filealloc = stat->st_size;
	sbuf->st_blksize = stat->st_blksize;
	sbuf->st_dev = stat->st_dev;
	sbuf->st_ino = stat->st_ino;
	sbuf->st_nlink = stat->st_nlink;
	sbuf->st_uid = stat->st_uid;
	sbuf->st_gid = stat->st_gid;	
	sbuf->st_rdev = stat->st_rdev;
	sbuf->st_mode = stat->st_mode;
}
int
PORTISATTY(int fd)
{
	return isatty(fd);
}
void *
PORTSBRK(unsigned long amt)
{
	return ((void *)sbrk(amt));
}
void
PORTEXIT(int val)
{
	exit(val);
}
#if 0
void
PORTHEAPSTART(void)
{
void *curstart = sbrk(0);
char *newstart = NULL;
	if(curstart > heapstart)
	{
		cfprintf("CFFINIT: The heap safety zone was exceeded by %d bytes.\n",
			-((long)(heapstart - curstart)));
		exit(-1);
	}
	heapcreep = (unsigned long)(heapstart - curstart);
	if((newstart = (void *)sbrk(heapcreep)) == (void *)-1) {
		cfprintf("Failed to set heap: zone=%lu HEAPSTART=%p curstart=%p.\n",
		heapcreep, heapstart, curstart);
		exit(-2);
	}
	else if(heapcreep && newstart+heapcreep != heapstart) {
		cfprintf("Heap was not set properly: desired=%p result=%p zone=%lu.\n",
		heapstart, newstart, heapcreep);
		exit(-3);
	}
}
#endif
int
PORTSTAT(void *path, CFSTAT *sbuf)
{
struct stat _stat;
int result = stat(path, &_stat);
	if(result == 0)
		fix_statbuf(&_stat, sbuf);
	return result;
}
void
PORTFSTAT(int handle, CFSTAT *sbuf)
{
struct stat stat;

	if(handle <= -131 && handle >= -135)
		handle += 135;
	fstat(handle, &stat);
	fix_statbuf(&stat, sbuf);	
}

long
PORTREAD(long handle, void *mem_addr, STOR dsk_addr, long amount)
{
	if(handle <= -131 && handle >= -135)
		handle += 135;
	else
		lseek(handle, dsk_addr.a0, SEEK_SET);  
	return read(handle, mem_addr, amount);
}
long
PORTWRITE(long handle, void *mem_addr, STOR dsk_addr, long amount)
{
	if(handle <= -131 && handle >= -135)
		handle += 135;
	else
		lseek(handle, dsk_addr.a0, SEEK_SET);  
	return write(handle, mem_addr, amount);
}
void
PORTSEEK(long handle, STOR spot, int mode, STOR *loc)
{
STOR ret;
	if(handle <= -131 && handle >= -135)
		handle += 135;
	switch(mode)
	{
		case S_END:
			mode = SEEK_END;
			break;
		case S_CUR:
			mode = SEEK_CUR;
			break;
		case S_SET:
			mode = SEEK_SET;
			break;
		default:
			mode = -1;
	}
	ret.a4.s1 = 0;
	ret.a4.s0 = lseek((int)handle, spot.a0, mode);
	if(loc)
		*loc = ret;
}
long
PORTOPEN(void *path, int mode)
{
int omode = O_BINARY;

	if(mode & F_STAT)
		omode |= O_RDONLY;
	else if((mode & F_RDWR) == F_RDONLY)
		omode |= O_RDONLY;
	else if((mode & F_RDWR) == F_WRONLY)
		omode |= O_WRONLY;
	else if((mode & F_RDWR) == F_RDWR)
		omode |= O_RDWR;
	if(mode & F_TRUNC)
		omode |= O_TRUNC;
	if(mode & F_EXCL)
		omode |= O_EXCL;
	if(mode & F_APPEND)
		omode |= O_APPEND;

	return open(path,omode);
}
long
PORTCLOSE(long handle)
{
	if(handle <= -131 && handle >= -135)
		handle += 135;
	return close((int)handle);
}
long
PORTCREATE(void *path, int mode)
{
	return creat(path, S_IREAD|S_IWRITE);
}
long
PORTUNLINK(void *path)
{
	return unlink(path);
}
#if PCDOS == 1
static void
dos_truncate(short handle, long amount)
{
#include <dos.h>
union REGS r;
#if 0
cfprintf("DOS TRUNCATE handle=%d amount=%u\n", handle , amount);
#endif
	r.h.al = 0;		/* SEEK_SET */
	r.h.ah = 0x42;	/* seek */
	r.x.bx = handle;
	r.x.dx = (short)amount;
	r.x.cx = (short)(amount>>16);
	intdos(&r,&r);

	r.h.ah = 0x40;	/* write */
	r.x.bx = handle;
	r.x.cx = 0;		/* amount == 0 */
	intdos(&r,&r);
	
	r.h.ah = 0x3e;	/* close */
	r.x.bx = handle;
	intdos(&r,&r);
}
#endif
long
PORTTRUNCATE(long handle, char *path, STOR amount)
{
	if(handle <= -131 && handle >= -135)
		handle += 135;
#if PCDOS == 1
	/* DJCC has a bugged ftruncate */
	dos_truncate((short)handle, amount.a0);
	return PORTOPEN(path, F_RDWR);
#else
	ftruncate((int)handle, amount.a0);
	return handle;
#endif
}
void
PORTCLOSETRUNC(long handle, STOR amount)
{
	if(handle <= -131 && handle >= -135)
		handle += 135;
#if PCDOS == 1
	/* DJCC has a bugged ftruncate */
	dos_truncate((short)handle, amount.a0);
#else
	ftruncate((int)handle, amount.a0);
	close((int)handle);
#endif
}
void
PORTFLUSH(long handle)
{
	if(handle <= -131 && handle >= -135)
		handle += 135;
#if 0
	if(handle < 0)
		 sync();
	else 
#endif
		fsync((int)handle);
}

char *
PORTGETCWD(void *buf, int maxlen)
{
char *result;

	result = getcwd((char *)buf, maxlen);
	((char *)buf)[maxlen] = 0;
	if(result)
	{
		while(*((char *)buf))
		{
			if(*((char *)buf) == '\\') *((char *)buf) = '/';
			++((char *)buf);
		}
 	}
	return result;
}

long
PORTCHDIR(void *path)
{
	return chdir(path);
}
unsigned long
PORTTIME(void)
{
	return time(NULL);
}
unsigned long
PORTQTIME(void)
{
static unsigned long lasttime;
static unsigned long lasttick;
static unsigned long tickup;
static unsigned long newtick;

	if(!lasttime) {
		lasttime = time(NULL);
		lasttick = *((unsigned long *)0xE000046C);
	} 
	newtick = *((unsigned long *)0xE000046C);
	if(newtick != lasttick)
	{
		if(newtick > lasttick)
			tickup += (newtick - lasttick) * 54931;
		else
			tickup += (newtick + (0xffffffff - lasttick) + 1) * 54931;
		lasttick = newtick;
		if(tickup > 1000000) {
			tickup -= 1000000;
			lasttime += 1;
		}
	}
	return lasttime;
}
unsigned long
PORTCLOCK(void)
{
	return clock();
}
volatile void
PORTABORT(void)
{
extern volatile void abort();
	abort();
}
void *
PORTOPENDIR(char *name)
{
	return opendir(name);
}
void *
PORTREADDIR(void *it, CFDIRENT *d)
{
struct dirent *rd;
	if((rd = readdir(it))) {
		d->d_namlen = rd->d_namlen;
		d->d_name = rd->d_name;
		d->d_mode = M_EXTRNFILE;
	}
	return rd;
}
void
PORTTELLDIR(void *it, STOR *loc)
{
STOR me;
	me.item = 0;
	me.a0 = telldir(it); 
	if(loc)
		*loc = me;
}
void
PORTSEEKDIR(void *it, STOR *loc)
{
	seekdir(it, loc->a0);
}
void
PORTREWINDDIR(void *it)
{
	rewinddir(it);
}
long
PORTCLOSEDIR(void *it)
{
	return closedir(it);
}
int
PORTPRINT(int c)
{
	return write(1, &c, 1);
}
char *
PORTGETENV(char *symb)
{
	return getenv(symb);
}
int
PORTGETPAGESIZE()
{
	return 4096;
}


/* THE BASIC OS DRIVERS */

/* Primary Memory driver */
long
pmem_driver(int func, int handle, void *mem_addr, STOR dsk_addr, long amount)
{
	switch(func)
	{
		case	S_GETSPACE:
		{
		unsigned long min, nib, current;

			amount = round_up(amount, CHUNK_SIZE);
			min = round_up(amount / 4, CHUNK_SIZE);
			nib = round_up(amount / 16, CHUNK_SIZE);
			current = amount;
			((STOR *)mem_addr)->a2.size = 0;
			((STOR *)mem_addr)->a2.type = STO_CHUNK;

			while(current >= min) {
			  if((((STOR *)mem_addr)->a1 = PORTSBRK(current)) != (void *)-1) {
				if(((STOR *)mem_addr)->a0 & (NIBBLE-1))
				{/* Normally the system uses NIBBLE bytes as the alignment */
				  ((STOR *)mem_addr)->a0 = 
				  			round_up(((STOR *)mem_addr)->a0, NIBBLE);
				  current -= NIBBLE;
				}
				((STOR *)mem_addr)->a2.size = current;
				return 0;
			  }
			  current -= nib;
			}
			return 1;
		}
		case	S_GIVESPACE:
			return 1;
		default:
			return 1;
	}
	return 0;
}

/* Secondary Memory driver */
long
smem_driver(int func, int handle, void *mem_addr, STOR dsk_addr, long amount)
{
	if(!(testflags & 1))
	{/* 
		IF SECONDARY MEMORY REALLY EXISTS, THEN PUT A REAL DRIVER HERE.
		The system adapts to a 0 return from S_GETSPACE by mapping 
		secondary memory to primary memory when cfinit is called.
		S_OPEN is never called.
	 */
		switch (func)
		{
			case S_GETSPACE:
				*((long *)mem_addr) = 0;
				break;
		}
		return -1;
	} /* END: Real driver */
	else {
	/* THIS CODE IS JUST FOR TESTING PURPOSES, IT USES A LOCAL MEMORY BUFFER */

#define SMEMMAX (4096*1024)
static char *sbuf = NULL;
static long smemalloc = 0;

		if(sbuf == NULL)
			if((sbuf = PORTSBRK(SMEMMAX)) == (void *)-1)
				return -1;

		switch(func)
		{
			case	S_CLOSE:
				return 0;		/* a real driver would do something */			
			case	S_READBLK:
			case	S_WRITEBLK:
			{
			char *smemaddr = sbuf + dsk_addr.a0;
			long xfer = amount;
				if(dsk_addr.a0 + amount > smemalloc)
					xfer = smemalloc - dsk_addr.a0;
				if(xfer < 0) {
					return -1;
				}
				if(func == S_READBLK)
					 mymemmove(mem_addr, smemaddr, xfer);
				else mymemmove(smemaddr, mem_addr, xfer);
				return xfer;
			}
			case	S_GETSPACE:
				amount = round_up(amount, CHUNK_SIZE);
				((STOR *)mem_addr)->a0 = smemalloc;
				((STOR *)mem_addr)->a2.size = 0;
				((STOR *)mem_addr)->a2.type = STO_CHUNK;
				if(amount + smemalloc < SMEMMAX) {
					((STOR *)mem_addr)->a2.size = amount;
					smemalloc += amount;
					return 0;
				} else {/* Not enough space available, return error + avail */
					((STOR *)mem_addr)->a2.size = 
									(SMEMMAX - smemalloc) & ~CHUNK_SIZE;
					return 1;
				}
			case	S_GIVESPACE:
				if(((STOR *)mem_addr)->a0 == 
						smemalloc - ((STOR *)mem_addr)->a2.size)
				{/* Accept returns if they are at the end */
					smemalloc -= ((STOR *)mem_addr)->a2.size;
					return 0;
				}
				break;
			default:
				break;
		}
		return 1;
	}/* END: test driver */
}

/* Internal file driver */
long
cfile_driver(int func, int handle, void *mem_addr, STOR dsk_addr, long amount)
{
long result = 0;

	switch(func)
	{
		case S_READBLK:
			result = PORTREAD(handle, mem_addr, dsk_addr, amount);
			break;
		case S_WRITEBLK:
			result = PORTWRITE(handle, mem_addr, dsk_addr, amount);
			break;
		case	S_GETSPACE:
		{
		u_long result;
		STOR base;
		STOR end;
			PORTSEEK(handle, BIGZERO, S_END, &base);
			amount = round_up(amount, CHUNK_SIZE);
			end.a4.s0 = base.a0 + amount-1;
			end.a4.s1 = 0;
			PORTWRITE(handle, "0", end, 1);
			PORTSEEK(handle, BIGZERO, S_END, &end);
			result = end.a0 - base.a0;
			((STOR *)mem_addr)->a0 = base.a0;
			((STOR *)mem_addr)->a2.size = result;
			((STOR *)mem_addr)->a2.type = STO_CHUNK;
			return (result == amount) ? 0:1;		
		}
		case	S_GIVESPACE:
		{
		STOR base;
			PORTSEEK(handle, BIGZERO, S_END, &base);
			if((((STOR *)mem_addr)->a0 + ((STOR *)mem_addr)->a2.size) == base.a0)
			{/* OK to return space if at end */
#if 0
			long result;
				base.a0 -= ((STOR *)mem_addr)->a2.size;
				result = PORTTRUNCATE(handle, base);
#endif
				return 0;	/* signal caller that this space is ok to truncate */
			}
			else return 1;
		}
		case	S_OPEN:
			result = PORTOPEN(mem_addr, handle);
			break;
		case	S_CLOSE:
			result = PORTCLOSE(handle);
			break;
		case	S_CREATE:
			result = PORTCREATE(mem_addr, handle);
			break;
		case	S_UNLINK:
			result = PORTUNLINK(mem_addr);
			break;
		case	S_SEEK:
			PORTSEEK(handle, dsk_addr, amount, mem_addr);
			break;
		case	S_FLUSH:
			PORTFLUSH(handle);
			break;
		case	S_CLOSETRUNC: /* truncate and close, (PCDOS needs this) */
			PORTCLOSETRUNC(handle, dsk_addr);
			break;
		default:
			return 1;
	}
	return result;
}
/* External file driver */
long
xfile_driver(int func, int handle, void *mem_addr, STOR dsk_addr, long amount)
{
long result = 0;

	switch(func)
	{
		case	S_GETSPACE:
			((STOR *)mem_addr)->a2.size = 0;
		case	S_GIVESPACE:
			return 1;
		case	S_OPEN:
			result = PORTOPEN(mem_addr, handle);
			break;
		case	S_CLOSE:
			result = PORTCLOSE(handle);
			break;
		case	S_UNLINK:
			result = PORTUNLINK(mem_addr);
			break;
		case	S_CREATE:
			result = PORTCREATE(mem_addr,handle);
			break;
		case	S_READBLK:
			result = PORTREAD(handle, mem_addr, dsk_addr, amount);
			break;
		case	S_WRITEBLK:
			result = PORTWRITE(handle, mem_addr, dsk_addr, amount);
			break;
		case	S_SEEK:
			PORTSEEK(handle, dsk_addr, amount, mem_addr);
			break;
		case	S_FLUSH:
			PORTFLUSH(handle);
			break;
		default:
			return 1;
	}
	return result;
}
