/* revamped version by Loren J. Rittle. Thanks Loren !! */

/* I guess I should start to put my sources into RCS........ */

#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/execbase.h>

/* have to use explicit 4 dereferencing here, since we can't always access
   globals so early (a4 etc) */
#define BASE_EXT_DECL
#define BASE_NAME *(void **)4
#include <inline/exec.h>
#include <libraries/dosextens.h>
#include <limits.h>

#include <sys/syscall.h>
#ifdef BASECRT0
#include <sys/exec.h>
#endif /* BASECRT0 */

/* get the current revision number. Version control is automatically done by
 * OpenLibrary(), I just have to check the revision number 
 */
#undef IX_VERSION
#include "../library/version.h"

#undef DEBUG
#ifdef DEBUG
#define DP(a) kprintf a
static int inline __geta4() {int res;asm ("movel a4,%0" : "=g" (res));return res;}
#else
#define DP(a)
#endif

#define MSTRING(x) STRING(x)
#define STRING(x) #x

struct Library *ixemulbase;

static int start_stdio(int, char **, char **);

/*
 * Have to take care.. I may not use any library functions in this file,
 * since they are exactly then used, when the library itself couldn't be
 * opened...
 */

extern int main();
extern int expand_cmd_line;
extern char *default_wb_window;
extern int errno;
extern char **environ;
extern char *_ctype_;
extern int sys_nerr;
extern struct ExecBase *SysBase;
extern struct Library *DOSBase;
extern struct __sFILE **__sF;
static int ENTRY();
static int exec_entry();

asm("
	.text

	jmp	pc@(_ENTRY-.+2)	| by default jump to normal AmigaDOS startup

	| this is a struct exec, for now only OMAGIC is supported
	.globl	exec
exec:
	.word	___machtype	| a_mid
	.word	0407		| a_magic = OMAGIC
	.long	___text_size	| a_text
	.long	___data_size	| a_data
	.long	___bss_size	| a_bss
	.long	0		| a_syms
	.long	_exec_entry	| a_entry
	.long	0		| a_trsize
	.long	0		| a_drsize

	| word alignement is guaranteed
");

#ifdef BASECRT0
extern int __datadata_relocs();
extern int __data_size, __bss_size;

#ifdef RCRT0
/* have to do this this way, or it is done base-relative.. */
static inline int dbsize() 
{
  int res;
  asm ("movel #___data_size,%0; addl #___bss_size,%0" : "=r" (res));
  return res;
}

static void inline
ix_resident (void *base, int num, int a4, int size, void *relocs)
{
  typedef void (*func)(int, int, int, void *);

  ((func)((int)base - 6*(SYS_ix_resident + 4))) (num, a4, size, relocs);
}

#else
static void inline
ix_resident (void *base, int num, int a4)
{
  typedef void (*func)(int, int);

  ((func)((int)base - 6*(SYS_ix_resident + 4))) (num, a4);
}
#endif
#endif /* BASECRT0 */

static int
exec_entry (struct Library *ixembase, int argc, char *argv[], char *env[])
{
#ifdef BASECRT0
  register int a4;
  /* needed, so that data can be accessed. ix_resident might change this
     again afterwards */
  asm volatile ("lea	___a4_init,a4" : "=r" (a4) : "0" (a4));
  asm volatile ("movel	a4,%0" : "=r" (a4) : "0" (a4));

#ifdef RCRT0
  ix_resident (ixembase, 4, a4, dbsize(), __datadata_relocs);
#else
  ix_resident (ixembase, 2, a4);
#endif
#endif /* BASECRT0 */
  ixemulbase = ixembase;
  return ix_exec_entry (argc, argv, env, &errno, start_stdio);
}

/* this thing is best done with sprintf else, but it has to work without the
 * library as well ;-(
 */
__inline static char *
itoa (int num)
{
  short snum = num;

  /* how large can a long get...?? */
  /* Answer (by ljr): best method to use (in terms of portability)
     involves number theory.  The exact number of decimal digits
     needed to store a given number of binary digits is

	ceiling ( number_of_binary_digits * log(2) / log(10) )
     or
	ceiling ( number_of_binary_digits * 0.301029996 )

     Since sizeof evaluates to the number of bytes a given type takes 
     instead of the number of bits, we need to multiply sizeof (type) by
     CHAR_BIT to obtain the number of bits.  Since an array size specifier
     needs to be integer type, we multiply by 302 and divide by 1000 instead
     of multiplying by 0.301029996.  Finally, we add 1 for the null terminator
     and 1 because we want the ceiling of the function instead of the floor.
     Funny thing about this whole affair is that you really wanted to know
     the size a short could expand to be and not a long...  :-) I know
     comments get out of date, etc.  The nice thing about this method is
     that the size of the array is picked at compile time based upon the
     number of bytes really needed by the local C implementation. */
  static char buf[sizeof snum * CHAR_BIT * 302 / 1000 + 1 + 1];
  char *cp;
  
  buf[sizeof buf - 1] = 0;
  for (cp = &buf[sizeof buf - 1]; snum; snum /= 10) 
    *--cp = (snum % 10) + '0';

  return cp;
}

__inline static char *
pstrcpy (char *start, char *arg)
{
  while (*start++=*arg++) ;
  return start-1;
}

__inline static char *
build_warn (char *t1, int num1)
{
  static char buf[255];
  char *cp;
  
  cp = pstrcpy (buf, t1);
  cp = pstrcpy (cp, itoa (num1));

  return buf;
}

static int
ENTRY (void)
{
  register unsigned char *rega0  asm("a0");
  register unsigned long  regd0  asm("d0");
#ifdef BASECRT0
  register int a4;
#endif /* BASECRT0 */

  UBYTE *aline = rega0;
  ULONG alen = regd0;
#ifdef BASECRT0
  struct Library *ibase;

  /* needed, so that data can be accessed. ix_resident() might change this
     again afterwards */
  asm volatile ("lea	___a4_init,a4" : "=r" (a4) : "0" (a4));
  asm volatile ("movel a4,%0" : "=r" (a4) : "0" (a4));
#endif /* BASECRT0 */

#ifndef BASECRT0
  ixemulbase = OpenLibrary ("ixemul.library", IX_VERSION);
  if (ixemulbase)
#else /* BASECRT0 */
DP(("ENTRY: a4 = $%lx\n", __geta4()));

  ibase = OpenLibrary ("ixemul.library", IX_VERSION);
  if (ibase)
#endif /* BASECRT0 */
    {
      int res;

      /* just warn, in case the user tries to run program which might require
       * more functions than are currently available under this revision. */
#ifndef BASECRT0
      if (ixemulbase->lib_Version == IX_VERSION &&
	  ixemulbase->lib_Revision < IX_REVISION)
#else /* BASECRT0 */
      if (ibase->lib_Version == IX_VERSION &&
	  ibase->lib_Revision < IX_REVISION)
#endif /* BASECRT0 */
	/* don't need to block signals, they are blocked until after 
         * ix_startup() in current releases */
	__request_msg (build_warn ("ixemul.library warning: needed revision "
				     MSTRING (IX_REVISION) ", current revision ",
#ifndef BASECRT0
	  	         	     ixemulbase->lib_Revision), "Continue");
#else /* BASECRT0 */
	  	         	     ibase->lib_Revision), "Continue");

#ifdef RCRT0
      ix_resident (ibase, 4, a4, dbsize(), __datadata_relocs);
#else
      ix_resident (ibase, 2, a4);
#endif

DP(("ix_resident: a4 = $%lx\n", __geta4()));
#endif /* BASECRT0 */

#ifdef BASECRT0
      ixemulbase = ibase;
#endif /* BASECRT0 */

      res = ix_startup (aline, alen, 
		        expand_cmd_line, default_wb_window, start_stdio, &errno);

      CloseLibrary (ixemulbase);

      return res;
    }
  else
    {
      struct Process *me = (struct Process *)((*(struct ExecBase **)4)->ThisTask);

      __request_msg ("Need at least version " MSTRING (IX_VERSION)
		     " of ixemul.library.", "Abort");
      
      /* quickly deal with the WB startup message, as the library couldn't do
       * this for us. Nothing at all is done that isn't necessary to just shutup
       * workbench..*/
      if (! me->pr_CLI)
        {
	  Forbid (); 
	  ReplyMsg ((WaitPort (& me->pr_MsgPort), GetMsg (& me->pr_MsgPort)));
	}
      
      return 20;
    }
}

int
start_stdio (int argc, char **argv, char **env)
{
  int res;
#ifndef BASECRT0
  extern void etext ();
  extern void _mcleanup ();
#else /* BASECRT0 */

DP(("start_stdio1: a4 = $%lx\n", __geta4()));
#endif /* BASECRT0 */

  /* more to follow ;-) */
  ix_get_vars2 (6, &_ctype_, &sys_nerr, &SysBase, &DOSBase, &__sF, &environ);
  environ = env;

#ifndef BASECRT0
#ifdef MCRT0
  atexit(_mcleanup);
  monstartup(start_stdio, etext);
#endif
#endif /* not BASECRT0 */

  res = main (argc, argv, env);
  return res;
}

#ifndef BASECRT0
#ifdef CRT0
/*
 * null mcount and moncontrol,
 * just in case some routine is compiled for profiling
 */
asm(".globl mcount");
asm(".globl _moncontrol");
asm("_moncontrol:");
asm("mcount: rts");
#endif CRT0
#endif /* not BASECRT0 */
