/*
 * Motif
 *
 * Copyright (c) 1987-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these librararies and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/*
 * HISTORY
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#ifdef REV_INFO
#ifndef lint
static char rcsid[] = "$TOG: XmTabList.c /main/9 1999/04/27 17:49:59 samborn $"
#endif
#endif

#ifndef X_NOT_STDC_ENV
#include <stdlib.h>
#endif
#include <string.h>
#include <ctype.h>

#include <Xm/XmosP.h>		/* For ALLOCATE/DEALLOCATE_LOCAL */
#include "MessagesI.h"
#include "ResIndI.h"
#include "XmI.h"
#include "XmRenderTI.h"
#include "XmStringI.h"
#include "XmTabListI.h"

/* Warning Messages */
#define NEGATIVE_VALUE_MSG		_XmMMsgXmTabList_0000

/********    Static Function Declarations    ********/

static XmTab GetNthTab(XmTabList tl,
		       int pos,
		       XmTab cur_tab,
		       int cur_pos); 


/********    End Static Function Declarations    ********/

/*
 * This function returns the tab in tl at position pos.  It starts searching
 * either from the start of the tablist or from cur_tab if cur_pos is closer to 
 * pos than zero and cur_tab is not NULL.
 */
static XmTab
GetNthTab(XmTabList tl, int pos, XmTab cur_tab, int cur_pos)
{
  XmTab		prev_tab;
  unsigned int 	count;
  int		i;
  
  if (pos == 0) return(_XmTabLStart(tl));
  
  count = _XmTabLCount(tl);
  
  if (abs(pos) >= count)
  {
    if (pos > 0) return(_XmTabPrev(_XmTabLStart(tl)));
    else return(_XmTabLStart(tl));
 }

  /* Convert pos and cur_pos to positives less than count */
  if (pos < 0) pos += count;
  cur_pos %= count;
  if (cur_pos < 0) cur_pos += count;
    
  if (pos == cur_pos) return(cur_tab);

  /* Is start or cur_tab closer? */
  if ((cur_tab != NULL) &&
      ((pos > cur_pos/2) || (pos < (count + cur_pos)/2)))
    {
      prev_tab = cur_tab;
      i = pos - cur_pos;
    }
  else
    {
      prev_tab = _XmTabLStart(tl);
      i = (pos < count/2) ? pos : pos - count;
    }
  
  switch (i/abs(i))
    {
    case 1:
      for (; i > 0; i--)
	prev_tab = _XmTabNext(prev_tab);
      break;
	  
    case -1:
      for (; i < 0; i++)
	prev_tab = _XmTabPrev(prev_tab);
      break;
    }
  
  return(prev_tab);
}

/*
 * A tablist is a doubly linked ring of tabs. 
 */
XmTabList
XmTabListInsertTabs(XmTabList oldlist, 
		    XmTab *tabs,
		    Cardinal tab_count,
		    int position)
{
  XmTabList	tl;
  int		i;
  XmTab		prev_tab, tab, next_tab;

  _XmProcessLock();
  if ((tabs == NULL) || (tab_count == 0)) {
	_XmProcessUnlock();
	return(oldlist);
  }

  if (oldlist == NULL)
    {
      tl = (XmTabList)XtMalloc(sizeof(_XmTabListRec));
  
      _XmTabLCount(tl) = tab_count;
      
      prev_tab = _XmTabCopy(tabs[0]);

      _XmTabLStart(tl) = prev_tab;

      for (i = 1; i < tab_count; i++)
	{
	  tab = _XmTabCopy(tabs[i]);
	  
	  _XmTabPrev(tab) = prev_tab;
	  _XmTabNext(prev_tab) = tab;
	  prev_tab = tab;
	}
      
      _XmTabNext(prev_tab) = _XmTabLStart(tl);
      _XmTabPrev(_XmTabLStart(tl)) = prev_tab;
    }
  else
    {
      tl = XmTabListCopy(oldlist, 0, 0);
      
      /* Hook in first tab */
      tab = _XmTabCopy(tabs[0]);

      prev_tab = GetNthTab(tl, position, NULL, 0);
      
      if (position == 0) _XmTabLStart(tl) = tab;

      next_tab = _XmTabNext(prev_tab);
      
      _XmTabNext(prev_tab) = tab;
      _XmTabPrev(tab) = prev_tab;
      prev_tab = tab;

      /* Hook in rest of tabs. */
      for (i = 1; i < tab_count; i++)
	{
	  tab = _XmTabCopy(tabs[i]);
	  _XmTabNext(prev_tab) = tab;
	  _XmTabPrev(tab) = prev_tab;
	  prev_tab = tab;
	}	  
      
      /* Complete circle */
      _XmTabNext(prev_tab) = next_tab;
      _XmTabPrev(next_tab) = prev_tab;

      _XmTabLCount(tl) += tab_count;

      XmTabListFree( oldlist );
    }

  _XmProcessUnlock();
  return(tl);
}

/*ARGSUSED*/
Widget
_XmCreateTabList(Widget parent,
		 String name,	/* unused */
		 ArgList arglist, /* unused */
		 Cardinal argcount) /* unused */
{
  XmRendition	rend = (XmRendition)parent;
  XmTabList	tl = NULL;
  
  if (_XmRendTabs(rend) == NULL) 
    {
      tl = (XmTabList)XtMalloc(sizeof(_XmTabListRec));
      bzero((char *)tl, sizeof(_XmTabListRec));
      _XmRendTabs(rend) = tl;
    }
  
  return((Widget)tl);
}

/* 
 * This copying routine also works with the internal marking scheming used
 * by the insert and replace routines. 
 */
XmTabList
XmTabListCopy(XmTabList tablist,
	      int offset,
	      Cardinal count)
{
  XmTabList	tl;
  XmTab		old_tab, tab, next_tab;
  unsigned int	i;
  
  _XmProcessLock();
  if (tablist == NULL) {
	_XmProcessUnlock();
	return(NULL);
  }
  
  tl = (XmTabList)XtMalloc(sizeof(_XmTabListRec));
  
  /* Zero count implies copy from offset to end/beginning */
  if (count == 0) count = (_XmTabLCount(tablist) - abs(offset));

  if (count > _XmTabLCount(tablist)) count = _XmTabLCount(tablist);
  
  old_tab = GetNthTab(tablist, offset, NULL, 0);

  /* If marked, routine called by insert/replace. Don't copy. */
  tab = _XmTabMark(old_tab) ? old_tab : _XmTabCopy(old_tab);
  
  /* Add first. */
  _XmTabLCount(tl) = count;
  _XmTabLStart(tl) = tab;

  /* Add rest. */
  for (i = 1; i < count; i++)
    {
      old_tab = (offset >= 0) ? _XmTabNext(old_tab) : _XmTabPrev(old_tab);
      /* See above.  Don't copy if marked. */
      next_tab = _XmTabMark(old_tab)? old_tab : _XmTabCopy(old_tab);
  
      _XmTabNext(tab) = next_tab;
	
      _XmTabPrev(next_tab) = tab;
	
      tab = next_tab;
    }
  
  /* Complete circle. */
  _XmTabNext(tab) = _XmTabLStart(tl);
  _XmTabPrev(_XmTabLStart(tl)) = tab;

  _XmProcessUnlock();
  return(tl);
}

/* 
 * Marked tabs have mark cleared but aren't actually freed.
 */
void
XmTabListFree(XmTabList tablist)
{
  int	i;
  XmTab	tab, next;

  _XmProcessLock();
  if (tablist == NULL) {
	_XmProcessUnlock();
	return;
  }
  
  tab = _XmTabLStart(tablist);
  
  for (i = 1; i < _XmTabLCount(tablist); i++)
    {
      next = _XmTabNext(tab);
      
      if (_XmTabMark(tab)) _XmTabMark(tab) = FALSE;
      else XmTabFree(tab);

      tab = next;
    }
  
  if (_XmTabMark(tab)) _XmTabMark(tab) = FALSE;
  else XmTabFree(tab);

  _XmProcessUnlock();
  XtFree((char *)tablist);
}

Cardinal
XmTabListTabCount(XmTabList tablist)
{
  Cardinal ret_val;
  _XmProcessLock();
  if (tablist == NULL) {
	_XmProcessUnlock();
	return 0;
  }

  ret_val = _XmTabLCount(tablist);
  _XmProcessUnlock();
  return ret_val;
}

XmTab
XmTabListGetTab(XmTabList tablist,
		Cardinal position)
{
   XmTab ret_val;
  _XmProcessLock();
  if (tablist == NULL || abs(position) >= _XmTabLCount(tablist)) {
	_XmProcessUnlock();
	return((XmTab)NULL);
  }

  ret_val = _XmTabCopy(GetNthTab(tablist, position, NULL, 0));
  _XmProcessUnlock();
  return ret_val;
}

/*
 * This routine uses the mark bit of tabs so that replaced tabs can
 * be copied upon replacement and not copied when the rest of the tabs
 * are copied or freed when the old tabs are freed.
 */
XmTabList
XmTabListReplacePositions(XmTabList oldlist,
			  Cardinal *position_list,
			  XmTab *tabs,
			  Cardinal tab_count)
{
  unsigned int	i;
  unsigned int	cur_pos;
  XmTab		cur_tab, tab, prev, next;
  XmTabList	tl;
  
  _XmProcessLock();
  if ((oldlist == NULL) ||
      (position_list == NULL) ||
      (tabs == NULL) || (tab_count == 0)) {
    _XmProcessUnlock();
    return(oldlist);
  }

  tl = (XmTabList)XtMalloc(sizeof(_XmTabListRec));
  _XmTabLCount(tl) = _XmTabLCount(oldlist);
  cur_tab = _XmTabLStart(tl) = _XmTabLStart(oldlist);
  cur_pos = 0;

  /* Make the replacement in the old list, then copy and free. */
  for (i = 0; i < tab_count; i++)
    {
      cur_tab = GetNthTab(tl, position_list[i],
			  cur_tab, cur_pos);
      cur_pos = position_list[i];
      
      prev = _XmTabPrev(cur_tab);
      next = _XmTabNext(cur_tab);
      
      /* replace tab copying */
      tab = _XmTabCopy(tabs[i]);
      
      if (prev == cur_tab) { /* only one tab in list */
	_XmTabPrev(tab) = _XmTabNext(tab) = tab;
      } else {
	_XmTabNext(prev) = tab;
	_XmTabPrev(tab) = prev;
	_XmTabNext(tab) = next;
	_XmTabPrev(next) = tab;
      }
      if (cur_tab == _XmTabLStart(tl))
	_XmTabLStart(tl) = tab;

      XmTabFree(cur_tab);
      cur_tab = tab;
    }

  XtFree((char *)oldlist);

  _XmProcessUnlock();
  return(tl);
}

/*
 * This routine uses a mark/sweep algorithm.
 * A pass over position_list is made to mark tabs for removal without
 * disturbing their positions.
 * Then a pass is made over the oldlist removing marked tabs.
 * A final pass is made to copy the remaining tabs.
 */
XmTabList
XmTabListRemoveTabs(XmTabList oldlist,
		    Cardinal *position_list,
		    Cardinal position_count)
{
  XmTab		cur_tab, tab, prev, next;
  int		cur_pos, i;
  XmTabList	tl;

  _XmProcessLock();
  if ((oldlist == NULL) ||
      (position_list == NULL) ||
      (position_count == 0)) {
    _XmProcessUnlock();
    return(oldlist);
  }
  
  cur_tab = _XmTabLStart(oldlist);
  cur_pos = 0;

  /* Get position, set mark */
  for (i = 0; i < position_count; i++)
    {
      cur_tab = GetNthTab(oldlist, position_list[i],
			  cur_tab, cur_pos);
      cur_pos = position_list[i];
      _XmTabMark(cur_tab) = TRUE;
    }
  
  /* Free marked tabs */
  for (tab = _XmTabNext(_XmTabLStart(oldlist));
       tab != _XmTabLStart(oldlist);
       tab = next)
    {
      if (_XmTabMark(tab))
	{
	  prev = _XmTabPrev(tab);
	  next = _XmTabNext(tab);
	  
	  _XmTabNext(prev) = next;
	  _XmTabPrev(next) = prev;

	  XmTabFree(tab);
	  _XmTabLCount(oldlist) --;
	}
      else
	  next = _XmTabNext(tab);
    }
  
  /* tab is now at start. */
  if (_XmTabMark(tab))
    {
      if (tab == _XmTabNext(tab))
	/* We've deleted all the tabs. */
	{
	  _XmTabLCount(oldlist) = 1;
	  _XmTabMark(tab) = FALSE;
	  XmTabListFree(oldlist);
	  _XmProcessUnlock();
	  return((XmTabList)NULL);
	}
      
      _XmTabLStart(oldlist) = _XmTabNext(tab);

      prev = _XmTabPrev(tab);
      next = _XmTabNext(tab);
      
      _XmTabNext(prev) = next;
      _XmTabPrev(next) = prev;
      
      XmTabFree(tab);
      _XmTabLCount(oldlist) --;
    }

  tl = XmTabListCopy(oldlist, 0, 0);
  XmTabListFree(oldlist);
  
  _XmProcessUnlock();
  return(tl);
}

XmTab
XmTabCreate(float value,
	    unsigned char units,
	    XmOffsetModel offset_model,
	    unsigned char alignment,
	    char *decimal)
{
  XmTab 	tab;

  _XmProcessLock();
  tab = (XmTab)XtMalloc(sizeof(_XmTabRec));
  
  _XmTabMark(tab) = FALSE;
  if (value >= 0) 
    {
      _XmTabValue(tab) = value;
    }
  else 
    {
      _XmTabValue(tab) = 0.0;
      XmeWarning(NULL, NEGATIVE_VALUE_MSG);
    }
  _XmTabUnits(tab) = units;
  _XmTabModel(tab) = offset_model;
  _XmTabAlign(tab) = alignment;
  _XmTabDecimal(tab) = XtNewString(decimal);

  _XmProcessUnlock();
  return(tab);
}

/*ARGSUSED*/
Widget
_XmCreateTab(Widget parent,
	     String name,	/* unused */
	     ArgList arglist,
	     Cardinal argcount)
{
  static XrmQuark quarks[] = {0, 0, 0, 0, 0};

  XmTabList	tl = (XmTabList)parent;
  XrmQuark	qarg;
  float	        value = 0.0;
  unsigned char	units = XmPIXELS;
  XmOffsetModel	model = XmABSOLUTE;
  unsigned char	alignment = XmALIGNMENT_BEGINNING;
  char 		*decimal = ".";
  XmTab		tab, start;
  int		i;
  
  /* Init quark list */
  if (quarks[0] == 0)
    {
      quarks[0] = XrmPermStringToQuark(XmNtabValue);
      quarks[1] = XrmPermStringToQuark(XmNunitType);
      quarks[2] = XrmPermStringToQuark(XmNoffsetModel);
      quarks[3] = XrmPermStringToQuark(XmNalignment);
      quarks[4] = XrmPermStringToQuark(XmNdecimal);
    }

  /* Get arguments from arglist */
  for (i = 0; i < argcount; i++)
    {
      qarg = XrmStringToQuark(arglist[i].name);
      
      if (qarg == quarks[0])
	value = (float)arglist[i].value;
      else if (qarg == quarks[1])
	units = (unsigned char)arglist[i].value;
      else if (qarg == quarks[2])
	model = (XmOffsetModel)arglist[i].value;
      else if (qarg == quarks[3])
	alignment = (unsigned char)arglist[i].value;
      else if (qarg == quarks[4])
	decimal = (char *)arglist[i].value;
    }
  
  tab = XmTabCreate(value, units, model, alignment, decimal);
  
  if (_XmTabLCount(tl) == 0)
    {
      _XmTabLStart(tl) = tab;
      _XmTabPrev(tab) = tab;
      _XmTabNext(tab) = tab;
    }
  else
    {
      start = _XmTabLStart(tl);
      _XmTabNext(tab) = start;
      _XmTabPrev(tab) = _XmTabPrev(start);
      _XmTabNext(_XmTabPrev(start)) = tab;
      _XmTabPrev(start) = tab;
    }  

  _XmTabLCount(tl)++;

  return((Widget)NULL);
}

void
XmTabFree(XmTab tab)
{
  if (tab == NULL) return;
  
  XtFree(_XmTabDecimal(tab));
  XtFree((char *)tab);
}

float
XmTabGetValues(XmTab tab,
	       unsigned char *units,
	       XmOffsetModel *offset,
	       unsigned char *alignment,
	       char **decimal)
{
  float ret_val;
  _XmProcessLock();
  if (units != NULL) *units = _XmTabUnits(tab);
  if (offset != NULL) *offset = _XmTabModel(tab);
  if (alignment != NULL) *alignment = _XmTabAlign(tab);
  if (decimal != NULL) *decimal = _XmTabDecimal(tab);
  
  ret_val = _XmTabValue(tab);
  _XmProcessUnlock();
  return ret_val;
}

void
XmTabSetValue(XmTab tab,
	      float value)
{
  _XmProcessLock();
  if (value >= 0) _XmTabValue(tab) = value;
  else XmeWarning(NULL, NEGATIVE_VALUE_MSG);
  _XmProcessUnlock();
}

XmTab
_XmTabCopy(XmTab tab)
{
  XmTab	new_tab;
  
  new_tab = (XmTab)XtMalloc(sizeof(_XmTabRec));
  
  memcpy((char *)new_tab, (char *)tab, sizeof(_XmTabRec));
  
  _XmTabMark(new_tab) = FALSE;
  _XmTabDecimal(new_tab) = XtNewString(_XmTabDecimal(tab));
  
  return(new_tab);
}

/***********
 * _XmTabListGetPosition 
 * returns the x pixel coordinate of the specified tab.
 **********/
Position
_XmTabListGetPosition(
	Screen * screen,
        XmTabList tab_list,
        unsigned char unit_type,
        Cardinal tab_position)
{
    XmTab tab ;
    Position xpos = 0 ;
    unsigned char units; 
    XmOffsetModel offset; 

    tab = XmTabListGetTab(tab_list, tab_position);

    if (tab) {
	xpos = (Position) XmTabGetValues(tab, 
					 &units, 
					 &offset, 
					 NULL, 
					 NULL);
	xpos = _XmConvertUnits(screen, XmHORIZONTAL, units, xpos, unit_type);
	/* a little bit of recursivity here */
	if ((offset == XmRELATIVE) && tab_position){
	    xpos += _XmTabListGetPosition(screen, tab_list, unit_type,
					  tab_position-1);
	}
	XmTabFree(tab) ;
    }

    return xpos ;
}

#ifdef _XmDEBUG_XMTABLIST

static char *
units_image(XtEnum units)
{
  static char buf[100];

  switch (units)
    {
    case XmPIXELS:
      return "px";
    case Xm100TH_MILLIMETERS:
      return "100mm";
    case Xm1000TH_INCHES:
      return "1000in";
    case Xm100TH_POINTS:
      return "100pt";
    case Xm100TH_FONT_UNITS:
      return "100fu";
    case XmINCHES:
      return "in";
    case XmCENTIMETERS:
      return "cm";
    case XmMILLIMETERS:
      return "mm";
    case XmPOINTS:
      return "pt";
    case XmFONT_UNITS:
      return "fu";
    default:
      sprintf(buf, "<Unknown units %d>", units);
      return buf;
    }
}

static char *
model_image(XmOffsetModel model)
{
  static char buf[100];

  switch (model)
    {
    case XmABSOLUTE:
      return "abs.";
    case XmRELATIVE:
      return "rel.";
    default:
      sprintf(buf, "<Unknown model %d>", model);
      return buf;
    }
}

static char *
alignment_image(XtEnum alignment)
{
  static char buf[100];

  switch (alignment)
    {
    case XmALIGNMENT_BEGINNING:
      return "beginning";
    case XmALIGNMENT_CENTER:
      return "center";
    case XmALIGNMENT_END:
      return "end";
    default:
      sprintf(buf, "<Unknown alignment %d>", alignment);
      return buf;
    }
}

void 
_Xm_dump_tab(XmTab tab)
{
  unsigned int mark = _XmTabMark(tab);
  /* unsigned int ref_count = ((_XmTab)tab)->ref_count; */
  float value = _XmTabValue(tab);
  unsigned char units = _XmTabUnits(tab);
  XmOffsetModel model = _XmTabModel(tab);
  unsigned char alignment = _XmTabAlign(tab);
  char *decimal = _XmTabDecimal(tab);
  XmTab next = _XmTabNext(tab);
  XmTab prev = _XmTabPrev(tab);

  printf ("%p: %f %s, %s from %s '%s', %p %p, %d\n",
	  tab, value, units_image(units), 
	  model_image(model), alignment_image(alignment), 
	  decimal, next, prev, mark);
}

void
_Xm_dump_tablist(XmTabList list)
{
  int i;
  int count = _XmTabLCount(list);
  XmTab tab = _XmTabLStart(list);

  printf("(XmTabList)%p: count %d, start %p.\n", list, count, tab);
  for (i = 0; i < count; i++)
    {
      printf ("  #%d> ", i);
      _Xm_dump_tab(tab);
      tab = _XmTabNext(tab);
    }
}

#endif /* _XmDEBUG_XMTABLIST */
