/* undo.c - part of asedit program
 *
 * set of procedures to realize Undo and Redo edit operations;
 * maximum number of changes that may be undone is set by EditSTACK_SIZE
 *
 * Last revision - 21.03.1994
 *
 * In the version for asedit 1.2x declaration od undo/redo data types is moved into
 * asedit.h and the specific data is obtained from the general  aseditWindowStruct  structure.
 */

/*
 * Copyright 1991 - 1994,  Andrzej Stochniol, London, UK
 *
 * ASEDIT text editor, both binary and source (hereafter, Software) is
 * copyrighted by Andrzej Stochniol (hereafter, AS) and ownership remains
 * with AS.
 *
 * AS grants you (hereafter, Licensee) a license to use the Software
 * for academic, research and internal business purposes only, without a
 * fee.  Licensee may distribute the binary and source code (if released)
 * to third parties provided that the copyright notice and this statement
 * appears on all copies and that no charge is associated with such copies.
 *
 * Licensee may make derivative works.  However, if Licensee distributes
 * any derivative work based on or derived from the Software, then
 * Licensee will:
 * (1) notify AS regarding its distribution of the derivative work, and
 * (2) clearly notify users that such derivative work is a modified version
 *      and not the original ASEDIT distributed by AS.
 *
 * Any Licensee wishing to make commercial use of the Software should
 * contact AS to negotiate an appropriate license for such commercial use.
 * Commercial use includes:
 * (1) integration of all or part of the source code into a product for sale
 *     or license by or on behalf of Licensee to third parties, or 
 * (2) distribution of the binary code or source code to third parties that
 *     need it to utilize a commercial product sold or licensed by or on 
 *     behalf of Licensee.
 *
 * A. STOCHNIOL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS
 * SOFTWARE FOR ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR
 * IMPLIED WARRANTY.  IN NO EVENT SHALL A. STOCHNIOL BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * By using or copying this Software, Licensee agrees to abide by the
 * copyright law and all other applicable laws, and the terms of this
 * license.
 * AS shall have the right to terminate this license immediately by
 * written notice upon Licensee's breach of, or non-compliance with, any
 * of its terms.  Licensee may be held legally responsible for any
 * copyright infringement that is caused or encouraged by Licensee's
 * failure to abide by the terms of this license.
 *
 *
 * 	Andrzej Stochniol	(A.Stochniol@ic.ac.uk)
 * 	30 Hatch Road
 * 	London SW16 4PN
 * 	UK
 */


#include <stdio.h>
#include <string.h>

#include <Xm/Text.h>

#include "asedit.h"		/* for the definition of aseditWindowStruct  */


/* external declarations */
extern Display  *display;                               /*  Display     */
extern XmStringCharSet charset;

/* declarations specific for undo were moved into asedit.h since asedit 1.2x */

#ifdef _NO_PROTO
void flush_edit_stack( );
void pop_edit_stack ();
void push_edit_stack();

int get_id_from_asdat();
aseditWindowStruct *get_win_from_asdat();

#else
void flush_edit_stack(EditStack *s);
void pop_edit_stack (EditStack *s, EditActionPtr *el);
void push_edit_stack(EditStack *s, EditActionPtr el);

int get_id_from_asdat(XtPointer as_client_data);
aseditWindowStruct *get_win_from_asdat(XtPointer as_client_data);

#endif



#ifdef _NO_PROTO
void push_edit_stack(s, el)
    EditStack *s;
    EditActionPtr el;
#else  /* _NO_PROTO */

void push_edit_stack(EditStack *s, EditActionPtr el)
#endif
{
    /* push an element on the edit stack (on its top); the stack has predefined
       maximum length (EditSTACK_SIZE); if the stack is filled up one element
       from the bottom of the stack is destroyed, address of the bottom is moved
       up so we get one space on the stack. In fact it is not a classic
       stack which I implemented here but this "cyclic" version gives an easy
       way of restoring last EditSTACK_SIZE edit commands;
       because of the possibility of moving of the stack bottom the address
       of any element on the stack must be calculated "modulo" EditSTACK_SIZE;
    */

    s->el[s->top] = el;
    s->top = (s->top + 1) % EditSTACK_SIZE;

    /* check if the stack is used up completely; if so destroy the oldest
       element (i.e. from the bottom of the stack) and move the address
       of the bottom
    */
    if(s->bottom == s->top)
    {
	/* if memory was allocated for the text free it */
	if(s->el[s->bottom]->del_text)
	    { XtFree(s->el[s->bottom]->del_text); s->el[s->bottom]->del_text = NULL; }
	if(s->el[s->bottom]->ins_text)
	    { XtFree(s->el[s->bottom]->ins_text); s->el[s->bottom]->ins_text = NULL; }

	s->bottom = (s->bottom + 1) % EditSTACK_SIZE;
    }
} /* push_edit_stack */

#ifdef _NO_PROTO
void pop_edit_stack(s, el)
    EditStack *s;
    EditActionPtr *el;
#else  /* _NO_PROTO */

void pop_edit_stack(EditStack *s, EditActionPtr *el)
#endif
{
    /* pop the element from the top of the edit stack s */
    if(s->bottom != s->top)
    {
	s->top --;
	if(s->top < 0) s->top += EditSTACK_SIZE;	/* "cyclic" stack */
	*el = s->el[s->top];				/* moved here to avoid
							   checking twice for sign
							   of s->topp -- */
    }
    else *el = NULL;					/* empty stack */
} /* pop_edit_stack */


#ifdef _NO_PROTO
void flush_edit_stack(s)
    EditStack *s;
#else  /* _NO_PROTO */

void flush_edit_stack(EditStack *s)
#endif
{
    /* flush the edit stack pointed by s */
    int i, j, n;
    n = s->top - s->bottom;
    if(n < 0) n += EditSTACK_SIZE;		/* "cyclic" stack */

    for(i=0; i<n; i++)
    {
	j = s->top -i -1;
	if(j < 0) j += EditSTACK_SIZE;
	/* if memory was allocated for the text free it */
	if(s->el[j]->del_text)
	    { XtFree(s->el[j]->del_text); s->el[j]->del_text = NULL; }

	if(s->el[j]->ins_text)
	    { XtFree(s->el[j]->ins_text); s->el[j]->ins_text = NULL; }
    }
    s->top = s->bottom;
} /* flush_edit_stack */


#ifdef _NO_PROTO
void reset_undo_redo(win)
    aseditWindowStruct *win;
#else  /* _NO_PROTO */

void reset_undo_redo( aseditWindowStruct *win)
#endif
{
    /* resets undo/redo by flushing redo and undo stacks  and set the redo/undo flags */

    flush_edit_stack(&(win->redo_stack));
    flush_edit_stack(&(win->undo_stack));

    win->can_undo = win->can_redo = False;
}   /* reset_undo_redo */


#ifdef _NO_PROTO
void SaveActionForUndo(w, client_data, call_data)
    Widget w;
    XtPointer client_data;
    XtPointer call_data;
#else  /* _NO_PROTO */

void SaveActionForUndo(Widget w, XtPointer client_data, XtPointer call_data)
#endif
{
    /* save the current XmTextVerifyCallbackStruct for later use; additionally
       for delete operation save the deleted text in the undo_text->text->ptr;
       for an easy access in undo/redo I terminate undo_text->text->ptr with a
       NULL (so it can be used straight away in the, for example, XmTextReplace).
    */

    XmTextVerifyCallbackStruct *txt_data = (XmTextVerifyCallbackStruct *)call_data;
    /**XmTextVerifyPtr txt_data = (XmTextVerifyPtr) call_data; ***/
    EditActionPtr previous_undo;
    String text_buffer;
    String text_changed;
    Arg    al[2];
    int    reason;		/* reason for the callback */
    int    len;

    EditActionPtr  undo_el;	/* txt_data for undo operation */

    /* to speed the process of editing we preallocate a buffer for edit_history,
       with exactly the same length as EditSTACK_SIZE; this get rid of permanent
       allocation and deallocation of memory for every edit action; the memory
       must be only allocated/deallocated to store text added (not for all
       variables describing the edit operation) */
    /* Since asedit 1.2 the data below is obtained from aseditWindowStruct ...
      (old) static EditAction edit_history[EditSTACK_SIZE];
      (old) static Boolean edit_history_initialized = False;
    */
    Boolean undo_sequence = False, redo_sequence = False;	 /* default values */
    int i;

    int id                  = get_id_from_asdat(client_data);
    aseditWindowStruct *win = get_win_from_asdat(client_data);


    /* when you start editing allocate memory for text blocks in edit_history */
    if(!win->edit_history_initialized)
    {
	win->edit_history_initialized = True;
	for(i=0; i < EditSTACK_SIZE; i++)
	{
	
	    win->edit_history[i].del_text = NULL;
	    win->edit_history[i].ins_text = NULL;
	    win->edit_history[i].undo_sequence = False;
	    win->edit_history[i].redo_sequence = False;

	    ; /* was needed in the version with text blocks */
	    /****OLD   edit_history[i].text =
			 (XmTextBlock) XtMalloc(sizeof(XmTextBlockRec));
	    *****/
	}
    }

    /* first check if the previous undo_txt was connected with the mouse operation;
       if so and the current operation is move do not save the current move
       operation;  WE ONLY save first new move operation !!!
    */
    pop_edit_stack(&(win->undo_stack), &previous_undo);
    if(previous_undo == NULL && txt_data->reason == XmCR_MOVING_INSERT_CURSOR )  return;   /* do not store initial movements */

    reason = txt_data->reason;

    if(previous_undo != NULL )                   /* the stack is not empty */
    {
        /* set the previous redo_sequence to False (it might be set later on ) */
        previous_undo->redo_sequence = False;

        if(previous_undo->reason == XmCR_MOVING_INSERT_CURSOR &&
           txt_data->reason      == XmCR_MOVING_INSERT_CURSOR )
	{
            push_edit_stack(&(win->undo_stack), previous_undo); /* push it back ... */
            return;                     /* RETURN - do not store successive moves */
        }
        else
        {
            /* take care of both undo_sequence & redo_sequence */
            /* the following is only used for Motif 1.2x !!! */
            if(XmVersion >= 1002 && previous_undo->reason == XmCR_MODIFYING_TEXT_VALUE)
	    {
                if( reason == XmCR_MODIFYING_TEXT_VALUE /* Change All case, sequence of insert files or insert file & change(s) !!! ... */
                    || (reason == XmCR_MOVING_INSERT_CURSOR &&  
                                previous_undo->currInsert == txt_data->currInsert))
		/* the second case happens for Motif 1.2 even in a standard typing
		   mode (in Motif 1.2 the old modify callback is practically
		   replaced with a sequence of modify and moving calbacks)
		*/
                {
                    previous_undo->redo_sequence = True;
                    redo_sequence = undo_sequence = True;
		}
		/* the following is a hack to get rid off joinig togeteher 
		   few insert file operations or insert operation and  following
		   changes
		*/
		if(reason == XmCR_MODIFYING_TEXT_VALUE && 
			 previous_undo->currInsert == previous_undo->newInsert &&
			previous_undo->startPos == previous_undo->endPos)
		{
			previous_undo->redo_sequence = False;
			redo_sequence = undo_sequence = False;
		}
            }
            push_edit_stack(&(win->undo_stack), previous_undo); /* push it back ... */
        }
    }


    /* the undo_el is a pointer to an element in edit_history, the
       element in edit_history has the same index as the top element on the
       undo_stack (which has a "cyclic" nature)
    */
    undo_el = &(win->edit_history[win->undo_stack.top]);


    /* if there was anything on the redo stack flush it (we are just about to
       put a new entry on the undo stack); make the redo button insensitive  */
    flush_edit_stack(&(win->redo_stack));
    win->can_redo = False;
    XtSetSensitive(win->menu_redo_button,  False);


    switch(reason)
    {
	case XmCR_MODIFYING_TEXT_VALUE:
	    /* for a modifying of the edit text allocate memory and the text;
	       there may be two cases: text addition or deletion;  */
	    len = txt_data->text->length;
	    if(len > 0)
	    {	/* text adding */
		undo_el->ins_text = (String) XtMalloc((len+1) * sizeof(char)); /* + NULL */
		/**AIX 3.2.5 - NO: strncpy(undo_el->ins_text, txt_data->text->ptr, len); */ /* can't use strcpy
						because ptr field is not NULL terminated */
		/* NOTE for AIX 3.2.5: when we use the above strncpy under AIX 3.2.5 *nothing*
 		   is usually copied !!!! I don't know why, maybe because the field is not a
		   real String because it's not NULL terminated;
		   because of the above we use the following workaround:
		*/
		{
		    /* AIX 3.2.5 hack (instead of using strncpy) */
		    int k;
		    for(k=0; k<len; k++) undo_el->ins_text[k] = txt_data->text->ptr[k];
		}
		/* terminate the string with NULL (for easy use in undo/redo) */
#if defined(vax11c) || defined(__DECC)
                undo_el->ins_text[len] = '\0';
#else
		undo_el->ins_text[len] =  NULL;
#endif /* vax11c */

	    }

	    /* now check if any data was deleted or not (if so store it) */
	    len = (int) (txt_data->endPos - txt_data->startPos); /* amount of text deleted */
	    if(len > 0)
	    {	/* text deleting ...(or just replacing with pending delete)
		 - store it but do not change text->length.
		   it will be used to recognise both those cases */
		undo_el->del_text = (String) XtMalloc((len+1) * sizeof(char));

		/* get the address of the internally stored text */
		XtSetArg(al[0], XmNvalue, &text_buffer);
		XtGetValues(w, al, 1);
		strncpy(undo_el->del_text, text_buffer+txt_data->startPos,len);
		XtFree(text_buffer);

#if defined(vax11c) || defined(__DECC)
                undo_el->del_text[len] = '\0';
#else
		undo_el->del_text[len] = NULL;
#endif /* vax11c */


	    }
	    break;

	case XmCR_MOVING_INSERT_CURSOR:
	    break;	/* no text to store ... */
    }
    /* set the rest of undo_el .... */
    undo_el->reason 	= txt_data->reason;
    undo_el->currInsert 	= txt_data->currInsert;
    undo_el->newInsert 	= txt_data->newInsert;
    undo_el->startPos 	= txt_data->startPos;
    undo_el->endPos	= txt_data->endPos;
    undo_el->undo_sequence = undo_sequence;
    undo_el->redo_sequence = redo_sequence;

    /* push the undo data on the undo stack ... */
    push_edit_stack(&(win->undo_stack), undo_el);
    /* set sensitivity to the undo button */
    XtSetSensitive(win->menu_undo_button, True);
    win->can_undo = True;

} /* SaveActionForUndo */


/*****************************  UndoRedo  *************************************
**
**	Process undo or redo actions for edit_text widget.
**      The kind of the action is set via action_type.
**      !!!!! The structure of the procedure is nearly the same as TextCB
**      so if you make changes there or here remeber to make the changes in
**      the other procedure. For full comments see TextCB !!!
*/
#ifdef _NO_PROTO
void UndoRedo (win, action_type)
    aseditWindowStruct *win;
    int action_type;
#else  /* _NO_PROTO */

void UndoRedo (aseditWindowStruct *win, int action_type)
#endif
{

    EditActionPtr    txt_data=NULL;
    int    reason;		  /* reason for the callback in TextCB */
    XmTextPosition   insert_len;  /* length of text to be inserted */
    XmTextPosition   start, end;

    long ins_len, del_len;
    Boolean	last_element = False;	/* to show that last element from the stack 
					   has been just poped from the stack */


    if(action_type == MENU_UNDO)
    {
	pop_edit_stack(&(win->undo_stack), &txt_data);
	if(win->undo_stack.bottom == win->undo_stack.top)  /* last element was popped... */
	{
	    last_element = True;
	    win->can_undo = False;
	    XtSetSensitive(win->menu_undo_button,  False);
	    XFlush(display);	/* sensitivity must be set before next keys are
				   processed, otherwise with a busy system we
				   could "skip over" the bottom of the stack
				   and all track would be lost !!! */
	}
    }
    else
    {
	 pop_edit_stack(&(win->redo_stack), &txt_data);
	 if(win->redo_stack.bottom == win->redo_stack.top)  /* last element was popped... */
	 {
	     last_element = True;
	     win->can_redo = False;
	     XtSetSensitive(win->menu_redo_button,  False);
	     XFlush(display);	/* as above; be sure that the item is insensitive */
	 }
    }

    if(txt_data == NULL) 	return;	/* should not happen, we are doing this just in
				   case. If it did happen it would mean that
				   the undo/redo was called when the appropriate
				   stack was empty, i.e. the sensitivity of the
				   appropriate button was set improperly (but now it will
				   set correctly)
				   in 1.27 this might happen as well when this procedure is
 				   called via actions !! */


    /* push the obtained element onto the OPPOSITE stack & set sensitivity */
    if(action_type == MENU_UNDO)
    {
	 push_edit_stack(&(win->redo_stack), txt_data);
	 win->can_redo = True;
	 XtSetSensitive(win->menu_redo_button,  True);
    }
    else
    {
	 push_edit_stack(&(win->undo_stack), txt_data);
	 win->can_undo = True;
	 XtSetSensitive(win->menu_undo_button,  True);
    }



    /* set the logical value do_undo_redo_action to True */
    win->do_undo_redo_action = True;

    /*  finally do the appropriate undo/redo operation .... */
    reason = txt_data->reason;

    switch(reason)
    {
	case XmCR_MODIFYING_TEXT_VALUE:
	    /* increase (redo) or decrease (undo) the changes counter ... */
	    if(action_type == MENU_UNDO)
	    {
		(win->changes_counter) --;
		if(win->changes_counter == 0L ) 
		{
		    write_ls(win->changes_status, charset, "%s", " ");
		    set_titles_mwindow_icon(win, win->filename, NULL); 
		}
		if(win->changes_counter == -1L ) 
		{
		    write_ls(win->changes_status, charset, "%s", "*");
		    set_titles_mwindow_icon(win, win->filename, (char *)lstr.changeHint); 
		}

	    }
	    else
	    {
		(win->changes_counter) ++;	/* redo */
		if(win->changes_counter == 1L ) 
		{
		    write_ls(win->changes_status, charset, "%s", "*");
		    set_titles_mwindow_icon(win, win->filename, (char *)lstr.changeHint);
		}
	    }


	    /* for the undo operation the action must be reversed !!!
	       (what was stored	on the undo stack is a plain copy of the
	       operation performed, with only one addition for the delete
	       operation, when the text was stored but the
	       length field was not changed)
	    */
	    /* for SG there is a problem with a strlen function, if the
		argument is NULL we get the coredump !!! */
	    if(txt_data->ins_text) ins_len = strlen(txt_data->ins_text);
	    else 		   ins_len = 0;
	    if(txt_data->del_text) del_len = strlen(txt_data->del_text);  
	    else		   del_len = 0;

	    if(action_type == MENU_UNDO)
	    {
		start      = txt_data->startPos;
		end        = txt_data->startPos + ins_len;
			
		XmTextReplace(win->edit_text, start, end, txt_data->del_text);
	    }
	    else	/* redo */
	    {
		start      = txt_data->startPos;
		end        = txt_data->endPos;
		XmTextReplace(win->edit_text, start, end, txt_data->ins_text);
	    }

	    /* for highlighting we can use:
	       XmTextSetHighlight(win->edit_text, left, right, XmHIGHLIGHT_SELECTED);
							  XmHIGHLIGHT_NORMAL  - not highlighted
							  XmHIGHLIGHT_SECONDARY_SELECTED - underlined
	    ****/

	    /******* TEMP DEBUG
		fprintf(stderr, "changes_counter = %ld\n", win->changes_counter); ****/
	    break;

	case XmCR_MOVING_INSERT_CURSOR:
	    if(action_type == MENU_UNDO)
		XmTextSetInsertionPosition(win->edit_text, txt_data->currInsert);
	    else
		XmTextSetInsertionPosition(win->edit_text, txt_data->newInsert); /* redo */

	    break;

	default:
	    /* an unknown client_data was received and there is no setup to handle this
	       -  do  nothing; end procedure nicely  */
	    fprintf(stderr, "Warning: an unknown client_data in undo_redo callback\n");
	    fprintf(stderr,"Detected reason = %d \n", reason);
	    break;
    }

    if(!last_element)
    {
        /* make a recursive call for a sequence of undo/redo */
        if(action_type == MENU_UNDO && txt_data->undo_sequence  ||
           action_type == MENU_REDO && txt_data->redo_sequence )
                UndoRedo(win, action_type);
    }


} /* UndoRedo */

