/* from Dale Schumacher's dLibs */
/* heavily modified by ers and jrb */

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <osbind.h>
#include <mintbind.h>
#include <memory.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>

int	errno;
int	__mint;		/* 0 for TOS, MiNT version number otherwise */

char	_rootdir;	/* user's preferred root directory */

clock_t _starttime;	/* 200 HZ tick when we started the program */
clock_t _childtime;	/* time consumed so far by our children */
FILE	_iob[_NFILE];	/* stream buffers initialized below */

/* functions registered by user for calling at exit */
#ifdef __STDC__
typedef void (*ExitFn)(void);
#else
typedef void (*ExitFn)();
#endif
static ExitFn *_at_exit;
static int num_at_exit;	/* number of functions registered - 1 */

/*
 * get MiNT version number. Since this has to be done in supervisor mode,
 * we might as well set the start-up time of the system here, too.
 */

static void
getMiNT()
{
	long *cookie;

/* get the system time in 200HZ ticks from the BIOS _hz_200 variable */
	_starttime = *((unsigned long *) 0x4ba);
	_childtime = 0;

	cookie = *((long **) 0x5a0);
	if (!cookie)
		__mint = 0;
	else {
		while (*cookie) {
			if (*cookie == 0x4d694e54L) {	/* MiNT */
				__mint = cookie[1];
				return;
			}
			cookie += 2;
		}
	}
	__mint = 0;
}

/* supplied by the user */
__EXTERN int main __PROTO((int, char **, char **));

/* in getbuf.c */
__EXTERN void _getbuf __PROTO((FILE *));

void
_main(_argc, _argv, _envp)
	long _argc;
	char **_argv, **_envp;
{
	register FILE *f;
	register int i;
	char *s, *t, *new;
	extern int __default_mode__;	/* in binmode.c or defined by user */
	extern short _app;	/* tells if we're an application or acc */

	num_at_exit = 0;
	errno = 0;
/*
 * check for MiNT
 */
	(void)Supexec(getMiNT);
	if (__mint && _app)
		(void)Pdomain(1);	/* set MiNT domain */

/*
 * initialize UNIXMODE stuff. Note that this library supports only
 * a few of the UNIXMODE variables, namely "b" (binary mode default)
 * and "r<c>" (default root directory).
 */
	if ((s = getenv("UNIXMODE")) != 0) {
		while (*s) {
			if (*s == 'b')
				__default_mode__ = _IOBIN;
			else if (*s == 'r' && s[1])
				_rootdir = *++s;
			s++;
		}
	}

	if (_rootdir >= 'A' && _rootdir <= 'Z')
		_rootdir = _rootdir - 'A' + 'a';

/*
 * if we're running under MiNT, and the current drive is U:, then this
 * must be our preferred drive
 */
	if (!_rootdir && __mint >= 9) {
		if (Dgetdrv() == 'U'-'A')
			_rootdir = 'u';
	}

/* if stderr is not re-directed to a file, force 2 to console
 * (UNLESS we've been run from a shell we trust, i.e. one that supports
 *  the official ARGV scheme, in which case we leave stderr be).
 */
	if(!*_argv[0] && isatty(2))
	    (void)Fforce(2, -1);

	stdin->_flag = _IOREAD|_IOFBF|__default_mode__;
	stdout->_flag = _IOWRT|_IOLBF|__default_mode__;
	stderr->_flag = _IORW|_IONBF|__default_mode__;
			 /* some brain-dead people read from stderr */

	for(i = 0, f = _iob; i < 3; ++i, ++f) {	/* flag device streams */
	    if(isatty(f->_file = i))
			f->_flag |= _IODEV;
	    else
	        if(f == stdout)	{ /* stderr is NEVER buffered */
	        /* if stdout re-directed, make it full buffered */
	            f->_flag &= ~(_IOLBF | _IONBF);
	            f->_flag |=  _IOFBF;
	        }
	    _getbuf(f);	/* get a buffer */
	}
 	for(i = 3; i < _NFILE; i++, f++) {
	  f->_flag = 0;		/* clear flags, if this is a dumped program */
	}

	/* Fix up environment, if necessary. At present, the only variable
	 * affected is PATH; the "standard" path separators for PATH are
	 * ',' and ';' in the Atari world, but POSIX mandates ':'. This
	 * conflicts with the use of ':' as a drive separator, so we
	 * also convert names like A:\foo to /dev/A/foo
	 * NOTE: this conversion must be undone in spawn.c so that
	 * old fashioned programs will understand us!
	 */
	for (i = 0; s = _envp[i]; i++) {	/* '=', NOT '==' */
		if (s[0] == 'P' && s[1] == 'A' && s[2] == 'T' &&
		    s[3] == 'H' && s[4] == '=') {
			new = alloca(4*strlen(s));
			strncpy(new, s, 5);
			t = new+5;
			s += 5;
			while (*s) {
				if (s[1] == ':') {	/* drive letter */
					*t++ = '/';
					*t++ = 'd'; *t++ = 'e'; *t++ = 'v';
					*t++ = '/'; *t++ = *s++; s++;
				} else if (*s == ';' || *s == ',') {
					*t++ = ':'; s++;
				} else if (*s == '\\') {
					*t++ = '/'; s++;
				} else {
					*t++ = *s++;
				}
			}
			*t++ = 0;
			_envp[i] = strdup(new);
			break;
		}
	}

	/* ANSI-Draft: A return from the initial call to the main 
	 * function is equivalent to calling the exit function with
	 * the value returned by the main function as its argument. If
	 * the main function executes a return that specifies no
	 * value, the termination status returned to the host
	 * environment is undefined. [section 2.1.2.2]
	 */
	exit(main((int) _argc, _argv, _envp));
}

__EXITING exit(status)
	int status;
{
	register int i, f;

	for(i = num_at_exit - 1; i >= 0; --i)
		(*_at_exit[i])();

	for(i=0; i<_NFILE; ++i) {
		f = _iob[i]._flag;
		if(f & (_IORW | _IOREAD | _IOWRT))
			if (_iob[i]._file <= 2) /* only flush std. streams */
				fflush(&_iob[i]);
			else
				fclose(&_iob[i]);
	}
	_exit(status);
}

/* register a function for execution on termination */
/* Ansi requires atleast 32 entries, we make it dynamic and hope
   it meets the ansi requirement */
 
int atexit(func)
	ExitFn func;
{
        ExitFn *new_at_exit = _at_exit;
    
	if (num_at_exit == 0)
		new_at_exit = (ExitFn *)malloc((size_t)sizeof(ExitFn));
	else
		new_at_exit = (ExitFn *)realloc(new_at_exit,
			(size_t)((num_at_exit + 1) * sizeof(ExitFn)));
	if(new_at_exit == (ExitFn *)NULL)
		return -1;	/* failure */

        _at_exit = new_at_exit;
	_at_exit[num_at_exit++] = func;
	return 0;		/* success */
}
