/*
** $PROJECT: ProgED Phrase-Completion API-Client
**
** $VER: PhraseCompletion.c 1.2 (06.07.96)
**
** by
**
** Stefan Ruppert , Windthorststrasse 5 , 65439 Floersheim , GERMANY
**
** (C) Copyright 1996
** All Rights Reserved !
**
** $HISTORY:
**
** 06.07.96 : 001.002 : some bugs fixed
** 14.02.96 : 001.001 : initial
**
*/

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

/* $STARTDEFINE: "BumpRev defines"*/
#define VERSION  1
#define REVISION 2
#define DATE "6.7.96"
#define VERS "PhraseCompletion 1.2"
#define VSTRING "PhraseCompletion 1.2 (6.7.96)\r\n"
#define VERSTAG "\0$VER: PhraseCompletion 1.2 (6.7.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 "proged:sources/include/ped.h"

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

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

/*GB*** ProgED-API/PharseCompletion ******************************************

	 $VER: PharseCompletion.doc 1.2 (6.7.96)

	 NAME
		  PhraseCompletion for ProgED 2.x (or above)

	 FUNCTION
		  Just run ProgED 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]

	 FUNCTION
		  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

		  MAX -- maximal number of matching words

		  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 direcition.

		  ALL -- searchs in all opened buffers

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

	 SEE ALSO
		  Emacs, ProgED/API

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

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

#define PUDDLE_SIZE              4096
#define BUFFER_LEN               512

#define EOS                      '\0'

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

/*FE*/

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

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_ReplyPort;

	STRPTR gd_ProgEDHost;

	ULONG gd_Result;
	LONG gd_Found;

	struct List gd_FoundList;
	struct Node *gd_LastFound;

	ULONG gd_Len;
	ULONG gd_Max;
	UBYTE gd_Buffer[BUFFER_LEN];
	UBYTE gd_Name[BUFFER_LEN];
	UBYTE gd_LastName[BUFFER_LEN];
};
/*FE*/

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

RegCall LONG PhraseComp(REGA0 struct CommandData *cmd);

BOOL send_apimessage(struct GlobalData *gd,struct APIMessage *msg);
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 GlobalData *gdata = NULL;
/*FE*/

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

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

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

		DB(("proged api started!\n"));

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

			if((gd = AsmAllocPooled(pool,sizeof(struct GlobalData),SysBase)) != NULL)
			{
				gdata = gd;

				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);

				gd->gd_ProgEDHost  = "PED_API";

				NewList(&gd->gd_FoundList);

				if(gd->gd_UtilityBase != NULL && gd->gd_RexxSysBase != NULL)
				{
					if((gd->gd_ReplyPort = CreateMsgPort()) != NULL)
					{
						struct APIClient Client;
						struct ArexxExtCmds Command;
						struct APIMessage msg;

						Client.ac_ClientPort = gd->gd_ReplyPort;
						Client.ac_Notify     = 0;
						Client.ac_name       = "Pharse Completion";
						Client.ac_Next       = NULL;

						msg.am_MsgType   = PED_API_REGISTER;
						msg.am_MsgArg[0] = (ULONG) &Client;

						if(send_apimessage(gd,&msg))
						{
							struct APIMessage *apimsg;
							BOOL active = TRUE;

							Command.External = TRUE;
							Command.Name     = "PHRASECOMPLETION";
							Command.Template = "WHITESPACES,MAX/N,CASE/S,BACKWARD/S,ALL/S";
							Command.Defaults[0] = " \t\n\"";
							Command.Defaults[1] = (APTR) NULL;
							Command.Defaults[2] = (APTR) FALSE;
							Command.Defaults[3] = (APTR) FALSE;
							Command.Defaults[4] = (APTR) TRUE;
							Command.CommFunc    = PhraseComp;
							Command.NextCmd     = NULL;

							msg.am_MsgType   = PED_API_ADD_INTERNAL_COMMAND;
							msg.am_MsgArg[0] = (ULONG) &Command;

							if(send_apimessage(gd,&msg))
							{
								DB(("ped phrase completion api active !\n"));

								do
								{
									WaitPort(gd->gd_ReplyPort);

									while((apimsg = (struct APIMessage *) GetMsg(gd->gd_ReplyPort)))
									{
										switch(apimsg->am_MsgType)
										{
										case PED_API_QUIT:
											active = FALSE;
											break;
										}

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

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

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

			AsmDeletePool(pool,SysBase);
		}

		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*/

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

/*FS*//*"LONG PhraseComp(REGA0 struct CommandData *cmd)"*/
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 Line *ln)
{
	STRPTR name;
	STRPTR ptr;
	LONG len;

	ptr = ln->Buffer;

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

			name = ptr;
			while(*ptr != '\0' && 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 != '\0');
	} else
	{
		do
		{
			while(*ptr != '\0' && strchr(args->ia_WhiteSpaces,*ptr) != NULL)
				ptr++;

			name = ptr;
			while(*ptr != '\0' && 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 != '\0');
	}

	return;
}
/*FE*/

RegCall GetA4 LONG PhraseComp(REGA0 struct CommandData *cmd)
{
	struct GlobalData *gd = gdata;
	struct IntArgs *args = (struct IntArgs *) cmd->CommandArgs;
	struct PEDWindow *pwin = cmd->CurrentWindow;
	struct PEDText *ptext  = pwin->Text;
	struct Line *ln = ptext->FirstLine;
	LONG retval = RC_WARN;

	UWORD  column = pwin->CursorCol;
	STRPTR buf    = pwin->Line->Buffer;
	STRPTR 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";

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

				while(try > 0 && gd->gd_Max > 0)
				{
					try--;
					if(args->ia_Backward)
					{
						ln = pwin->Line;
						ln = ln->NextLine;
						while(ln != NULL && gd->gd_Max > 0)
						{
							match_expand(gd,args,ln);
							ln = ln->NextLine;
						}
					} else
					{
						ln = pwin->Line;
						ln = ln->PrevLine;
						while(ln != NULL && gd->gd_Max > 0)
						{
							match_expand(gd,args,ln);
							ln = ln->PrevLine;
						}
					}
					args->ia_Backward = !args->ia_Backward;
				}

#if 0
				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;
					}

				}
#endif

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

					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)
			{
				sprintf(gd->gd_LastName,"BACK TIMES %ld\n",gd->gd_Len);
				cmd->ExecuteInternalCommand(gd->gd_LastName);

				strcpy(gd->gd_LastName,"TYPE TEXT ");
				strncpy(&gd->gd_LastName[10],found,foundlen);
				gd->gd_LastName[10+foundlen] = '\0';
				cmd->ExecuteInternalCommand(gd->gd_LastName);

				strncpy(gd->gd_LastName,found,foundlen);
				gd->gd_LastName[foundlen] = '\0';

				retval = RC_OK;
			}
		}
	}

	return retval;
}
/*FE*/

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

/*FS*/ BOOL send_apimessage(struct GlobalData *gd,struct APIMessage *msg)
{
	struct MsgPort *ped;

	msg->am_Message.mn_Node.ln_Succ = NULL;
	msg->am_Message.mn_Node.ln_Pred = NULL;
	msg->am_Message.mn_Node.ln_Type = NT_MESSAGE;
	msg->am_Message.mn_Node.ln_Pri  = 0;
	msg->am_Message.mn_Node.ln_Name = NULL;
	msg->am_Message.mn_ReplyPort    = gd->gd_ReplyPort;
	msg->am_Message.mn_Length       = sizeof(struct APIMessage);

	/* FindPort()/PutMsg() pair must be in forbidden state ! */
	Forbid();
	if((ped = FindPort(gd->gd_ProgEDHost)) != NULL)
		PutMsg(ped,&msg->am_Message);
	Permit();

	if(ped == NULL)
		return FALSE;

	WaitPort(gd->gd_ReplyPort);
	GetMsg(gd->gd_ReplyPort);

	return TRUE;
}
/*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);

			DB(("found : \"%s\"\n",node->ln_Name));

			rc = TRUE;
		}

	return rc;
}
/*FE*/

