/*
**	Chat.c
**
**	Chat support code
**
**	Copyright © 1990-1995 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

#include "termGlobal.h"

	// Maximum number of characters to enter per line

#define CHAT_LINE_SIZE	512

	// Local data

STATIC struct List	*ChatList;
STATIC struct Node	*ChatNode;
STATIC STRPTR		 ChatBuffer,
			 ChatUndo,
			 ChatWork,
			 ChatTemp;
STATIC LONG		 ChatPosition;
STATIC Object		*ChatGadget;
STATIC struct Hook	 ChatHook = { {NULL},(HOOKFUNC)ChatKey };
STATIC BOOL		 ChatWasActive;

	/* ChatKey(register __a1 ULONG *Msg,register __a2 struct SGWork *Work):
	 *
	 *	String gadget editing hook code. This is where all the
	 *	magic happens.
	 */

ULONG __saveds __asm
ChatKey(register __a1 ULONG *Msg,register __a2 struct SGWork *Work)
{
		/* Someone activated the string gadget and
		 * hit a key.
		 */

	if(*Msg == SGH_KEY)
	{
		BOOL	NeedChange	= FALSE,
			DidSomething	= FALSE;

			// Remember that this gadget was activated

		Forbid();

		ChatWasActive = TRUE;

		Permit();

			// Clear the history list when pressing Amiga+Del/Amiga+Backspace

		if((Work -> IEvent -> ie_Qualifier & (AMIGARIGHT | AMIGALEFT)) && (Work -> IEvent -> ie_Code == DEL_CODE || Work -> IEvent -> ie_Code == BACKSPACE_CODE))
		{
			FreeList(ChatList);

			NewList(ChatList);

			ChatNode = NULL;

			Work -> Actions &= ~(SGA_USE | SGA_BEEP);

			return(TRUE);
		}

			/* Right-Amiga-key was pressed, release the
			 * string gadget so user may select a menu
			 * item.
			 */

		if((Work -> IEvent -> ie_Qualifier & AMIGARIGHT) && Work -> IEvent -> ie_Code < 96)
		{
			if(!(Work -> IEvent -> ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)) && (Work -> IEvent -> ie_Code == KEYCODE_X || Work -> IEvent -> ie_Code == KEYCODE_Q))
				return(TRUE);
			else
			{
				Work -> Actions = (Work -> Actions & ~(SGA_USE | SGA_BEEP)) | SGA_END | SGA_REUSE;

				return(TRUE);
			}
		}

			// This looks like a function key. Send the corresponding macro.

		if(Work -> IEvent -> ie_Code >= F01_CODE && Work -> IEvent -> ie_Code <= F10_CODE)
		{
			STRPTR	String;
			LONG	Len,Index = Work -> IEvent -> ie_Code - F01_CODE;

			Forbid();

				// Pick the right macro

			if(Work -> IEvent -> ie_Qualifier & IEQUALIFIER_CONTROL)
				String = MacroKeys -> Keys[3][Index];
			else
			{
				if(Work -> IEvent -> ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
					String = MacroKeys -> Keys[2][Index];
				else
				{
					if(Work -> IEvent -> ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
						String = MacroKeys -> Keys[1][Index];
					else
						String = MacroKeys -> Keys[0][Index];
				}
			}

				// Anything to send at all?

			if(Len = strlen(String))
			{
				struct DataMsg *Msg;

				if(Msg = (struct DataMsg *)CreateMsgItem(sizeof(struct DataMsg) + Len + 1))
				{
					Msg -> Type = DATAMSGTYPE_SERIALCOMMAND;
					Msg -> Data = (STRPTR)(Msg + 1);

					strcpy(Msg -> Data,String);

						// Send the command

					PutMsgItem(SpecialQueue,(struct MsgItem *)Msg);
				}
			}

			Permit();

			return(TRUE);
		}

			/* The user pressed the cursor-right key to
			 * move the cursor to the next word in the buffer.
			 */

		if(Work -> IEvent -> ie_Code == CURSORRIGHT && (Work -> IEvent -> ie_Qualifier & IEQUALIFIER_CONTROL))
		{
			if(Work -> BufferPos != Work -> NumChars)
			{
				WORD i,Position = -1;

				for(i = Work -> BufferPos ; i < Work -> NumChars ; i++)
				{
					if(Work -> WorkBuffer[i] == ' ')
					{
						for( ; i < Work -> NumChars ; i++)
						{
							if(Work -> WorkBuffer[i] != ' ')
							{
								Position = i;
								break;
							}
						}

						break;
					}
				}

				if(Position != -1)
					Work -> BufferPos = Position;
				else
					Work -> BufferPos = Work -> NumChars;

				Work -> EditOp = EO_MOVECURSOR;
			}

			return(TRUE);
		}

			/* The user pressed the cursor-right key to
			 * move the cursor to the previous word in the buffer.
			 */

		if(Work -> IEvent -> ie_Code == CURSORLEFT && (Work -> IEvent -> ie_Qualifier & IEQUALIFIER_CONTROL))
		{
			if(Work -> BufferPos)
			{
				WORD i,Position = -1;

				for(i = Work -> BufferPos ; i >= 0 ; i--)
				{
					if(Work -> WorkBuffer[i] != ' ')
					{
						Position = i;
						break;
					}
				}

				if(Position == -1)
					Position = 0;

				if(Position)
				{
					i = Position;

					Position = -1;

					for( ; i >= 0 ; i--)
					{
						if(Work -> WorkBuffer[i] == ' ')
						{
							Position = i + 1;
							break;
						}
					}
				}

				if(Position != -1)
					Work -> BufferPos = Position;
				else
					Work -> BufferPos = 0;

				Work -> EditOp = EO_MOVECURSOR;
			}

			DidSomething = TRUE;
		}

			/* The user pressed the cursor-up key to
			 * scroll through the command history.
			 */

		if(Work -> IEvent -> ie_Code == CURSORUP)
		{
				/* Shift key: jump to first command
				 * history entry.
				 */

			if(Work -> IEvent -> ie_Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
			{
				if(ChatList -> lh_Head -> ln_Succ)
					ChatNode = ChatList -> lh_Head;
				else
					ChatNode = NULL;

				NeedChange = TRUE;
			}
			else
			{
				if(ChatNode)
				{
					if(ChatNode -> ln_Pred -> ln_Pred)
					{
						ChatNode = ChatNode -> ln_Pred;

						NeedChange = TRUE;
					}
				}
				else
				{
					if(ChatList -> lh_Head -> ln_Succ)
					{
						ChatNode = ChatList -> lh_TailPred;

						NeedChange = TRUE;
					}
				}
			}

			DidSomething = TRUE;
		}

			/* The user pressed the cursor-down key to
			 * scroll through the command history.
			 */

		if(Work -> IEvent -> ie_Code == CURSORDOWN)
		{
				/* Shift key: jump to last command
				 * history entry.
				 */

			if(Work -> IEvent -> ie_Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
			{
				if(ChatList -> lh_Head -> ln_Succ)
					ChatNode = ChatList -> lh_TailPred;
				else
					ChatNode = NULL;

				NeedChange = TRUE;
			}
			else
			{
				if(ChatNode)
				{
					if(ChatNode -> ln_Succ -> ln_Succ)
						ChatNode = ChatNode -> ln_Succ;
					else
						ChatNode = NULL;
				}

				NeedChange = TRUE;
			}

			DidSomething = TRUE;
		}

			// Update the contents of the string gadget

		if(NeedChange)
		{
			LONG Len;

			if(ChatNode)
				strcpy(Work -> WorkBuffer,ChatNode -> ln_Name);
			else
				Work -> WorkBuffer[0] = 0;

			strcpy(Work -> StringInfo -> UndoBuffer,Work -> PrevBuffer);

			Work -> StringInfo -> UndoPos = Work -> BufferPos;

			Len = strlen(Work -> WorkBuffer);

			if(Len < Work -> BufferPos)
				Work -> BufferPos = Len;

			Work -> NumChars	= Len;
			Work -> Actions		= (Work -> Actions & ~SGA_BEEP) | SGA_USE | SGA_REDISPLAY;
		}

			// User pressed return?

		if(Work -> Actions & SGA_END)
		{
				// Deactivate the gadget?

			if(Work -> IEvent -> ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
			{
				Work -> Actions &= ~SGA_END;

				Forbid();

				ChatWasActive = FALSE;

				Permit();
			}
			else
			{
				struct DataMsg	*Msg;
				LONG		 Len = Work -> NumChars;

					// If any text was entered, add it to the history list

				if(Work -> WorkBuffer[0])
				{
					BOOL AddIt = TRUE;

						// Don't add duplicates

					if(ChatNode)
					{
						if(!Stricmp(ChatNode -> ln_Name,Work -> WorkBuffer))
							AddIt = FALSE;
					}

						// Allocate space for the text, then add it

					if(AddIt)
					{
						struct Node *SomeNode;

						if(SomeNode = (struct Node *)AllocVecPooled(sizeof(struct Node) + Len + 1,MEMF_ANY))
						{
							strcpy(SomeNode -> ln_Name = (STRPTR)(SomeNode + 1),Work -> WorkBuffer);

							AddTail(ChatList,SomeNode);
						}
					}
				}

				ChatNode = NULL;

					// Transfer the buffer

				if(Msg = (struct DataMsg *)CreateMsgItem(sizeof(struct DataMsg) + Len + 3))
				{
					STRPTR Extra;

					Msg -> Type = DATAMSGTYPE_SERIALCOMMAND;
					Msg -> Data = (STRPTR)(Msg + 1);

					strcpy(Msg -> Data,Work -> WorkBuffer);

						// Add EOL characters

					switch(Config -> TerminalConfig -> SendCR)
					{
						case EOL_CR:

							Extra = "\r";
							break;

						case EOL_LF:

							Extra = "\n";
							break;

						case EOL_CRLF:

							Extra = "\r\n";
							break;

						case EOL_LFCR:

							Extra = "\n\r";
							break;

						default:

							Extra = NULL;
							break;
					}

					if(Extra)
						strcpy(Msg -> Data + Len,Extra);

						// Clear the string gadget

					Work -> WorkBuffer[0]	= 0;
					Work -> BufferPos	= 0;
					Work -> NumChars	= 0;
					Work -> Actions		= (Work -> Actions & ~(SGA_BEEP | SGA_END)) | SGA_USE | SGA_REDISPLAY;

					Work -> StringInfo -> UndoPos		= 0;
					Work -> StringInfo -> UndoBuffer[0]	= 0;

						// Send the line

					PutMsgItem(SpecialQueue,(struct MsgItem *)Msg);
				}
			}

			DidSomething = TRUE;
		}

			// Control character entered?

		if(!DidSomething && Work -> Code && !(Work -> Code & 0xFFE0))
		{
			UBYTE Code = Work -> Code & 0x1F;

			if(Code == '\t' && Work -> IEvent -> ie_Code != TAB_CODE)
				return(TRUE);

			if(Code != '\r' && Code != '\b')
			{
				struct DataMsg *Msg;

				if(Code == ('S' & 0x1F) || Code == ('Q' & 0x1F))
				{
					if(Config -> SerialConfig -> xONxOFF)
					{
						Forbid();

						if(Code == ('S' & 0x1F))
						{
							if(Status == STATUS_READY)
								Status = STATUS_HOLDING;
						}
						else
						{
							if(Status == STATUS_HOLDING)
								Status = STATUS_READY;
						}

						Permit();
					}

					if(!Config -> SerialConfig -> PassThrough)
						return(TRUE);
				}

				if(Msg = (struct DataMsg *)CreateMsgItem(sizeof(struct DataMsg) + 2))
				{
					Work -> Actions &= ~SGA_USE;

					Msg -> Type = DATAMSGTYPE_SERIALCOMMAND;
					Msg -> Data = (STRPTR)(Msg + 1);

					Msg -> Data[0] = Code;
					Msg -> Data[1] = 0;

					PutMsgItem(SpecialQueue,(struct MsgItem *)Msg);
				}
			}
		}

		return(TRUE);
	}
	else
	{
		if(*Msg == SGH_CLICK)
		{
				// Remember activation

			Forbid();

			ChatWasActive = TRUE;

			Permit();

			return(TRUE);
		}
		else
			return(FALSE);
	}
}

	/* HideChatGadget():
	 *
	 *	Remove the chat gadget, but don't free the buffers.
	 */

VOID
HideChatGadget()
{
	if(ChatGadget)
	{
		GetAttr(STRINGA_BufferPos,ChatGadget,(ULONG *)&ChatPosition);

		RemoveGList(Window,(struct Gadget *)ChatGadget,1);

		DisposeObject(ChatGadget);

		ChatGadget = NULL;
	}
}

	/* DeleteChatGadget():
	 *
	 *	Remove the chat gadget, also take care of the buffers.
	 */

VOID
DeleteChatGadget()
{
	HideChatGadget();

	if(ChatBuffer)
	{
		FreeVecPooled(ChatBuffer);

		ChatBuffer = NULL;
	}

	if(ChatList)
	{
		DeleteList(ChatList);

		ChatList = NULL;
	}
}

	/* UpdateChatGadget():
	 *
	 *	Redraw the chat gadget imagery.
	 */

VOID
UpdateChatGadget()
{
	if(ChatGadget)
	{
		struct RastPort	*RPort = Window -> RPort;
		LONG		 Left,Right,Top;

			// Draw the separator bar

		if(StatusWindow || Config -> ScreenConfig -> StatusLine == STATUSLINE_DISABLED)
			Top = UserFontHeight;
		else
			Top = StatusDisplayHeight + UserFontHeight;

		Top	= Window -> Height - (Window -> BorderBottom + Top + 2);
		Left	= Window -> BorderLeft;
		Right	= Window -> Width - (Window -> BorderRight + 1);

		SetAPen(RPort,DrawInfo -> dri_Pens[SHADOWPEN]);
		Move(RPort,Left,Top);
		Draw(RPort,Right,Top);

		Top++;

		SetAPen(RPort,DrawInfo -> dri_Pens[SHINEPEN]);
		Move(RPort,Left,Top);
		Draw(RPort,Right,Top);

			// Redraw the gadget

		RefreshGList((struct Gadget *)ChatGadget,Window,NULL,1);
	}
}

	/* ActivateChat(BOOL Reactivate):
	 *
	 *	Activate the chat gadget.
	 */

VOID __regargs
ActivateChat(BOOL Reactivate)
{
	if(ChatGadget)
	{
		Forbid();

		if(Reactivate)
			Reactivate = ChatWasActive;
		else
			Reactivate = TRUE;

		Permit();

		if(Reactivate)
			ActivateGadget((struct Gadget *)ChatGadget,Window,NULL);
	}
}

	/* CreateChatGadget():
	 *
	 *	Create the chat gadget and add it to the main window.
	 */

BOOL
CreateChatGadget()
{
	ChatWasActive = TRUE;

		// Allocate the history list

	if(!ChatList)
	{
		if(!(ChatList = (struct List *)AllocVecPooled(sizeof(struct MinList),MEMF_ANY)))
			return(FALSE);
		else
			NewList(ChatList);

		ChatNode = NULL;
	}

		// Allocate the undo/work/editing buffers

	if(!ChatBuffer)
	{
		if(!(ChatBuffer = (STRPTR)AllocVecPooled(5 * CHAT_LINE_SIZE,MEMF_ANY | MEMF_CLEAR)))
		{
			DeleteChatGadget();

			return(FALSE);
		}
		else
		{
			ChatUndo = ChatBuffer	+ CHAT_LINE_SIZE;
			ChatWork = ChatUndo	+ CHAT_LINE_SIZE;
			ChatTemp = ChatWork	+ CHAT_LINE_SIZE;
		}
	}

		// Finally create the gadget

	if(!ChatGadget)
	{
		LONG Bottom = Window -> BorderBottom + UserFontHeight - 1;

		if(!StatusWindow && Config -> ScreenConfig -> StatusLine != STATUSLINE_DISABLED && !Config -> ScreenConfig -> SplitStatus)
			Bottom += StatusDisplayHeight;

		if(!(ChatGadget = NewObject(NULL,STRGCLASS,
			GA_Left,		Window -> BorderLeft,
			GA_Height,		UserFontHeight,
			GA_RelBottom,		-Bottom,
			GA_RelWidth,		-(Window -> BorderLeft + Window -> BorderRight),
			GA_TabCycle,		FALSE,
			STRINGA_TextVal,	ChatBuffer,
			STRINGA_MaxChars,	CHAT_LINE_SIZE,
			STRINGA_Buffer,		ChatBuffer,
			STRINGA_BufferPos,	ChatPosition,
			STRINGA_UndoBuffer,	ChatUndo,
			STRINGA_WorkBuffer,	ChatWork,
			STRINGA_NoFilterMode,	TRUE,

				// NOTE: This should really look like below,
				//       according to the BOOPSI documentation.

//			STRINGA_Pens,		((LONG)DrawInfo -> dri_Pens[BACKGROUNDPEN] << 16) | DrawInfo -> dri_Pens[TEXTPEN],
//			STRINGA_ActivePens,	((LONG)DrawInfo -> dri_Pens[BACKGROUNDPEN] << 16) | DrawInfo -> dri_Pens[TEXTPEN],

			STRINGA_Pens,		((LONG)DrawInfo -> dri_Pens[BACKGROUNDPEN] << 8) | DrawInfo -> dri_Pens[TEXTPEN],
			STRINGA_ActivePens,	((LONG)DrawInfo -> dri_Pens[BACKGROUNDPEN] << 8) | DrawInfo -> dri_Pens[TEXTPEN],
			STRINGA_EditHook,	&ChatHook,
		TAG_DONE)))
		{
			DeleteChatGadget();

			return(FALSE);
		}
		else
			AddGList(Window,(struct Gadget *)ChatGadget,(UWORD)~0,1,NULL);
	}

	return(TRUE);
}
