/*
** $PROJECT: GoldED Phrase-Completion API-Client
**
** $VER: PhraseCompletion.c 1.4 (27.04.96)
**
** by
**
** Stefan Ruppert , Windthorststrasse 5 , 65439 Floersheim , GERMANY
**
** (C) Copyright 1996
** All Rights Reserved !
**
** $HISTORY:
**
** 27.04.96 : 001.004 : completion didn't worded, if word was at the beginning of a line (fixed)
** 14.04.96 : 001.003 : now uses WHITESPACES arg to get the word to expand
** 24.02.96 : 001.002 : searches now in the line of the cursor
** 09.02.96 : 001.001 : separated in a own program
**
*/

/*FS*//* --------------------------- version defines ---------------------------- */

/* $STARTDEFINE: "BumpRev defines"*/
#define VERSION  1
#define REVISION 4
#define DATE "27.4.96"
#define VERS "PhraseCompletion 1.4"
#define VSTRING "PhraseCompletion 1.4 (27.4.96)\r\n"
#define VERSTAG "\0$VER: PhraseCompletion 1.4 (27.4.96)"
#define NAME "PhraseCompletion"
#define PROJECTNAME "PhraseCompletion: "

/* $ENDDEFINE:   */
/*FE*/

/*FS*//* ------------------------------- include -------------------------------- */

#define __USE_SYSBASE

#include <string.h>
#include <ctype.h>

#include <exec/types.h>
#include <exec/memory.h>

#include <dos/dos.h>

#include <rexx/errors.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/utility.h>
#include <proto/rexxsyslib.h>

#include <clib/alib_protos.h>
#include <clib/alib_stdio_protos.h>

#include "golded:api/include/golded.h"

#include "debug.h"
#include "register.h"
/*FE*/

/*FS*//* ------------------------------- AutoDoc -------------------------------- */

/*GB*** GoldED-API/PharseCompletion ******************************************

	 $VER: PharseCompletion.doc 1.4 (27.4.96)

	 NAME
		  PhraseCompletion

	 VERSION
		  1.4

	 DESCRIPTION
		  Just run GoldED and load this as an API-Client ! Then you have the
		  following new ARexx command :

	 TEMPLATE
		  PHRASECOMPLETION WHITESPACES,MAX/N,CASE/S,BACKWARD/S,ALL/S

	 FORMAT
		  PHRASECOMPLETION [[WHITESPACES] whitespace-characters] [[MAX] number]
								 [CASE] [BACKWARD] [ALL]

	 DESCRIPTION
		  The PHRASECOMPLETION ARexx command provides you a new phrase
		  completion method. It extracts the actual word-phrase (before cursor)
		  and tries to complete this phrase. This is done by searching in the
		  buffers for a word beginning with this extracted phrase. The first
		  found is displayed in the buffer. If there are more than one words
		  matching the phrase, you can step through all found words by just
		  using this command again with same cursor position. If the last word
		  is reached a ERROR_NO_MORE_ENTRIES error code is returned and then it
		  starts again with the first word.

	 INPUTS
		  WHITESPACES -- a string, which represent the characters to use as
				whitespaces. Default is " \t".

		  MAX -- maximal number of matching words. Default is ~0.

		  CASE -- don't ignore case-sensetive. Default is ignore.

		  BACKWARD -- normaly the buffer is first searched from the actual
				line to the beginnig and then from actual line to the end of
				the buffer. This argument swaps the search direction.

		  ALL -- searchs in all opened buffers

	 RESULTS
		  the command returns RC_OK, if it could complete the pharse. RC_WARN
		  if not !

	 SPECIAL REQUIREMENTS
		  Needs GoldED 3.x to run.

	 AVAILABILITY
		  ftp://wuarchive.wustl.edu/pub/aminet/text/edit/GED-PhrCmp1.4.lha
		  And all other Aminet sites

	 AUTHOR
		  Stefan Ruppert
		  Windthorststrasse 5
		  65439 Floersheim
		  Germany

		  Internet:
		  ruppert@informatik.fh-wiesbaden.de
		  ruppert@goofy.zdv.uni-mainz.de
		  http://www.uni-mainz.de/~ruppert

	 SEE ALSO
		  Emacs, GoldED/API

*****************************************************************************/
/*FE*/

/*FS*//* ------------------------------- defines -------------------------------- */

#define PUDDLE_SIZE              4096
#define BUFFER_LEN               512

#define PHRASECOMP_ARGS          "WHITESPACES,MAX/N,CASE/S,BACKWARD/S,ALL/S"

#define MAX_PARAMETER            6

#define EOS                      '\0'

#define USETAG(tag,check)        ((check) ? (tag) : TAG_IGNORE)

/*FE*/

/*FS*//* ----------------------------- structures ------------------------------- */

struct RexxCommands
{
	STRPTR rxc_Command;
	LONG (*rxc_Handler) (struct GlobalData *,struct APIMessage *);
	STRPTR rxc_Template;
};

struct GlobalData
{
	struct Library *gd_SysBase;
	struct Library *gd_DOSBase;
	struct Library *gd_UtilityBase;
	struct Library *gd_RexxSysBase;

	APTR gd_Pool;
	APTR gd_FoundPool;

	struct MsgPort *gd_ApiPort;
	struct MsgPort *gd_RexxPort;

	STRPTR gd_GoldEDHost;

	ULONG gd_Result;
	LONG gd_Found;

	struct List gd_FoundList;
	struct Node *gd_LastFound;

	ULONG gd_Para[MAX_PARAMETER];

	ULONG gd_Len;
	ULONG gd_Max;
	UBYTE gd_Buffer[BUFFER_LEN];
	UBYTE gd_Name[BUFFER_LEN];
	UBYTE gd_LastName[BUFFER_LEN];

	struct APIModifyRequest gd_Modify;
};
/*FE*/

/*FS*//* ------------------------------ prototypes ------------------------------ */

void search_command(struct GlobalData *gd,struct APIMessage *msg);

LONG RexxCmd_PhraseComp(struct GlobalData *gd,struct APIMessage *msg);

BOOL send_rexxcommand(struct GlobalData *gd,STRPTR cmd);
BOOL alloc_name(struct GlobalData *gd,STRPTR name,LONG len);

RegCall void *AsmCreatePool(REGD0 ULONG ,REGD1 ULONG ,REGD2 ULONG ,REGA6 struct Library *);
RegCall void AsmDeletePool(REGA0 APTR ,REGA6 struct Library *);
RegCall void *AsmAllocPooled(REGA0 APTR ,REGD0 ULONG ,REGA6 struct Library *);
RegCall void AsmFreePooled(REGA0 APTR ,REGA1 APTR ,REGD0 ULONG ,REGA6 struct Library *);

/*FE*/

/*FS*//* -------------------------- static data items --------------------------- */

static const STRPTR version = VERSTAG;
static const STRPTR prgname = NAME;

static struct RexxCommands rexxcmd[] = {
	{"PHRASECOMPLETION",RexxCmd_PhraseComp,PHRASECOMP_ARGS},
	{NULL}};

static struct TagItem apitags[] = {
	{API_Client_Name       ,(ULONG) NAME},
	{API_Client_Copyright  ,(ULONG) "©1996 Stefan Ruppert"},
	{API_Client_Purpose    ,(ULONG) VERS "\n\nPhrase completion\nthrough all buffers"},
	{API_Client_Template   ,(ULONG) "PHRASECOMPLETION " PHRASECOMP_ARGS},
	{TAG_DONE,0}};
/*FE*/

/*FS*//* ------------------------- template definition -------------------------- */

#define template    "HOST/K/A"

enum {
	ARG_HOST,
	ARG_MAX};
/*FE*/

/* --------------------------- main entry point --------------------------- */

/*FS*/GetA4 int main(void)
{
	struct Library *SysBase = *((struct Library **) 4L);
	struct Library *DOSBase;
	struct RDArgs *args;
	LONG para[ARG_MAX];
	STRPTR obj = prgname;
	LONG rc = RETURN_OK;

	LONG i;

	/* clear args buffer */
	for(i = 0 ; i < ARG_MAX ; i++)
		para[i] = 0;

	if((DOSBase = OpenLibrary("dos.library",37)) != NULL)
	{
		LONG err = 0;

		if((args = ReadArgs(template,para,NULL)) != NULL)
		{
			APTR pool;

			if((pool = AsmCreatePool(MEMF_CLEAR | MEMF_ANY, PUDDLE_SIZE, PUDDLE_SIZE,SysBase)) != NULL)
			{
				struct GlobalData *gd;     

				DB(("golded host : %s\n",para[ARG_HOST]));
				if((gd = AsmAllocPooled(pool,sizeof(struct GlobalData),SysBase)) != NULL)
				{
					gd->gd_Pool = pool;

					gd->gd_SysBase = SysBase;
					gd->gd_DOSBase = DOSBase;

					gd->gd_UtilityBase   = OpenLibrary("utility.library",37);
					gd->gd_RexxSysBase   = OpenLibrary("rexxsyslib.library",36);

					NewList(&gd->gd_FoundList);

					if(gd->gd_UtilityBase != NULL && gd->gd_RexxSysBase != NULL)
					{
						if((gd->gd_ApiPort = CreateMsgPort()))
						{
							if((gd->gd_RexxPort = CreateMsgPort()))
							{
								gd->gd_GoldEDHost   = (STRPTR)   para[ARG_HOST];

								sprintf(gd->gd_Buffer,"API PORT=%ld CLASS=%ld",
															 gd->gd_ApiPort,
															 API_CLASS_ROOT | API_CLASS_REXX);

								if(send_rexxcommand(gd,gd->gd_Buffer) && gd->gd_Result == RC_OK)
								{
									struct APIMessage *apimsg;
									struct APIMessage *msg;

									BOOL active = TRUE;

									DB(("xrefapi active !\n"));

									do
									{
										WaitPort(gd->gd_ApiPort);

										while((apimsg = (struct APIMessage *) GetMsg(gd->gd_ApiPort)))
										{
											for(msg = apimsg ; msg ; msg = msg->api_Next)
											{
												if(msg->api_State == API_STATE_NOTIFY)
												{
													switch(msg->api_Class)
													{
													case API_CLASS_ROOT:
														switch(msg->api_Action)
														{
														case API_ACTION_DIE:
															active = FALSE;
															break;
														case API_ACTION_INTRODUCE:
															msg->api_Data = apitags;
															break;
														}
														break;
													case API_CLASS_REXX:
														switch(msg->api_Action)
														{
														case API_ACTION_COMMAND:
															search_command(gd,msg);
															break;
														default:
															msg->api_Error = API_ERROR_UNKNOWN;
														}
														break;
													default:
														msg->api_Error = API_ERROR_UNKNOWN;
													}
												}
											}

											ReplyMsg((struct Message *) apimsg);
										}
									} while(active);
									SetIoErr(0);
								}
								DeleteMsgPort(gd->gd_RexxPort);
							}
							DeleteMsgPort(gd->gd_ApiPort);
						}

						if(gd->gd_FoundPool != NULL)
							AsmDeletePool(gd->gd_FoundPool,SysBase);
					}

					CloseLibrary(gd->gd_RexxSysBase);
					CloseLibrary(gd->gd_UtilityBase);
				}

				AsmDeletePool(pool,SysBase);
			}
			FreeArgs(args);
		}

		if(err == 0)
			err = IoErr();

		if(err != 0)
		{
			PrintFault(err,obj);
			rc = RETURN_ERROR;
		}

		CloseLibrary(DOSBase);
	}

	return rc;
}
#define SysBase         gd->gd_SysBase
#define DOSBase         gd->gd_DOSBase
#define UtilityBase     gd->gd_UtilityBase
#define RexxSysBase     gd->gd_RexxSysBase
/*FE*/

/* -------------------------- find rexx command --------------------------- */

/*FS*/ void search_command(struct GlobalData *gd,struct APIMessage *msg)
{
	struct APIRexxNotify *notify = (struct APIRexxNotify *) msg->api_Data;
	struct RexxCommands  *rxcmd  = rexxcmd;
	LONG len;

	notify->arn_RC = RC_WARN;

	while(rxcmd->rxc_Command)
	{
		len = strlen(rxcmd->rxc_Command);

		if(!strncmp(notify->arn_Command,rxcmd->rxc_Command,len))
		{
			struct RDArgs *rdargs;
			struct RDArgs *args;
			LONG i;

			msg->api_State = API_STATE_CONSUMED;

			strncpy(gd->gd_Buffer,&notify->arn_Command[len],BUFFER_LEN);
			strncat(gd->gd_Buffer,"\n",BUFFER_LEN);

			if(rdargs = (struct RDArgs *) AllocDosObject(DOS_RDARGS,NULL))
			{
				rdargs->RDA_Source.CS_Buffer   = gd->gd_Buffer;
				rdargs->RDA_Source.CS_Length   = strlen(gd->gd_Buffer);

				for(i=0 ; i < MAX_PARAMETER ; i++)
					gd->gd_Para[i]=0;

				DB(("rdargs at : %lx\n",rdargs));

				if((args = ReadArgs(rxcmd->rxc_Template,(LONG *) gd->gd_Para,rdargs)))
				{
					DB(("args at %lx\n",args));

					notify->arn_RC = rxcmd->rxc_Handler(gd,msg);

					FreeArgs(args);
				}
				FreeDosObject(DOS_RDARGS , rdargs);
			}
		}
		rxcmd++;
	}

	if(notify->arn_RC != RC_OK)
	{
		Fault(IoErr(),NAME,gd->gd_Buffer,BUFFER_LEN);
		notify->arn_CommandError = gd->gd_Buffer;
	}
}
/*FE*/

/* ---------------------------- rexx commands ----------------------------- */

/*FS*//*"LONG RexxCmd_PhraseComp(struct GlobalData *gd,struct APIMessage *msg)"*/
struct IntArgs
{
	STRPTR ia_WhiteSpaces;
	LONG *ia_Max;
	LONG ia_Case;
	LONG ia_Backward;
	LONG ia_All;
};
/*FS*/ void match_expand(struct GlobalData *gd,struct IntArgs *args,struct LineNode *ln)
{
	STRPTR name;
	STRPTR ptr;
	STRPTR end;
	LONG len;

	ptr = ln->Text;
	end = ptr + ln->Len;

	if(args->ia_Case)
	{
		do
		{
			while(ptr < end && strchr(args->ia_WhiteSpaces,*ptr) != NULL)
				ptr++;

			name = ptr;
			while(ptr < end && strchr(args->ia_WhiteSpaces,*ptr) == NULL)
				ptr++;

			len = ptr - name;
			if(len > gd->gd_Len)
			{
				if(!strncmp(name,gd->gd_Buffer,gd->gd_Len))
				{
					alloc_name(gd,name,len);
					if(--gd->gd_Max == 0)
						break;
				}
			}
		} while(ptr < end);
	} else
	{
		do
		{
			while(ptr < end && strchr(args->ia_WhiteSpaces,*ptr) != NULL)
				ptr++;

			name = ptr;
			while(ptr < end && strchr(args->ia_WhiteSpaces,*ptr) == NULL)
				ptr++;

			len = ptr - name;
			if(len > gd->gd_Len)
			{
				if(!Strnicmp(name,gd->gd_Buffer,gd->gd_Len))
				{
					alloc_name(gd,name,len);
					if(--gd->gd_Max == 0)
						break;
				}
			}
		} while(ptr < end);
	}

	if(ln->Fold != NULL)
	{
		struct LineNode *end;
		len = ln->Fold->Lines;
		ln  = ln->Fold->TextNodes;

		end = ln + len;
		while(ln < end && gd->gd_Max > 0)
		{
			match_expand(gd,args,ln++);
		}
	}

	return;
}
/*FE*/

LONG RexxCmd_PhraseComp(struct GlobalData *gd,struct APIMessage *msg)
{
	struct EditConfig *ec = msg->api_Config;
	struct LineNode *ln;
	struct IntArgs *args = (struct IntArgs *) gd->gd_Para;

	UWORD  column;
	STRPTR wrd;
	STRPTR buf;
	LONG retval = RC_WARN;

	gd->gd_Max = ~0;
	if(args->ia_Max != NULL)
		gd->gd_Max = *args->ia_Max;

	column = ec->Column;
	buf    = ec->CurrentBuffer;
	wrd    = gd->gd_Buffer;

	if(column > 0)
	{
		STRPTR found = NULL;
		STRPTR end   = buf + column;
		STRPTR begin = end - 1;
		LONG foundlen;
		LONG len;

		while(begin >= buf && strchr(args->ia_WhiteSpaces,*begin) == NULL)
			begin--;
		begin++;
		column -= (end - begin);

		len = end - begin;

		if(len == 0)
			SetIoErr(ERROR_REQUIRED_ARG_MISSING);
		else
		{
			movmem(begin,wrd,len);
			wrd[len] = 0;
			gd->gd_Len = len;

			DB(("expand : %s\n",wrd));

			if(!IsListEmpty(&gd->gd_FoundList) && gd->gd_LastName[0] != '\0' &&
				!strncmp(gd->gd_LastName,wrd,strlen(gd->gd_LastName)))
			{
				struct Node *node = gd->gd_LastFound;

				DB(("Get Next word : %lx\n",node));
				if(node != NULL)
				{
					node = node->ln_Succ;
					if(node->ln_Succ != NULL)
						gd->gd_LastFound = node;
					else
						gd->gd_LastFound = NULL;
				} else
					gd->gd_LastFound = gd->gd_FoundList.lh_Head;

				if(gd->gd_LastFound != NULL)
				{
					found    = gd->gd_LastFound->ln_Name;
					foundlen = strlen(found);
				} else
					SetIoErr(ERROR_NO_MORE_ENTRIES);
			} else
			{
				LONG try = 2;

				strcpy(gd->gd_LastName,wrd);
				if(gd->gd_FoundPool != NULL)
					AsmDeletePool(gd->gd_FoundPool,SysBase);
				NewList(&gd->gd_FoundList);
				gd->gd_LastFound = NULL;
				gd->gd_FoundPool = AsmCreatePool(MEMF_ANY | MEMF_CLEAR,PUDDLE_SIZE,PUDDLE_SIZE,SysBase);

				if(args->ia_WhiteSpaces == NULL)
					args->ia_WhiteSpaces = " \t";

				while(try > 0 && gd->gd_Max > 0)
				{
					try--;
					if(args->ia_Backward)
					{
						struct LineNode *end = ec->TextNodes + ec->Lines;
						ln = &ec->TextNodes[ec->Line]+1;
						while(ln < end && gd->gd_Max > 0)
							match_expand(gd,args,ln++);
					} else
					{
						ULONG len;

						ln = &ec->TextNodes[ec->Line];
						/* cut current line to the cursor pos */
						len = ln->Len;
						ln->Len = ec->Column-1;
						match_expand(gd,args,ln);
						ln->Len = len;
						ln--;

						while(ln >= ec->TextNodes && gd->gd_Max > 0)
							match_expand(gd,args,ln--);
					}
					args->ia_Backward = !args->ia_Backward;
				}

				if(args->ia_All && gd->gd_Max > 0)
				{
					struct EditConfig *first = ec;

					while(first->Node.ln_Pred != NULL)
						first = (struct EditConfig *) first->Node.ln_Pred;

					first = (struct EditConfig *) first->Node.ln_Succ;

					while(first->Node.ln_Succ != NULL && gd->gd_Max > 0)
					{
						struct LineNode *end = first->TextNodes + first->Lines;
						DB(("name : %s\n",first->Name));

						if(first != ec)
						{
							ln = first->TextNodes;
							while(ln < end && gd->gd_Max > 0)
								match_expand(gd,args,ln++);
						}

						first = (struct EditConfig *) first->Node.ln_Succ;
					}

				}

				if(gd->gd_FoundList.lh_Head->ln_Succ != NULL)
				{
					struct Node *node = gd->gd_FoundList.lh_Head;

	#if 0
					if(args->ia_Select)
						node = select_node(gd,&gd->gd_FoundList,msg->api_Global->F_ScrnName);
	#endif

					if(node != NULL)
					{
						gd->gd_LastFound = node;
						found = node->ln_Name;
						foundlen = strlen(found);
					} else
					  SetIoErr(ERROR_OBJECT_NOT_FOUND);
				} else
				  SetIoErr(ERROR_OBJECT_NOT_FOUND);
			}

			DB(("found = \"%s\" , len : %ld\n",found,foundlen));
			if(found != NULL && foundlen > 0)
			{
				strncpy(gd->gd_LastName,found,foundlen);
				gd->gd_LastName[foundlen] = '\0';

				strncpy(gd->gd_Buffer,ec->CurrentBuffer,column);
				strncpy(&gd->gd_Buffer[column],found,foundlen);
				strcpy(&gd->gd_Buffer[column + foundlen],ec->CurrentBuffer + ec->Column);
				gd->gd_Modify.mr_Next   = NULL;
				gd->gd_Modify.mr_Line   = ec->Line;
				gd->gd_Modify.mr_Column = column + foundlen;
				gd->gd_Modify.mr_Data   = gd->gd_Buffer;
				gd->gd_Modify.mr_Size   = column + foundlen + (ec->CurrentLen - ec->Column);

				msg->api_Modify = &gd->gd_Modify;

				msg->api_Refresh |= API_REFRESH_LINE;

				retval = RC_OK;
			}
		}
	}

	return retval;
}
/*FE*/

/* -------------------------- support functions --------------------------- */

/*FS*/BOOL send_rexxcommand(struct GlobalData *gd,STRPTR cmd)
{
	struct MsgPort *rexxport;

	Forbid();

	if((rexxport = FindPort(gd->gd_GoldEDHost)))
	{
		struct RexxMsg *rxmsg;
		struct RexxMsg *answer;

		if((rxmsg = CreateRexxMsg(gd->gd_RexxPort, NULL, NULL)) != NULL)
		{
			if((rxmsg->rm_Args[0] = CreateArgstring(cmd, strlen(cmd))) != NULL)
			{
				rxmsg->rm_Action = RXCOMM | RXFF_RESULT;

				PutMsg(rexxport, &rxmsg->rm_Node);

				do
				{
					WaitPort(gd->gd_RexxPort);

					if((answer = (struct RexxMsg *) GetMsg(gd->gd_RexxPort)))
						 gd->gd_Result = answer->rm_Result1;

				} while (!answer);

				Permit();

				if(answer->rm_Result1 == RC_OK)
				{
					if(answer->rm_Result2)
					{
						if(gd->gd_Buffer)
							 strcpy(gd->gd_Buffer, (char *)answer->rm_Result2);

						DeleteArgstring((char *)answer->rm_Result2);
					}
				}

				DeleteArgstring((char *)ARG0(answer));

				DeleteRexxMsg(answer);

				return(TRUE);
			}
		}
	}

	Permit();

	return(FALSE);
}
/*FE*/
/*FS*/ BOOL alloc_name(struct GlobalData *gd,STRPTR name,LONG len)
{
	struct Node *node;
	BOOL rc = FALSE;

	for(node = gd->gd_FoundList.lh_Head ; node->ln_Succ != NULL ; node = node->ln_Succ)
		if(node->ln_Type == len && !strncmp(node->ln_Name,name,len))
			break;

	if(node->ln_Succ == NULL)
		if((node = AsmAllocPooled(gd->gd_FoundPool,sizeof(struct Node) + len + 1,SysBase)) != NULL)
		{
			node->ln_Name = (STRPTR) (node + 1);
			node->ln_Type = len;

			strncpy(node->ln_Name,name,len);

			AddTail(&gd->gd_FoundList,node);

			rc = TRUE;
		  DB(("allocated : %s\n",node->ln_Name));
		}


	return rc;
}
/*FE*/

