/*(( "Kopf" */
/* -----------------------------------------------------------------------------

   $Id: util.c,v 1.4 1997/06/29 19:24:13 mshopf Exp mshopf $

   GoldED API client, ©1997 Matthias Hopf.
   Compiled with SasC.


   Utility functions.

   ------------------------------------------------------------------------------
*/

/*)) */
/*(( "Includes" */

#include "smartindent.h"
#include "util.h"
#include "semantics.h"

#include <proto/exec.h>

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

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

/*)) */
/*(( "Daten" */

struct Semantic *Semantics[] = SEMANTICS;

int ExecVersion;
int MemAllocSmallFlags;

#ifdef DEBUG
int Dbug_Counter;
int Dbug = 0;
#endif

/* Common parser errors */

const char *SYNTAX_ERROR                = "Syntax error in line %ld";
const char *UNMATCHED_BRACE_ERROR       = "Unmached brace in line %ld";
const char *PRAEPROCESSOR_ERROR         = "Praeprocessor error in line %ld";
const char *SUSPICIOUS_ERROR            = "Suspicious statement in line %ld";
const char *STRING_TERMINATION_ERROR    = "Non-terminated string above line %ld";
const char *INTERNAL_ERROR              = "!!! Internal parse error in line %ld !!!";

const char *UNKNOWN_MODE_ERROR          = "Unknown MODE. Known modes are:\n" SEMANTICS_NAMES;
const char *UNSPECIFIED_MODE_ERROR      = "No MODE specified (mandatory). Known modes are:\n" SEMANTICS_NAMES;

/*)) */

/********** Private **********/
/*(( "NewRequest ()" */

/* Create and initialize new APIOrder and APIModifyRequest.
 * Create a (maximum sized) string, too. */
static struct SmartOrder *NewRequest (sc_t *c, int Line, int Size)
{
    struct SmartOrder *o;

    if (! (o = AllocMem (sizeof (struct SmartOrder) + Size, MemAllocSmallFlags)) )
    {
	Error (c, "Out of memory in line %d");
	return NULL;
    }
    o->Order.api_Data   = &o->Modify;
    o->Modify.mr_Line   = Line;
    o->Modify.mr_Data   = OrderString (o);
    o->StringSize       = Size;

    return (o);
}


/*)) */
/*(( "LinkRequest ()" */

/* Link outstanding Order to message. */
static void LinkRequest (sc_t *c, struct SmartOrder *o)
{
    if (o)
    {
	o->Order.api_Next = c->Msg->api_Order;
	c->Msg->api_Order = &o->Order;
    }
}


/*)) */
/*(( "DoRequest ()" */

/* Create/modify Order for the current line, but the given position.
 * Old Orders are linked or expanded when necessary.
 * No indentation correctness is checked, no Client values are addapted! */
static void DoRequest (sc_t *c, int Pos, int Indent)
{
    struct SmartOrder *o = c->Req;
    char *Text;
    int  Len, Needed;

	/* Link old Request, when it is not one for the current line */
    if (o && c->CurrentLine != o->Modify.mr_Line)
    {
	debug (D_REQUESTS, ("Linking for line %ld\t", o->Modify.mr_Line));
	LinkRequest (c, o);
	c->Req = o = NULL;
    }

    GetText (c, &Text, &Len);
    Needed = Indent > 0 ? Len + Indent : Len;

	/* Create new Request when necessary */
    if (! o)
    {
	debug (D_REQUESTS, ("New Req for line %ld\t", c->CurrentLine));
	if (! (c->Req = o = NewRequest (c, c->CurrentLine, Needed + ADD_SPARSE_INDENTATION)) )
	    return;
	memcpy (OrderString (o), Text, Len);
	o->Modify.mr_Size = Len;
    }
	/* else check size of current Request and copy it to a new one when necessary */
    else if (o->StringSize < Needed)
    {
	debug (D_REQUESTS, ("Renew Req for line %ld old size %ld needed %ld\t", c->CurrentLine, o->StringSize, Needed));
	if (! (o = NewRequest (c, c->CurrentLine, Needed + ADD_SPARSE_INDENTATION)) )
	    return;
	memcpy (OrderString (o), Text, Len);
	o->Modify.mr_Size = Len;
	FreeRequest (c, c->Req);
	c->Req = o;
    }

	/* Now shift characters */
    memmove (OrderString (o) + Pos + Indent, OrderString (o) + Pos, o->Modify.mr_Size - Pos);
    if (Indent > 0)
	memset  (OrderString (o) + Pos, ' ', Indent);
    o->Modify.mr_Size += Indent;
}


/*)) */
/*(( "DoneCursorLine ()" */

/* We have indented the current cursor line.
 * Now set Cursor pos to something reasonable (when necessary). */

static void DoneCursorLine (sc_t *c)
{
    char *Text;
    int Len, i;

    if (! (c->Mode & MODE_CURSOR))
	return;

    GetText (c, &Text, &Len);

    debug (D_CURSOR, ("\nchecking line %ld, len %ld, indent %ld, cursorpos %ld\n", c->CurrentLine, Len, c->Indent, c->CursorX));

    for (i = 0; i < Len; i++)
	if (! isspace (Text [i]))
	    break;

    debug (D_CURSOR, ("first non-space pos %ld\n", i));

    if (i < Len)
    {
	if (c->CursorX < i)             /* current cursorpos less than first char pos */
	    c->CursorX = i;

	for (i = Len-1; i >= 0; i--)
	    if (! isspace (Text [i]))
		break;
	i++;
	debug (D_CURSOR, ("last non-space pos %ld, last non-space char 0x%lx\n", i-1, i> 0 ? (long) Text[i-1] : (long) '/'));
	if (c->CursorX > i)             /* current cursorpos greater than last char pos */
	    c->CursorX = i;
    }
    else                                /* empty line */
	c->CursorX = c->Indent;
}


/*)) */

/********** Public **********/
/*(( "InitIndent ()" */

void InitIndent (sc_t *c, int BeginLine, int EndLine, int Mode)
{
    struct EditConfig *Edit = c->Edit;

    debug (Dbug, ("Startline %ld, EndLine %ld, Mode 0x%lx\n", BeginLine, EndLine, Mode));
    memset (& (c->FirstLine), 0, sizeof (sc_t) - offsetof (sc_t, FirstLine));
    c->BeginIndent = BeginLine;
    c->EndIndent   = EndLine;
    c->Mode        = Mode;
    c->LastPos     = -1;

    c->FirstLine   = c->CurrentLine = 0; /* scan for first column "{" later */
    c->Node        = Edit->TextNodes;

    c->BlockStartX = Edit->BlockStartX;
    c->BlockEndX   = Edit->BlockEndX;
    c->CursorX     = Edit->Column;
}


/*)) */
/*(( "CleanupIndent ()" */

void CleanupIndent (sc_t *c)
{
    struct SmartOrder *o, **next, **last;
    struct EditConfig *Edit = c->Edit;
    struct LineNode *Nodes = Edit->TextNodes;

	/* Link last request */
    LinkRequest (c, c->Req);
    c->Req = NULL;

	/* Check requests, whether they are really necessary */
    last = (struct SmartOrder **) & (c->Msg->api_Order);
    o    = *last;
    while (o)
    {
	next = (struct SmartOrder **) & (o->Order.api_Next);
	debug (D_REQUESTS, ("checking request %lx for line %ld, next: %lx, last pointer: %lx\n", o, o->Modify.mr_Line, *next, last));
	if (o->Modify.mr_Size == Nodes [o->Modify.mr_Line] .Len)
	{
	    if (strncmp ((UBYTE *) (o+1), Nodes [o->Modify.mr_Line] .Text, o->Modify.mr_Size) == 0)
	    {
		debug (D_REQUESTS, ("Freeing unnecessary request %lx for line %ld.\n", o, o->Modify.mr_Line));
		*last = *next;
		FreeRequest (c, o);
		next  = last;
	    }
	}
	last = next;
	o    = *last;
    }

	/* Update display if necessary */
#if 0
    if (c->Msg->api_Order)
	c->Msg->api_Refresh |= 0;
#endif  /* Not necessary in current GoldEd version. */

	/* Create cursor move requests when necessary */
    if (c->CursorX != Edit->Column)
	if ( (o = NewRequest (c, Edit->Line, 0)) )
	{
	    o->Modify.mr_Data   = NULL;
	    o->Modify.mr_Column = c->CursorX;
	    LinkRequest (c, o);
	    c->Msg->api_Refresh |= API_REFRESH_SYNC;
	}

	/* Update block markers if necessary */
    if (c->BlockStartX != Edit->BlockStartX || c->BlockEndX != Edit->BlockEndX)
    {
	Edit->BlockStartX = c->BlockStartX;
	Edit->BlockEndX   = c->BlockEndX;
	c->Msg->api_Refresh |= API_REFRESH_MARKER;
    }

#ifdef DEBUG
    if (Dbug & D_REQUESTS)
    {
	Printf ("Orders for lines ");
	o = (struct SmartOrder *) c->Msg->api_Order;
	while (o)
	{
	    Printf ("%ld ", o->Modify.mr_Line);
	    o = (struct SmartOrder *) o->Order.api_Next;
	}
	Printf ("%%\n");
    }
#endif
}


/*)) */
/*(( "FreeRequest ()" */

/* Free the APIOrder, APIModifyRequest and the string space. */
void FreeRequest (sc_t *c, struct SmartOrder *Req)
{
    debug (D_REQUESTS, ("Freeing request for line %ld\t", Req->Modify.mr_Line));
    FreeMem (Req, sizeof (struct SmartOrder) + Req->StringSize);
}


/*)) */
/*(( "GetText ()" */

/* Get *real* current text and length */

void GetText (sc_t *c, char **Text, int *Len)
{
    if (c->Req && c->Req->Modify.mr_Line == c->CurrentLine)
    {
	*Text = OrderString (c->Req);
	*Len  = c->Req->Modify.mr_Size;
    }
    else if (c->Edit->Line == c->CurrentLine && c->Edit->CurrentBuffer)
    {
	*Text = c->Edit->CurrentBuffer;
	*Len  = c->Edit->CurrentLen;
    }
    else
    {
	*Text = c->Node->Text;
	*Len  = c->Node->Len;
    }
}


/*)) */
/*(( "Move ()" */

/* Move current line contents at position Pos when indentation is enabled
 * for the current line.
 * Perform indentation correctness checks. */
void Move (sc_t *c, int Pos, int NewPos)
{
    char *text;
    int  len, i, line;

    if (Pos == NewPos)
	return;

    debug (D_MOVE, ("Move(%ld->%ld)[Cur %ld]\t", Pos, NewPos, c->CurrentPos));

    line = c->CurrentLine;
	/* don't do anything when we are out of limits */
    if (line < c->BeginIndent || line > c->EndIndent)
	return;

    if (NewPos < 0)
	NewPos = 0;

    GetText (c, &text, &len);

    if (len < 1)
    {
	Error (c, "Move () on empty line %d!");
	return;
    }

	/* When there's nothing to shift, exit */
    if (Pos >= len)
	return;

	/* Backindent only as far as we are only killing spaces */
    if (NewPos < Pos)
    {
	    /* We want at least one space before the new position */
	for (i = Pos+1; i > NewPos; i--)
	    if (i >= 2 && text [i-2] != ' ')
		break;
	    /* We cannot shift without breaking the rules */
	if (i >= Pos)
	    return;
	NewPos = i;
    }

    i = NewPos - Pos;
    DoRequest (c, Pos, i);

    if (c->CurrentPos >= Pos)
	c->CurrentPos += i;
    if (c->NextPos >= Pos)
	c->NextPos += i;
    if (c->NextPosEOL >= Pos)
	c->NextPosEOL += i;

    if (line == c->Edit->Line        && c->CursorX     >= Pos)
	c->CursorX     += i;
    if (line == c->Edit->BlockStartY && c->BlockStartX >= Pos)
	c->BlockStartX += i;
    if (line == c->Edit->BlockEndY   && c->BlockEndX   >= Pos)
	c->BlockEndX   += i;

    debug (D_MOVE, ("Res(%ld)[Cur %ld]\t", i, c->CurrentPos));
}


/*)) */
/*(( "NextWord ()" */

/* Get the next word (maximum length MAX_WORD_LEN-1).
 * Returns an empty word after EOF.
 * Sets Current* when Flag is set. */
void NextWord (sc_t *c, char *buf, int Flag)
{
    char  *text;
    int    len, i, res;
    int    line        = c->CurrentLine;
    int    pos         = c->NextPos;
    int    last        = c->CurrentPos;
    struct LineNode *n = c->Node;
    char  *copy        = buf;
    int  (*IsWord) (struct SmartClient *c, char *buf, int len, int maxlen) = c->Sem->IsWord;

    *buf = '\0';

	/* We may continue to scan the current line */
    GetText (c, &text, &len);
	/* Has an unget happened? */
    if (last == pos)
	last = -1;

    for (;;)
    {
	    /* First make sure we have something to scan. */
	if (pos < 0 || pos >= len || IS_FOLD (n))
	{
	    if (line == c->Edit->Line && Flag)
	    {
		c->CurrentLine = line;
		c->Node        = n;
		DoneCursorLine (c);
	    }
	    if (++line >= c->Edit->Lines)
	    {
		c->CurrentLine = c->EndIndent +1; /* we are finished */
		return;
	    }
	    n++;

		/* not nice - this should be able to use GetText()... */
	    if (c->Edit->Line == line && c->Edit->CurrentBuffer)
	    {
		text = c->Edit->CurrentBuffer;
		len  = c->Edit->CurrentLen;
	    }
	    else
	    {
		text = n->Text;
		len  = n->Len;
	    }

	    pos  = 0;
	    last = -1;
	    continue;
	}

	    /* Then skip whitespace */
	if (! isspace (text[pos]))
	    break;
	pos++;
    }

	/* Now copy chars as long as they belong to the word and they fit into the buffer */
    res = MATCH_FALSE;
    for (i = pos; i < len && copy < buf + MAX_WORD_LEN-1; )
    {
	if ( (res = (*IsWord) (c, &text [pos], i-pos+1, len-pos)) == MATCH_FALSE )
	    break;
	*copy++ = text [i++];
	*copy   = '\0';
	if (res == MATCH_EXACT)
	    break;
    }

	/* Adapt Environment when Flag is set */
    if (! Flag)
    {
	debug (D_WORD, ("%ld<%s>\t", pos, buf));
	return;
    }

	/* Now skip remaining chars as long as they belong to the word */
    if (res == MATCH_TRUE)
	while (i < len && (res = (*IsWord) (c, &text [pos], i-pos+1, len-pos)) != MATCH_FALSE)
	{
	    i++;
	    if (res == MATCH_EXACT)
		break;
	}

	/* Now skip whitespace */
    while (i < len && isspace (text [i]))
	i++;
    if (i >= len)
	i = -1;

    debug (D_WORD, ("%ld%s%s[%s]\t", pos, last < 0 ? "b" : "", i < 0 ? "e" : "", buf));

    c->CurrentLine = line;
    c->CurrentPos  = pos;
    c->Node        = n;
    c->NextPos     = c->NextPosEOL = i;
    c->LastPos     = last;
}


/*)) */
/*(( "LastWord ()" */

void LastWord (sc_t *c)
{
    int i;
    c->NextPos = c->NextPosEOL = c->CurrentPos;
    c->LastPos = -1;
    for (i = c->CurrentPos-1; i >= 0; i--)
	if (! isspace (c->WordBuffer [i]))
	{
	    c->CurrentPos = i;
	    return;
	}
    c->CurrentPos = -1;
}


/*)) */

