/*****************************************************************************/
/*                           Jean-Eloi Dussartre                             */
/*                          M.I.T. Project Athena.                           */
/*                              January 1990.                                */
/*                   The Spreadsheet Widget implementation.                  */
/*                     The AtTable Object implementation.                    */
/*****************************************************************************/


/* Preliminary remarks:
 * All name prefixed by AtTable (At like in Athena) are public.
 * The AtTable makes a copy of any string inserted in the table. Thus it is
 * safe to pass a string to a set function and freeing or reusing the buffer
 * later.
 * Table are indexed as follow :
 * Upper left corner is (irow = 0, icol = 0), row index increases downward,
 * column index increases from left to right. The last cell is (nrow - 1,
 * ncol - 1).
 * Procedures parameters are usually in the following order :
 *           - ATable (usually named t).
 *           - Row value (either irow for index, or nrow for dimension).
 *           - Column value (either icol for index, or ncol for dimension).
 *           - Range value (trow for top row, lcol for left column,
 *                          brow for bottom row, rcol for right column).
 *           - Value (usually v).
 */


/* Controlling source code */

#define AtTABLE_C     /* Sign up in the preprocessor */

#define AtTABLE_C_RESETERROR__ /* Comment this definition if you don't want */
			       /* error to be reset on each entry point.    */

/* Include some stuff */

#include <stdio.h>
#include <X11/Intrinsic.h>
#include <string.h>
#include "AtTable.h"

/* Internal type definition */

typedef struct AtTableRegistrationRec_ *AtTableRegistration;

typedef struct {
                short trow,
		      lcol,
		      brow,
                      rcol;
	      } AtTableRange;
			
struct AtTableRegistrationRec_
	  {
	    AtTableEvent         eventMsk;  /* Event that happened     */
	    void                 *externId; /* id to pass to procedure */
	    AtTableEventHandler  externFcn; /* Address to call         */
	    AtTableRange         Scope;     /* Area of Interest        */
	    AtTableRegistration  cdr;       /* Link  next registration */
	  };

struct AtTableRec_
   {
    short int               nRow, nColumn;   /* Dimension              */
    AtTableRegistration     registryLst;     /* That's the notifiation */
    char                    **dheap;         /* Data Heap              */
   };

     /* Macro definition */
/*
 * The macros function follows the naming conventions :
 * Predicat are suffixed with P
 * Computed value are prefixed with Calc
 * Access to values are prefixed with Get
 */

         /* Access Definition */

#define CalcIndex(T, IROW, ICOL) (IROW + ((T)->nRow) * ICOL)
#define CalcLastRow(T)          ((T)->nRow - 1)
#define CalcLastColumn(T)       ((T)->nColumn - 1)
#define GetValue(T, IDX)        (((T)->dheap) [(IDX)])
#define GetCell(T, IROW, ICOL)  GetValue(T, CalcIndex(T, IROW, ICOL))

#define CheckRowP(T, IROW)      ((IROW >= 0) && (IROW < (T)->nRow))
#define CheckColumnP(T, ICOL)   ((ICOL >= 0) && (ICOL < (T)->nColumn))
#define CheckBoundP(T, IROW, ICOL) \
			   (CheckRowP(T, IROW) && CheckColumnP(T, ICOL))
#define CheckRangeP(T, IROW1, ICOL1, IROW2, ICOL2)               \
			   ((IROW1 <= IROW2) && (ICOL1 <= ICOL2) \
			    && CheckBoundP(t, IROW1, ICOL1)      \
			    && CheckBoundP(t, IROW2, ICOL2))
#define CheckRowRangeP(T, IROW1, IROW2)                                \
			   ((IROW1 <= IROW2) && CheckRowP(T, IROW1)    \
			    && CheckRowP(T, IROW2))
#define CheckColumnRangeP(T, ICOL1, ICOL2)                             \
			   ((ICOL1 <= ICOL2) && CheckColumnP(T, ICOL1) \
			    && CheckColumnP(T, ICOL2))

	  /* Define Error stuff */

#define ERR_NONE    0
#define ERR_BOUND   1
#define ERR_INIT    2
#define ERR_RANGE   3
#define ERR_DIM     4
#define ERR_EXTRNID 5

#define SetError(E)   (AtTableLastError = (E))


#define EmptyValueP(V)                ((V) == AtTableEmptyCell)
#define EmptyCellP(T, IROW, ICOL)     EmptyValueP(GetCell(T, IROW, ICOL))


  /* Registration list manipulation */
#define GetCdr(P)	((P)->cdr)

  /* Public variables */

char *AtTableEmptyCell      = "";         /* This is the empty cell */
int   AtTableLastError      = ERR_NONE;   /* This is the error var. */

  /* Geometrical manipulation */

void IntersectRange(AtTableRange r,
		    short int trow,
		    short int lcol,
		    short int brow,
		    short int rcol,
		    AtTableRange *R)

{
  R->trow = (trow <= r.trow)? trow : r.trow;
  R->lcol = (lcol <= r.lcol)? lcol : r.lcol;
  R->brow = (brow <= r.brow)? brow : r.brow;
  R->rcol = (rcol <= r.rcol)? rcol : r.rcol;
}

Boolean EmptyRangeP(AtTableRange r)

{
  return ((r.trow >  r.brow) || (r.lcol > r.rcol));
}

  /* Private functions */

static void BroadcastEvents(AtTable      t,
			    AtTableEvent event,
			    short int    trow,
			    short int    lcol,
			    short int    brow,
			    short int    rcol)
{  AtTableRegistration ptr;
   for (ptr = t->registryLst; (ptr); ptr = GetCdr(ptr))
      if (event & ptr->eventMsk)
	   (*(ptr->externFcn)) (ptr->externId,
			      t,
			      (event & ptr->eventMsk),
			      trow,
			      lcol,
			      brow,
			      rcol);
}

static void NotifyEvents(AtTableRegistration ptr,
			 AtTable             t,
			 AtTableEvent        event,
			 short int           trow,
			 short int           lcol,
			 short int           brow,
			 short int           rcol)

{
  if (event & ptr->eventMsk)
	 (*(ptr->externFcn)) (ptr->externId,
			      t,
			      (event & ptr->eventMsk),
			      trow,
			      lcol,
			      brow,
			      rcol);
}


  /* Entry Points */
  /* Creation and registration */

AtTable AtTableCreate(short int nrow, short int ncol)

{      AtTable t;

#ifdef AtTABLE_C_RESETERROR__
       SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */

       /* Check arguments */
       if ((nrow < 1) || (ncol < 1))
	  {
	    SetError(ERR_DIM);
	    return ((AtTable) NULL);
	  }
	else
	  { int idx;
	    t = XtNew (struct AtTableRec_);
	    t->nRow        = nrow;
	    t->nColumn     = ncol;
	    t->registryLst = (AtTableRegistration) NULL;
	    t->dheap = (char **) XtCalloc(nrow*ncol, sizeof (char *));
	    for (idx = 0; (idx < (nrow * ncol)); idx++)
	      (t->dheap) [idx] = AtTableEmptyCell;
	    return(t);
	  };
}

void AtTableDestroy(AtTable t)

{       AtTableRegistration ptr1, ptr2;
	int i;
#ifdef AtTABLE_C_RESETERROR__
       SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */

	BroadcastEvents(t, AtTableEventDeactivation,
			0, 0, CalcLastRow(t), CalcLastColumn(t));

	for (i = 0; (i < (t->nRow * t->nColumn)); i++)
	   XtFree(GetValue(t, i));
	XtFree(t->dheap);

	for (ptr1 = t->registryLst; (ptr1); ptr1 = ptr2)
	  {  ptr2 = GetCdr(ptr1);
	     XtFree(ptr1);
	  };

	XtFree(t);
}

void AtTableSubscribe(AtTable             t,
		      AtTableEvent        e,
		      void                *p,
		      AtTableEventHandler f)

{       AtTableRegistration  ptr = (AtTableRegistration)
		      XtMalloc(sizeof(struct AtTableRegistrationRec_));
#ifdef AtTABLE_C_RESETERROR__
     SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
	ptr->cdr       = t->registryLst;
	ptr->eventMsk  = e;
	ptr->externId  = p;
	ptr->externFcn = f;

	t->registryLst = ptr;

	NotifyEvents(t->registryLst, t, AtTableEventActivation,
		     0, 0, CalcLastRow(t), CalcLastColumn(t));
}

Boolean AtTableCancel(AtTable t, void    *p, AtTableEventHandler f)

{     AtTableRegistration ptr1, *ptr;
#ifdef AtTABLE_C_RESETERROR__
     SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
      for (ptr = &(t->registryLst); (*ptr); ptr = &(GetCdr(*ptr)))
	if (((*ptr)->externId == p) && ((*ptr)->externFcn == f))
	  {
	    NotifyEvents(*ptr, t, AtTableEventDeactivation,
			 0, 0, CalcLastRow(t), CalcLastColumn(t));
	    ptr1 = *ptr;
	    *ptr = GetCdr(*ptr);
	    XtFree(ptr1);
	  };

      SetError(ERR_EXTRNID);
      return(FALSE);
}

  /* Registration manipulation */

Boolean AtTableAddEvent(AtTable             t,
			void               *p,
			AtTableEventHandler f,
			AtTableEvent        e)

{     AtTableRegistration ptr;
#ifdef AtTABLE_C_RESETERROR__
     SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
      for (ptr = t->registryLst; (ptr); ptr = GetCdr(ptr))
	if ((ptr->externId == p) && (ptr->externFcn == f))
	  {
	    ptr->eventMsk |= e;
	    return(TRUE);
	  };
      SetError(ERR_EXTRNID);
      return(FALSE);
}

Boolean AtTableDropEvent(AtTable             t,
			 void                *p,
			 AtTableEventHandler f,
			 AtTableEvent        e)

{     AtTableRegistration ptr;
#ifdef AtTABLE_C_RESETERROR__
     SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
      for (ptr = t->registryLst; (ptr); ptr = GetCdr(ptr))
	if ((ptr->externId == p) && (ptr->externFcn == f))
	  {
	    ptr->eventMsk &= ~e;
	    return(TRUE);
	  };
      SetError(ERR_EXTRNID);
      return(FALSE);
}

Boolean AtTableChangeId(AtTable t,
			void    *p1,
			AtTableEventHandler f,
			void    *p2)
{     AtTableRegistration ptr;
#ifdef AtTABLE_C_RESETERROR__
     SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
      for (ptr = t->registryLst; (ptr); ptr = GetCdr(ptr))
	if ((ptr->externId == p1) && (ptr->externFcn == f))
	  {
	    ptr->externId = p2;
	    return(TRUE);
	  };
      SetError(ERR_EXTRNID);
      return(FALSE);
}

AtTableEvent AtTableGetEventMask(AtTable t, void *p, AtTableEventHandler f)

{     AtTableRegistration ptr;
#ifdef AtTABLE_C_RESETERROR__
     SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
      for (ptr = t->registryLst; (ptr); ptr = GetCdr(ptr))
	if ((ptr->externId == p) && (ptr->externFcn == f))
	  return(ptr->eventMsk);

      SetError(ERR_EXTRNID);
      return(0);
}

   /* Cell and range setting and clearing */

Boolean AtTableSetCell (AtTable t, short int irow, short int icol,char *v)

{
#ifdef AtTABLE_C_RESETERROR__
     SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
     if (!CheckBoundP(t, irow, icol))
       {
	    SetError(ERR_BOUND);
	    return (FALSE);
       }
     else
       { /* I keep my own copy of the stuff, be defensive it doesn't hurt! */
	 /* I am freeing the old string */
	 /* XtFree does not complain it ptr is NULL */
	 if (! EmptyCellP(t, irow, icol))
	    XtFree(GetCell(t, irow, icol));

	 GetCell(t, irow, icol) = ((v) && !(EmptyValueP(v)))
					 ? XtNewString(v)
					 : AtTableEmptyCell;

       };
       BroadcastEvents(t, AtTableEventRangeSet, irow, icol, irow, icol);
       return (TRUE); /* If I get there everything is fine */
}


char *AtTableGetCell(AtTable t, short int irow, short int icol)

{
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* TABLE_C_RESETERROR__ */

  if (!CheckBoundP(t, irow, icol))
    {
      SetError(ERR_BOUND);
      return ((char *) NULL);
    }
  else
    {
      BroadcastEvents(t, AtTableEventRangeGet, irow, icol, irow, icol);
      return(GetCell(t, irow, icol));
    };
}


Boolean AtTableClearRange(AtTable t,
			  short int trow,
			  short int lcol,
			  short int brow,
			  short int rcol)

{   /* Let first check the range of data we have */
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
    if (!CheckRangeP(t, trow, lcol, brow, rcol))
      {
	SetError(ERR_RANGE);
	return(FALSE);
      }
      else
      {  int idx;
	 for (; (lcol <= rcol); lcol++)
	   for (idx = CalcIndex(t, trow, lcol);
		(idx <= CalcIndex(t, brow, lcol));
		idx++)
		if (!EmptyValueP(GetValue(t, idx)))
		  {
		    XtFree(GetValue(t, idx));
		    GetValue(t, idx) = AtTableEmptyCell;
		  };
	 BroadcastEvents(t, AtTableEventRangeSet, trow, lcol, brow, rcol);
	 return(TRUE);
      };
}


short int AtTableGetRowNum(AtTable t)

{
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
  return(t->nRow);
}

short int AtTableGetColumnNum(AtTable t)

{
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
  return(t->nColumn);
}

Boolean AtTableSetRange(AtTable  t,
		       short int trow,
		       short int lcol,
		       short int brow,
		       short int rcol,
		       char      *v)
{   int idx;
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
    /* Check if this call is not usedto clear a range ? */
    if (!(v) || EmptyValueP(v))
       return(AtTableClearRange(t, trow, lcol, brow, rcol));

    /* Check range */
    if (!CheckRangeP(t, trow, lcol, brow, rcol))
      {
	SetError(ERR_RANGE);
	return(FALSE);
      }
     else
      { /* Loop on range */
	for (; (lcol <= rcol); lcol++)
	   for (idx = CalcIndex(t,trow, lcol);
		(idx <= CalcIndex(t, brow, lcol));
		idx++)
		{
		  if (!EmptyValueP(GetValue(t, idx)))
		    XtFree (GetValue(t, idx));
		  GetValue(t, idx) = XtNewString(v);
		};
	BroadcastEvents(t, AtTableEventRangeSet, trow, lcol, brow, rcol);
	return(TRUE);
      };
}

Boolean AtTableDeleteRows(AtTable t, short int trow, short int brow)

{  char **psrc, **pdst;
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
    if (!CheckRowRangeP(t, trow, brow))
      {
	SetError(ERR_RANGE);
	return(FALSE);
      }
     else
      {
	/* Clear the range */
	AtTableClearRange (t, trow, 0, brow, CalcLastColumn(t));
	/* Move stuff to new place */
	for (pdst = t->dheap + trow,
	     psrc = t->dheap + brow + 1;
	     (psrc < (t->dheap + CalcIndex(t, 1, CalcLastColumn(t))));
	     pdst +=  t->nRow + trow - brow - 1,
	     psrc +=  t->nRow + trow - brow - 1)
	      bcopy(psrc, pdst, sizeof (char *) * t->nRow + trow - brow - 1);
	/* Move the trailing rows in last column */
	bcopy(psrc, pdst, t->nRow - brow - 1);
	/* Finally realloc the table (shrink) */
	t->dheap = (char **) XtRealloc (t->dheap,
					  sizeof(char *)
					* (t->nRow +=  trow - brow - 1)
					* t->nColumn);
	BroadcastEvents(t, AtTableEventRangeSet | AtTableEventRowSize,
			trow, 0, CalcLastRow(t), CalcLastColumn(t));

	return(TRUE);
      };
}

Boolean AtTableDeleteColumns(AtTable t, short int lcol, short int rcol)

{
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
    if (!CheckColumnRangeP(t, lcol, rcol))
      {
	SetError(ERR_RANGE);
	return(FALSE);
      }
     else
      {
	/* Clear the range */
	AtTableClearRange (t, 0, lcol, CalcLastRow(t), rcol);
	/* Now move values to new place */
	bcopy (t->dheap + CalcIndex(t, 0, rcol + 1),
	       t->dheap + CalcIndex(t, 0, lcol),
	       t->nRow * (t->nColumn - rcol - 1) * sizeof (char *));
	/* Finally realloc the table (shrink) */
	t->dheap = (char **) XtRealloc (t->dheap,
					  sizeof(char *)
					* t->nRow
					* (t->nColumn -= rcol - lcol + 1));
	BroadcastEvents(t, AtTableEventRangeSet | AtTableEventColumnSize,
			0, lcol, CalcLastRow(t), CalcLastColumn(t));
	return(TRUE);
      };
}

Boolean AtTableInsertRows(AtTable t, short int irow, short int krow)

{   short int jrow, jcol;
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
    if (!CheckRowP(t, irow ))
      {
	SetError(ERR_RANGE);
	return(FALSE);
      }
     else
      {
	/* Realloc the table to get the extra cells needed */
	t->dheap = (char **) XtRealloc(t->dheap,
					 sizeof(char *)
				       * (t->nRow += krow)
				       * t->nColumn);

	/* Move stuff to put it at the right place         */

	/* Fill up the new range with empty value          */
	for (jcol = 0; (jcol < CalcLastColumn(t)); jcol++)
	  for (jrow = irow + 1; (jrow < (irow + krow)); jrow++)
	     GetCell(t, jrow, jcol) = AtTableEmptyCell;
	BroadcastEvents(t, AtTableEventRangeSet | AtTableEventColumnSize,
			irow + 1, 0, CalcLastRow(t), CalcLastColumn(t));
	return(TRUE);
      };
}

Boolean AtTableInsertColumns(AtTable t, short int icol, short int kcol)

{   int idx;
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
    if (!CheckColumnP(t, icol))
      {
	SetError(ERR_RANGE);
	return(FALSE);
      }
     else
      {
	/* Realloc the table to get space needed for new columns */
	t->dheap = (char **) XtRealloc(t->dheap,
					 sizeof(char *)
				       * t->nRow
				       * (t->nColumn += kcol));
	/* Move stuff from icol to icol + kcol + 1 */
	bcopy (t->dheap + CalcIndex(t, 0, icol + 1),
	       t->dheap + CalcIndex(t, 0, icol + kcol + 1),
	       sizeof (char *) * t->nRow * (t->nColumn - icol - 1));
	/* Fill up the new range with empty cell */
	for (idx = CalcIndex(t, 0, icol + 1);
	     (idx <= CalcIndex(t, CalcLastRow(t), icol + kcol));
	     idx++)
	    GetValue(t, idx) = AtTableEmptyCell;

	BroadcastEvents(t, AtTableEventRangeSet | AtTableEventColumnSize,
			0, icol + 1, CalcLastRow(t), CalcLastColumn(t));
	return(TRUE);
      };
}

Boolean AtTableMoveRange(AtTable   t,
			 short int trow1, short int lcol1,
			 short int brow1, short int rcol1,
			 short int trow2, short int lcol2,
			 short int brow2, short int rcol2)

{
    short int irow1, irow2;
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
    if (!(   CheckRangeP(t, trow1, lcol1, brow1, rcol1)
	  && CheckRangeP(t, trow2, lcol2, brow2, rcol2)))
      {
	SetError(ERR_RANGE);
	return(FALSE);
      }
     else
      {
	for(; (lcol1 < rcol1) | (lcol2 < rcol2); lcol1++, lcol2++)
	  for (irow1 = trow1, irow2 = trow2;
	       (irow1 < brow1) | (irow2 < brow2);
	       irow1++, irow2++)
	    {
	      if (!EmptyCellP(t, irow2, lcol2))
		XtFree(GetCell(t, irow2, lcol2));
	      GetCell(t, irow2, lcol2) = GetCell(t, irow1, lcol1);
	      GetCell(t, irow1, lcol1) = AtTableEmptyCell;
	    };
	  
	BroadcastEvents(t, AtTableEventRangeGet, trow1, lcol1, brow1, rcol1);
	BroadcastEvents(t, AtTableEventRangeSet, trow2, lcol2, brow2, rcol2);
	return(TRUE);
      };

}

Boolean AtTableCopyRange(AtTable   t,
			 short int trow1, short int lcol1,
			 short int brow1, short int rcol1,
			 short int trow2, short int lcol2,
			 short int brow2, short int rcol2)

{
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
    if (   !CheckRangeP(t, trow1, lcol1, brow1, rcol1)
	|| !CheckRangeP(t, trow2, lcol2, brow2, rcol2))
      {
	SetError(ERR_RANGE);
	return(FALSE);
      }
     else
      {
	return(TRUE);
      };
}



char *AtTableGetRowLabel(AtTable t, short int irow)

  {
   static char buffer [10];
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
  
  if (!CheckRowP(t, irow))
    {
	SetError(ERR_RANGE);
	return(NULL);
    }
  else
    {
      sprintf(buffer,"%d", irow);
      return(buffer);
    };
  }

char *AtTableGetColumnLabel(AtTable t, short int icol)

  {
   static char buffer [10];
#ifdef AtTABLE_C_RESETERROR__
    SetError (ERR_NONE);
#endif /* AtTABLE_C_RESETERROR__ */
  

  if (!CheckColumnP(t, icol))
    {
	SetError(ERR_RANGE);
	return(NULL);
    }
  else
    {
      sprintf(buffer,"%d", icol);
      return(buffer);
    };
  }




