#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#define INCL_DOSMODULEMGR
#define INCL_DOSMISC
#define INCL_DOSEXCEPTIONS
#define INCL_DOSERRORS
#define INCL_REXXSAA
#include <os2.h>

/*
 *      The Road goes ever on and on
 *          Down from the door where it began.
 *
 *     [Bilbo on p.35 of _The Lord of the Rings_, I/i: "A Long-Expected Party"]
 *     [Frodo on p.73 of _The Lord of the Rings_, I/iii: "Three Is Company"]
 */

#ifdef OEMVS
#ifdef MYMALLOC
/* sbrk is limited to first heap segement so make it big */
#pragma runopts(HEAP(8M,500K,ANYWHERE,KEEP,8K,4K) STACK(,,ANY,) ALL31(ON))
#else
#pragma runopts(HEAP(2M,500K,ANYWHERE,KEEP,8K,4K) STACK(,,ANY,) ALL31(ON))
#endif
#endif


#include "EXTERN.h"
#include "perl.h"

static void xs_init (pTHX);
static PerlInterpreter *my_perl;

ULONG PERLEXPORTALL(PCSZ name, LONG rargc, const RXSTRING *rargv, PCSZ queuename, PRXSTRING retstr);
ULONG PERLDROPALL(PCSZ name, LONG rargc, const RXSTRING *rargv, PCSZ queuename, PRXSTRING retstr);
ULONG PERLDROPALLEXIT(PCSZ name, LONG rargc, const RXSTRING *rargv, PCSZ queuename, PRXSTRING retstr);

#if defined (atarist)
/* The Atari operating system doesn't have a dynamic stack.  The
   stack size is determined from this value.  */
long _stksize = 64 * 1024;
#endif

/* Register any extra external extensions */

/* Do not delete this line--writemain depends on it */
EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);

static void
xs_init(pTHX)
{
    char *file = __FILE__;
    dXSUB_SYS;
        newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
}

int perlos2_is_inited;

static void
init_perlos2(void)
{
/*    static char *env[1] = {NULL};	*/

    Perl_OS2_init3(0, 0, 0);
}

static int
init_perl(int doparse)
{
    int exitstatus;
    char *argv[3] = {"perl_in_REXX", "-e", ""};

    if (!perlos2_is_inited) {
	perlos2_is_inited = 1;
	init_perlos2();
    }
    if (my_perl)
	return 1;
    if (!PL_do_undump) {
	my_perl = perl_alloc();
	if (!my_perl)
	    return 0;
	perl_construct(my_perl);
	PL_perl_destruct_level = 1;
    }
    if (!doparse)
        return 1;
    exitstatus = perl_parse(my_perl, xs_init, 3, argv, (char **)NULL);
    return !exitstatus;
}

static char last_error[4096];

static int
seterr(char *format, ...)
{
	va_list va;
	char *s = last_error;

	va_start(va, format);
	if (s[0]) {
	    s += strlen(s);
	    if (s[-1] != '\n') {
		snprintf(s, sizeof(last_error) - (s - last_error), "\n");
		s += strlen(s);
	    }
	}
	vsnprintf(s, sizeof(last_error) - (s - last_error), format, va);
	return 1;
}

/* The REXX-callable entrypoints ... */

ULONG PERL (PCSZ name, LONG rargc, const RXSTRING *rargv,
                    PCSZ queuename, PRXSTRING retstr)
{
    int exitstatus;
    char buf[256];
    char *argv[3] = {"perl_from_REXX", "-e", buf};
    ULONG ret;

    if (rargc != 1)
	return seterr("one argument expected, got %ld", rargc);
    if (rargv[0].strlength >= sizeof(buf))
	return seterr("length of the argument %ld exceeds the maximum %ld",
		      rargv[0].strlength, (long)sizeof(buf) - 1);

    if (!init_perl(0))
	return 1;

    memcpy(buf, rargv[0].strptr, rargv[0].strlength);
    buf[rargv[0].strlength] = 0;
    
    exitstatus = perl_parse(my_perl, xs_init, 3, argv, (char **)NULL);
    if (!exitstatus) {
	exitstatus = perl_run(my_perl);
    }

    perl_destruct(my_perl);
    perl_free(my_perl);
    my_perl = 0;

    if (exitstatus)
	ret = 1;
    else {
	ret = 0;
	sprintf(retstr->strptr, "%s", "ok");
	retstr->strlength = strlen (retstr->strptr);
    }
    PERL_SYS_TERM1(0);
    return ret;
}

ULONG PERLEXIT (PCSZ name, LONG rargc, const RXSTRING *rargv,
                    PCSZ queuename, PRXSTRING retstr)
{
    if (rargc != 0)
	return seterr("no arguments expected, got %ld", rargc);
    PERL_SYS_TERM1(0);
    return 0;
}

ULONG PERLTERM (PCSZ name, LONG rargc, const RXSTRING *rargv,
                    PCSZ queuename, PRXSTRING retstr)
{
    if (rargc != 0)
	return seterr("no arguments expected, got %ld", rargc);
    if (!my_perl)
	return seterr("no perl interpreter present");
    perl_destruct(my_perl);
    perl_free(my_perl);
    my_perl = 0;

    sprintf(retstr->strptr, "%s", "ok");
    retstr->strlength = strlen (retstr->strptr);
    return 0;
}


ULONG PERLINIT (PCSZ name, LONG rargc, const RXSTRING *rargv,
                    PCSZ queuename, PRXSTRING retstr)
{
    if (rargc != 0)
	return seterr("no argument expected, got %ld", rargc);
    if (!init_perl(1))
	return 1;

    sprintf(retstr->strptr, "%s", "ok");
    retstr->strlength = strlen (retstr->strptr);
    return 0;
}

ULONG
PERLLASTERROR (PCSZ name, LONG rargc, const RXSTRING *rargv, PCSZ queuename, PRXSTRING retstr)
{
    int len = strlen(last_error);

    if (len <= 256			/* Default buffer is 256-char long */
	|| !DosAllocMem((PPVOID)&retstr->strptr, len,
			PAG_READ|PAG_WRITE|PAG_COMMIT)) {
	    memcpy(retstr->strptr, last_error, len);
	    retstr->strlength = len;
    } else {
	strcpy(retstr->strptr, "[Not enough memory to copy the errortext]");
	retstr->strlength = strlen(retstr->strptr);
    }
    return 0;
}

ULONG
PERLEVAL (PCSZ name, LONG rargc, const RXSTRING *rargv, PCSZ queuename, PRXSTRING retstr)
{
    SV *res, *in;
    STRLEN len, n_a;
    char *str;

    last_error[0] = 0;
    if (rargc != 1)
	return seterr("one argument expected, got %ld", rargc);

    if (!init_perl(1))
	return seterr("error initializing perl");

  {
    dSP;
    int ret;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    in = sv_2mortal(newSVpvn(rargv[0].strptr, rargv[0].strlength));
    eval_sv(in, G_SCALAR);
    SPAGAIN;
    res = POPs;
    PUTBACK;

    ret = 0;
    if (SvTRUE(ERRSV))
	ret = seterr(SvPV(ERRSV, n_a));
    if (!SvOK(res))
	ret = seterr("undefined value returned by Perl-in-REXX");
    str = SvPV(res, len);
    if (len <= 256			/* Default buffer is 256-char long */
	|| !DosAllocMem((PPVOID)&retstr->strptr, len,
			PAG_READ|PAG_WRITE|PAG_COMMIT)) {
	    memcpy(retstr->strptr, str, len);
	    retstr->strlength = len;
    } else
	ret = seterr("Not enough memory for the return string of Perl-in-REXX");

    FREETMPS;
    LEAVE;

    return ret;
  }
}

ULONG
PERLEVALSUBCOMMAND(
  const RXSTRING    *command,          /* command to issue           */
  PUSHORT      flags,                  /* error/failure flags        */
  PRXSTRING    retstr )                /* return code                */
{
    ULONG rc = PERLEVAL(NULL, 1, command, NULL, retstr);

    if (rc)
	*flags = RXSUBCOM_ERROR;         /* raise error condition    */

    return 0;                            /* finished                   */
}

#define ArrLength(a) (sizeof(a)/sizeof(*(a)))

static const struct {
  char *name;
  RexxFunctionHandler *f;
} funcs[] = {
             {"PERL",			(RexxFunctionHandler *)&PERL},
             {"PERLTERM",		(RexxFunctionHandler *)&PERLTERM},
             {"PERLINIT",		(RexxFunctionHandler *)&PERLINIT},
             {"PERLEXIT",		(RexxFunctionHandler *)&PERLEXIT},
             {"PERLEVAL",		(RexxFunctionHandler *)&PERLEVAL},
             {"PERLLASTERROR",		(RexxFunctionHandler *)&PERLLASTERROR},
             {"PERLDROPALL",		(RexxFunctionHandler *)&PERLDROPALL},
             {"PERLDROPALLEXIT",	(RexxFunctionHandler *)&PERLDROPALLEXIT},
             /* Should be the last entry */
             {"PERLEXPORTALL",		(RexxFunctionHandler *)&PERLEXPORTALL}
          };

ULONG
PERLEXPORTALL(PCSZ name, LONG rargc, const RXSTRING *rargv, PCSZ queuename, PRXSTRING retstr)
{
   int i = -1;

   while (++i < ArrLength(funcs) - 1)
	RexxRegisterFunctionExe(funcs[i].name, funcs[i].f);
   RexxRegisterSubcomExe("EVALPERL", (PFN)&PERLEVALSUBCOMMAND, NULL);
   retstr->strlength = 0;
   return 0;
}

ULONG
PERLDROPALL(PCSZ name, LONG rargc, const RXSTRING *rargv, PCSZ queuename, PRXSTRING retstr)
{
   int i = -1;

   while (++i < ArrLength(funcs))
	RexxDeregisterFunction(funcs[i].name);
   RexxDeregisterSubcom("EVALPERL", NULL /* Not a DLL version */);
   retstr->strlength = 0;
   return 0;
}

ULONG
PERLDROPALLEXIT(PCSZ name, LONG rargc, const RXSTRING *rargv, PCSZ queuename, PRXSTRING retstr)
{
   int i = -1;

   while (++i < ArrLength(funcs))
	RexxDeregisterFunction(funcs[i].name);
   RexxDeregisterSubcom("EVALPERL", NULL /* Not a DLL version */);
   PERL_SYS_TERM1(0);
   retstr->strlength = 0;
   return 0;
}
