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

#undef __USE_INLINE__
#include <exec/types.h>
#include <utility/tagitem.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/rexxsyslib.h>
#include <proto/utility.h>

#include <rexx/rxslib.h>
#include <rexx/errors.h>
//#include "rexxmsgext.h" // this should change depening on the ultimate location of the structures

/* utils */

/*
 * Structure for the rexx host. Most of the code is inspired from Olaf
 * Barthel's sample ARexx code from the developer CD 2.1
 */


struct RexxHost
{
	struct MsgPort *Port;
	TEXT PortName[81];
} ;

struct ARexxMsg
{
	struct RexxMsg *rexxMsg;
	BOOL isReplied;
	struct RexxHost *rexxHost;
};

STRPTR dupstr(STRPTR src)
{
    STRPTR dest = NULL;
    ULONG len;
    if(src)
    {
        len = strlen(src);
        if((dest = IExec->AllocVec(len + 1, MEMF_ANY)))
        {
            strcpy(dest,src);
        }
    }
    return dest;
}


struct TimeRequest *
OpenTimer(void)
{
	struct MsgPort *port = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
	if (port == NULL)
	{
		return NULL;
	}

	struct TimeRequest *req = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
		ASOIOR_Size, sizeof(struct TimeRequest),
		ASOIOR_ReplyPort, port,
		TAG_END);

	if (req == NULL)
	{
		IExec->FreeSysObject(ASOT_PORT, port);
		return NULL;
	}

	int8 deverr = IExec->OpenDevice("timer.device", UNIT_MICROHZ,
		&req->Request, 0);

	if (deverr != IOERR_SUCCESS)
	{
		IExec->FreeSysObject(ASOT_IOREQUEST, req);
		IExec->FreeSysObject(ASOT_PORT, port);
		return NULL;
	}

	return req;
}


void
CloseTimer(struct TimeRequest *req)
{
	if (req != NULL)
	{
		struct MsgPort *port = req->Request.io_Message.mn_ReplyPort;

		IExec->CloseDevice(&req->Request);
		IExec->FreeSysObject(ASOT_IOREQUEST, req);
		IExec->FreeSysObject(ASOT_PORT, port);
	}
}

LONG
ReturnRexxMsg(struct RexxMsg * Message, CONST_STRPTR Result)
{
	STRPTR ResultString = NULL;

	/* No error has occured yet. */
	int32 ErrorCode = 0;

	/* Set up the RexxMsg to return no error. */
	Message->rm_Result1 = RC_OK;
	Message->rm_Result2 = 0;

	/* Check if the command should return a result. */
	if((Message->rm_Action & RXFF_RESULT) && Result != NULL)
	{
		/* To return the result string we need to make
		 * a copy for ARexx to use.
		 */
		if((ResultString = IRexxSys->CreateArgstring(Result, strlen(Result))))
		{
			/* Put the string into the secondary
			 * result field.
			 */
			Message->rm_Result2 = (LONG)ResultString;
		}
		else
		{
			/* No memory available. */
			ErrorCode = ERR10_003;
		}
	}

	/* Reply the message, regardless of the error code. */
	IExec->ReplyMsg((struct Message *)Message);

	return(ErrorCode);
}


void
ReturnErrorMsg(struct RexxMsg *msg, CONST_STRPTR port, int32 rc, int32 rc2)
{
	/* To signal an error the rc_Result1
	 * entry of the RexxMsg needs to be set to
	 * RC_ERROR. Unfortunately, we cannot convey
	 * the more meaningful error code through
	 * this interface which is why we set a
	 * Rexx variable to the error number. The
	 * Rexx script can then take a look at this
	 * variable and decide which further steps
	 * it should take.
	 */
	msg->rm_Result1 = rc;
	msg->rm_Result2 = rc2;

	/* Turn the error number into a string as
	 * ARexx only deals with strings.
	 */
	char value[12];
	IUtility->SNPrintf(value, sizeof(value), "%ld", rc2);

	/* Build the name of the variable to set to
	 * the error number. We will use the name of
	 * the host name and append ".LASTERROR".
	 */
	IRexxSys->SetRexxVarFromMsg("RC2", value, msg);

	IExec->ReplyMsg(&msg->rm_Node);
}

BOOL
PutMsgTo(CONST_STRPTR name, struct Message *msg)
{
	BOOL done = FALSE;

	IExec->Forbid();

	struct MsgPort *port = IExec->FindPort(name);
	if (port != NULL)
	{
		IExec->PutMsg(port, msg);
		done = TRUE;
	}

	IExec->Permit();

	return done;
}


STRPTR DoRexx(STRPTR port, STRPTR command, int32 *rc, int32 *rc2)
{
	*rc = 0;
	*rc2 = 0;
	STRPTR result = NULL;
	STRPTR dup = NULL;

	struct MsgPort *replyPort = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
	if (replyPort == NULL)
	{
		return NULL;
	}

	struct RexxMsg *rexxMsg = IRexxSys->CreateRexxMsg(replyPort, NULL, NULL);
	((struct Node *)rexxMsg)->ln_Name = "REXX";
	if (rexxMsg == NULL)
	{
		IExec->FreeSysObject(ASOT_PORT, replyPort);
		return NULL;
	}
	BOOL sent = FALSE;


	rexxMsg->rm_Args[0] = IRexxSys->CreateArgstring(command, strlen(command));

	if (rexxMsg->rm_Args[0] != NULL)
	{
		rexxMsg->rm_Action = RXCOMM | RXFF_RESULT | RXFF_STRING;

		sent = PutMsgTo(port, (struct Message*)rexxMsg);

		if (sent)
		{
			IExec->WaitPort(replyPort);
			(void)IExec->GetMsg(replyPort);
		}
		else
		{

		}

		*rc = rexxMsg->rm_Result1;

		if (*rc == RC_OK)
		{
			if (rexxMsg->rm_Result2 != 0)
			{
				result = (STRPTR)rexxMsg->rm_Result2;
			}
		}
		else
		{
			*rc2 = rexxMsg->rm_Result2;
		}

		IRexxSys->DeleteArgstring(rexxMsg->rm_Args[0]);
		rexxMsg->rm_Args[0] = NULL;
	}

	IRexxSys->DeleteRexxMsg(rexxMsg);
	rexxMsg = NULL;

	IExec->FreeSysObject(ASOT_PORT, replyPort);
	replyPort = NULL;

	if (result != NULL)
	{
		dup = dupstr(result);

		IRexxSys->DeleteArgstring(result);
		result = NULL;
	}

	return dup;
}


struct RexxHost *CreateRexxHost(CONST_STRPTR PortName)
{
	struct RexxHost *newHost = IExec->AllocVecTags(sizeof(struct RexxHost),
	AVT_Type, MEMF_PRIVATE, AVT_ClearWithValue, 0, TAG_DONE);

	if (newHost == NULL)
	{
	return NULL;
	}

	IUtility->Strlcpy(newHost->PortName, PortName, sizeof(newHost->PortName));

	IExec->Forbid();

	/* Check if the name already exists */
	if (IExec->FindPort(PortName) != NULL)
	{
	int32 index = 1;
	do
	{
	IUtility->SNPrintf(newHost->PortName, sizeof(newHost->PortName), "%s.%ld", PortName, index);
	index++;

	if (IExec->FindPort(newHost->PortName) == NULL)
	{
	break;
	}
	} while (1);
	}

	newHost->Port = IExec->AllocSysObjectTags(ASOT_PORT,
	ASOPORT_Name, 	newHost->PortName,
	ASOPORT_Public, TRUE,
	TAG_DONE);

	IExec->Permit();

	if (newHost->Port == NULL)
	{
	IExec->FreeVec(newHost);
	return NULL;
	}

	return newHost;
}


void DeleteRexxHost(struct RexxHost *host)
{
	if (host)
	{
	if (host->Port)
	{
	struct RexxMsg *msg;

	IExec->Forbid();
	while ((msg = (struct RexxMsg *)IExec->GetMsg(host->Port)) != NULL)
	{
	msg->rm_Result1 = RC_FATAL;
	IExec->ReplyMsg((struct Message *)msg);
	}

	IExec->FreeSysObject(ASOT_PORT, host->Port);
	IExec->Permit();
	}

	IExec->FreeVec(host);
	}
}

void WaitRexxHost(struct RexxHost *rexxHost, int timeout)
{

	struct TimeRequest *req = NULL;
	uint32 timermask        = 0;

	if (timeout > 0)
	{
		req = OpenTimer();

		if (req != NULL)
		{
			timermask = 1L << req->Request.io_Message.mn_ReplyPort->mp_SigBit;

			req->Request.io_Command = TR_ADDREQUEST;
			req->Time.Seconds       = 0;
			req->Time.Microseconds  = timeout;

			IExec->SendIO(&req->Request);
		}
	}

	uint32 hostmask = 1L << rexxHost->Port->mp_SigBit;
	uint32 waitmask = timermask | hostmask | SIGBREAKF_CTRL_C;

	uint32 sigmask = IExec->Wait(waitmask);

	if (req != NULL)
	{
		IExec->AbortIO(&req->Request);
		IExec->WaitIO(&req->Request);
		CloseTimer(req);
	}

	if (sigmask & SIGBREAKF_CTRL_C)
	{
		return;
	}


}

struct ARexxMsg *GetMsgRexxHost(struct RexxHost *rexxHost)
{
	struct ARexxMsg *am = NULL;

	struct RexxMsg *rexxMsg = NULL;

	rexxMsg = (struct RexxMsg *)IExec->GetMsg(rexxHost->Port);
	if (rexxMsg != NULL)
	{
		if((am = IExec->AllocVecTags(sizeof(struct ARexxMsg),AVT_Type, MEMF_PRIVATE, AVT_ClearWithValue, 0, TAG_DONE)))
		{
			am->rexxMsg = rexxMsg;
			am->rexxHost = rexxHost;
			am->isReplied = FALSE;
		}

	}
	return am;
}

uint32 GetSignalRexxHost(struct RexxHost *rexxHost)
{
	return rexxHost->Port->mp_SigBit;
}


void ReplyARexxMsg(struct ARexxMsg *am, int rc, int rc2, STRPTR result)
{
	if(am)
	{
		if(!am->isReplied)
		{
			if(rc == 0)
			{
				ReturnRexxMsg(am->rexxMsg, result);
			}
			else
			{
				ReturnErrorMsg(am->rexxMsg, am->rexxHost->PortName,rc,rc2);
			}
			am->isReplied = TRUE;
		}
	}
}

STRPTR GetVarARexxMsg(struct ARexxMsg *am, STRPTR varname)
{
	STRPTR result = IExec->AllocVecTags(256,AVT_Type, MEMF_PRIVATE, AVT_ClearWithValue, 0, TAG_DONE);
	if(result)
	{
		IRexxSys->GetRexxVarFromMsg(varname, result, am->rexxMsg);
	}
	return result;
}

void SetVarARexxMsg(struct ARexxMsg *am, STRPTR varname, STRPTR value)
{
	IRexxSys->SetRexxVarFromMsg(varname, value, am->rexxMsg);
}

void DeleteARexxMsg(struct ARexxMsg *am)
{
	if(!am->isReplied)
	{
		IExec->ReplyMsg(&am->rexxMsg->rm_Node);
		am->isReplied = TRUE;
	}
	IExec->FreeVec(am);
}

STRPTR GetArgsARexxMsg(struct ARexxMsg *am)
{
	return am->rexxMsg->rm_Args[0];
}

MODULE = Amiga::ARexx              PACKAGE = Amiga::ARexx

PROTOTYPES: DISABLE


APTR Host_init(name)
    STRPTR name;
    CODE:
    	RETVAL = CreateRexxHost(name);
    OUTPUT:
    	RETVAL

void Host_delete(rexxhost)
	APTR rexxhost;
	CODE:
		DeleteRexxHost(rexxhost);

void Host_wait(rexxhost,timeout)
	APTR rexxhost
	int timeout
	CODE:
		WaitRexxHost(rexxhost,timeout);

uint32 Host_signal(rexxhost)
	APTR rexxhost
	CODE:
		RETVAL = GetSignalRexxHost(rexxhost);
	OUTPUT:
		RETVAL

APTR Host_getmsg(rexxhost)
	APTR rexxhost
	CODE:
		RETVAL = GetMsgRexxHost(rexxhost);
	OUTPUT:
		RETVAL

void Msg_reply(rexxmsg,rc,rc2,result)
	APTR rexxmsg
	int rc
	int rc2
	STRPTR result
	CODE:
		ReplyARexxMsg(rexxmsg,rc,rc2,result);

void Msg_delete(rexxmsg)
	APTR rexxmsg
	CODE:
		DeleteARexxMsg(rexxmsg);

STRPTR Msg_argstr(rexxmsg)
	APTR rexxmsg
	CODE:
		RETVAL = GetArgsARexxMsg(rexxmsg);
	OUTPUT:
		RETVAL

STRPTR Msg_getvar(rexxmsg,varname)
	APTR rexxmsg
	STRPTR varname
	PPCODE:
		RETVAL = GetVarARexxMsg(rexxmsg,varname);
		sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG;
		if (RETVAL) IExec->FreeVec(RETVAL);

void Msg_setvar(rexxmsg,varname,value)
	APTR rexxmsg
	STRPTR varname
	STRPTR value
	CODE:
		SetVarARexxMsg(rexxmsg,varname,value);

STRPTR _DoRexx(port,command,rc,rc2)
	STRPTR port
	STRPTR command
	int32 &rc
	int32 &rc2
	PPCODE:
		RETVAL = DoRexx(port,command,&rc,&rc2);
		sv_setiv(ST(2), (IV)rc);
		SvSETMAGIC(ST(2));
		sv_setiv(ST(3), (IV)rc2);
		SvSETMAGIC(ST(3));
		sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG;
		IExec->FreeVec(RETVAL);