#include "SimpleRexx.h"
#include <proto/exec.h>
#undef NULL
#include "config.h"
#include "lisp.h"

#include "amiga.h"

static AREXXCONTEXT far handle;

#define REXXSIZE 32

static struct {
  int rc; /* 0 for commands, <> 0, for errors */
  union {
    struct { int id; int code; } error; /* Of failed messages */
    struct RexxMsg *msg; /* Received command */
  } u;
} pending_rexx_msgs[REXXSIZE];
static int pending_rexx_num, pending_rexx_in, pending_rexx_out;
static int amiga_arexx_initialized;

static struct {
  struct RexxMsg *msg;
  int id;
} sent_rexx_msg[REXXSIZE];
static int sent_rexx_id;

int check_arexx(int force, int kbd)
{
  struct RexxMsg *rmsg;
  int msg_received = FALSE;

  while (rmsg = GetARexxMsg(handle))
    {
      msg_received = TRUE;

      if (rmsg->rm_Node.mn_Node.ln_Type == NT_REPLYMSG)
	{
	  int i;

	  /* The message has been returned, remove it from sent messages */
	  for (i = 0; i < REXXSIZE && sent_rexx_msg[i].msg != rmsg; i++) ;
	  if (i < REXXSIZE) sent_rexx_msg[i].msg = 0;

	  if (rmsg->rm_Result1)
	    {
	      /* There was an error, add it to pending_rexx_msgs */
	      if (pending_rexx_num != REXXSIZE)
		{
		  pending_rexx_num++;
		  pending_rexx_msgs[pending_rexx_in].u.error.id =
		    i < REXXSIZE ? sent_rexx_msg[i].id : 0;
		  pending_rexx_msgs[pending_rexx_in].u.error.code = rmsg->rm_Result2;
		  pending_rexx_msgs[pending_rexx_in].rc = rmsg->rm_Result1;
		  pending_rexx_in = (pending_rexx_in + 1) % REXXSIZE;
		}
	      /* else ignore this error */
	    }
	  DeleteARexxMsg(handle, rmsg);
	}
      else
	{
	  if (pending_rexx_num == REXXSIZE)
	    {
	      /* Oops! Throw out message */
	      SetARexxLastError(handle, rmsg, "Emacs too busy");
	      ReplyARexxMsg(handle, rmsg, 0, 20);
	    }
	  else
	    {
	      pending_rexx_num++;
	      pending_rexx_msgs[pending_rexx_in].u.msg = rmsg;
	      pending_rexx_msgs[pending_rexx_in].rc = 0;
	      pending_rexx_in = (pending_rexx_in + 1) % REXXSIZE;
	    }
	}
    }
  if (kbd && amiga_arexx_initialized && (msg_received || force && pending_rexx_num > 0))
    {
      enque(AMIGASEQ, FALSE); enque('X', FALSE);
    }
  return msg_received;
}

DEFUN ("amiga-arexx-wait", Famiga_arexx_wait, Samiga_arexx_wait, 0, 0, 0,
  "Wait for an ARexx event (command or reply) before proceeding.")
  ()
{
    while (!check_arexx(FALSE, FALSE)) Wait(ARexxSignal(handle));
}

DEFUN ("amiga-arexx-check-command",
       Famiga_arexx_check_command, Samiga_arexx_check_command, 1, 1, 0,
       "Return t if command ID has finished, nil otherwise.")
     (id)
{
  int i, nid;

  CHECK_NUMBER (id, 0);
  nid = XUINT (id);

  for (i = 0; i < REXXSIZE && (!sent_rexx_msg[i].msg || nid != sent_rexx_msg[i].id);
       i++) ;

  return i == REXXSIZE ? Qnil : Qt;
}

DEFUN ("amiga-arexx-get-event", Famiga_arexx_get_event, Samiga_arexx_get_event,
  0, 0, 0,
  "Returns next arexx event, either an error or a command to execute.\n\
If no event is waiting, nil is returned.\n\
Errors are returned as a (id-of-failed-command severity error-code) list\n\
(Don't answer these events!).\n\
Commands are strings sent by an arexx process. They should be answered via\n\
amiga-arexx-reply. amiga-arexx-get-event will always return the same command\n\
till you do so.")
  ()
{
  struct RexxMsg *rmsg;

  check_arexx(FALSE, FALSE);
  if (pending_rexx_num)
    if (pending_rexx_msgs[pending_rexx_out].rc)
      {
	Lisp_Object id, error, rc;
	Lisp_Object res;

	XSET (id, Lisp_Int, pending_rexx_msgs[pending_rexx_out].u.error.id);
	XSET (error, Lisp_Int,
	      pending_rexx_msgs[pending_rexx_out].u.error.code & VALMASK);
	XSET (rc, Lisp_Int, pending_rexx_msgs[pending_rexx_out].rc & VALMASK);
	res = Fcons (id, Fcons (rc, Fcons (error, Qnil)));

	pending_rexx_out = (pending_rexx_out + 1) % REXXSIZE;
	pending_rexx_num--;

	return res;
      }
    else return build_string(ARG0(pending_rexx_msgs[pending_rexx_out].u.msg));

  return Qnil;
}

DEFUN ("amiga-arexx-reply", Famiga_arexx_reply, Samiga_arexx_reply,
  2, 2, 0,
"Replies to the first arexx message (the one got via amiga-arexx-get-event)\n\
with RC as return code.\n\
If RC=0, TEXT is the result, otherwise it is the error text. It can be nil.")
  (rc, text)
{
    int retcode;
    char *result;
    struct RexxMsg *rmsg;
    int ok = TRUE;

    if (!pending_rexx_num) error("No ARexx message to reply to.");
    CHECK_NUMBER(rc, 0);
    retcode = XINT(rc);

    if (!NULL (text))
    {
	CHECK_STRING(text, 0);
	result = XSTRING (text)->data;
    }
    else result = 0;

    if (pending_rexx_msgs[pending_rexx_out].rc)
      error("You can't answer an error !");
    rmsg = pending_rexx_msgs[pending_rexx_out].u.msg;
    pending_rexx_out = (pending_rexx_out + 1) % REXXSIZE;
    pending_rexx_num--;

    if (retcode && result) 
	ok = SetARexxLastError(handle, rmsg, result);
    ReplyARexxMsg(handle, rmsg, result, retcode);

    if (!ok) error("Failed to set ARexx error message.");

    return Qnil;
}

DEFUN ("amiga-arexx-send-command", Famiga_arexx_send_command, Samiga_arexx_send_command, 
  1, 2, "sARexx command: \n\
P",
  "Sends a command to ARexx for execution.\n\
If the second arg is non-nil, the command is directly interpreted.\n\
Returns an integer that uniquely identifies this message (for use in ???).")
  (str, as_file)
{
  struct ARexxMsg *rmsg;
  int i;
  Lisp_Object id;

  /* Find a free slot for message */
  for (i = 0; i < REXXSIZE && sent_rexx_msg[i].msg; i++) ;
  if (i == REXXSIZE) error("Too many arexx commands pending (max %d)", REXXSIZE);

  CHECK_STRING (str, 0);
  if (!(rmsg = SendARexxMsg(handle, XSTRING (str)->data, !NULL (as_file))))
    error("Failed to send command to ARexx.");

  sent_rexx_msg[i].msg = rmsg;
  sent_rexx_id = (sent_rexx_id + 1) & VALMASK;
  sent_rexx_msg[i].id = sent_rexx_id;


  XSET (id, Lisp_Int, sent_rexx_id);
  return id;
}

void init_amiga_rexx(void)
{
    extern ULONG inputsig;
    int i;

    handle = InitARexx("Emacs", "elx");
    inputsig |= ARexxSignal(handle);
    pending_rexx_num = pending_rexx_in = pending_rexx_out = 0;
    for (i = 0; i < REXXSIZE; i++) sent_rexx_msg[i].msg = 0;
    sent_rexx_id = 0;
}

void cleanup_amiga_rexx(void)
{
    FreeARexx(handle);
}

void syms_of_amiga_rexx(void)
{
    DEFVAR_BOOL ("amiga-arexx-initialized", &amiga_arexx_initialized,
		 "Set this to t when Emacs is ready to respond to ARexx messages.\n\
(ie C-\ X causes all pending ARexx messages to be answered)");
    amiga_arexx_initialized = 0;
    defsubr(&Samiga_arexx_send_command);
    defsubr(&Samiga_arexx_reply);
    defsubr(&Samiga_arexx_get_event);
    defsubr(&Samiga_arexx_check_command);
    defsubr(&Samiga_arexx_wait);
}
