/*	$OpenBSD: fty_enum.c,v 1.9 2001/01/22 18:02:17 millert Exp $	*/


/*
 * THIS CODE IS SPECIFICALLY EXEMPTED FROM THE NCURSES PACKAGE COPYRIGHT.
 * You may freely copy it for use as a template for your own field types.
 * If you develop a field type that might be of general use, please send
 * it back to the ncurses maintainers for inclusion in the next version.
 */
/***************************************************************************
*                                                                          *
*  Author : Juergen Pfeifer, juergen.pfeifer@gmx.net                       *
*                                                                          *
***************************************************************************/

#include "form.priv.h"

MODULE_ID("$From: fty_enum.c,v 1.15 2000/12/09 23:46:12 tom Exp $")

typedef struct {
  char **kwds;
  int  count;
  bool checkcase;
  bool checkunique;
} enumARG;

/*---------------------------------------------------------------------------
|   Facility      :  libnform  
|   Function      :  static void *Make_Enum_Type( va_list * ap )
|   
|   Description   :  Allocate structure for enumeration type argument.
|
|   Return Values :  Pointer to argument structure or NULL on error
+--------------------------------------------------------------------------*/
static void *Make_Enum_Type(va_list * ap)
{
  enumARG *argp = (enumARG *)malloc(sizeof(enumARG));

  if (argp)
    {
      int cnt = 0;
      char **kp = (char **)0;
      int ccase, cunique;

      argp->kwds        = va_arg(*ap,char **);
      ccase             = va_arg(*ap,int);
      cunique           = va_arg(*ap,int);
      argp->checkcase   = ccase   ? TRUE : FALSE;
      argp->checkunique = cunique ? TRUE : FALSE;

      kp = argp->kwds;
      while( kp && (*kp++) ) cnt++;
      argp->count = cnt;
    }
  return (void *)argp;
}

/*---------------------------------------------------------------------------
|   Facility      :  libnform  
|   Function      :  static void *Copy_Enum_Type( const void * argp )
|   
|   Description   :  Copy structure for enumeration type argument.  
|
|   Return Values :  Pointer to argument structure or NULL on error.
+--------------------------------------------------------------------------*/
static void *Copy_Enum_Type(const void * argp)
{
  enumARG *result = (enumARG *)0;

  if (argp)
    {
      const enumARG *ap = (const enumARG *)argp;

      result = (enumARG *)malloc(sizeof(enumARG));
      if (result)
	*result = *ap;
    }
  return (void *)result;
}

/*---------------------------------------------------------------------------
|   Facility      :  libnform  
|   Function      :  static void Free_Enum_Type( void * argp )
|   
|   Description   :  Free structure for enumeration type argument.
|
|   Return Values :  -
+--------------------------------------------------------------------------*/
static void Free_Enum_Type(void * argp)
{
  if (argp) 
    free(argp);
}

#define SKIP_SPACE(x) while(((*(x))!='\0') && (is_blank(*(x)))) (x)++
#define NOMATCH 0
#define PARTIAL 1
#define EXACT   2

/*---------------------------------------------------------------------------
|   Facility      :  libnform  
|   Function      :  static int Compare(const unsigned char * s,  
|                                       const unsigned char * buf,
|                                       bool  ccase )
|   
|   Description   :  Check wether or not the text in 'buf' matches the
|                    text in 's', at least partial.
|
|   Return Values :  NOMATCH   - buffer doesn't match
|                    PARTIAL   - buffer matches partially
|                    EXACT     - buffer matches exactly
+--------------------------------------------------------------------------*/
static int Compare(const unsigned char *s, const unsigned char *buf, 
		   bool ccase)
{
  SKIP_SPACE(buf); /* Skip leading spaces in both texts */
  SKIP_SPACE(s);

  if (*buf=='\0')
    {
      return (((*s)!='\0') ? NOMATCH : EXACT);
    } 
  else 
    {
      if (ccase)
	{
	  while(*s++ == *buf)
	    {
	      if (*buf++=='\0') return EXACT;
	    } 
	} 
      else 
	{
	  while(toupper(*s++)==toupper(*buf))
	    {
	      if (*buf++=='\0') return EXACT;
	    }
	}
    }
  /* At this location buf points to the first character where it no longer
     matches with s. So if only blanks are following, we have a partial
     match otherwise there is no match */
  SKIP_SPACE(buf);
  if (*buf) 
    return NOMATCH;

  /* If it happens that the reference buffer is at its end, the partial
     match is actually an exact match. */
  return ((s[-1]!='\0') ? PARTIAL : EXACT);
}

/*---------------------------------------------------------------------------
|   Facility      :  libnform  
|   Function      :  static bool Check_Enum_Field(
|                                      FIELD * field,
|                                      const void  * argp)
|   
|   Description   :  Validate buffer content to be a valid enumeration value
|
|   Return Values :  TRUE  - field is valid
|                    FALSE - field is invalid
+--------------------------------------------------------------------------*/
static bool Check_Enum_Field(FIELD * field, const void  * argp)
{
  char **kwds       = ((const enumARG *)argp)->kwds;
  bool ccase        = ((const enumARG *)argp)->checkcase;
  bool unique       = ((const enumARG *)argp)->checkunique;
  unsigned char *bp = (unsigned char *)field_buffer(field,0);
  char *s, *t, *p;
  int res;
  
  while( kwds && (s=(*kwds++)) )
    {
      if ((res=Compare((unsigned char *)s,bp,ccase))!=NOMATCH)
	{
	  p=t=s; /* t is at least a partial match */
	  if ((unique && res!=EXACT)) 
	    {
	      while( kwds && (p = *kwds++) )
		{
		  if ((res=Compare((unsigned char *)p,bp,ccase))!=NOMATCH)
		    {
		      if (res==EXACT)
			{
			  t = p;
			  break;
			}
		      else
			t = (char *)0;
		    }
		}
	    }	  
	  if (t)
	    {
	      set_field_buffer(field,0,t);
	      return TRUE;
	    }
	  if (!p)
	    break;
	}
    }
  return FALSE;
}

static const char *dummy[] = { (char *)0 };

/*---------------------------------------------------------------------------
|   Facility      :  libnform  
|   Function      :  static bool Next_Enum(FIELD * field,
|                                          const void * argp)
|   
|   Description   :  Check for the next enumeration value
|
|   Return Values :  TRUE  - next value found and loaded
|                    FALSE - no next value loaded
+--------------------------------------------------------------------------*/
static bool Next_Enum(FIELD * field, const void * argp)
{
  const enumARG *args = (const enumARG *)argp;
  char **kwds       = args->kwds;
  bool ccase        = args->checkcase;
  int cnt           = args->count;
  unsigned char *bp = (unsigned char *)field_buffer(field,0);

  if (kwds) {
    while(cnt--)
      {
	if (Compare((unsigned char *)(*kwds++),bp,ccase)==EXACT) 
	  break;
      }
    if (cnt<=0)
      kwds = args->kwds;
    if ((cnt>=0) || (Compare((const unsigned char *)dummy,bp,ccase)==EXACT))
      {
	set_field_buffer(field,0,*kwds);
	return TRUE;
      }
  }
  return FALSE;
}

/*---------------------------------------------------------------------------
|   Facility      :  libnform  
|   Function      :  static bool Previous_Enum(
|                                          FIELD * field,
|                                          const void * argp)
|   
|   Description   :  Check for the previous enumeration value
|
|   Return Values :  TRUE  - previous value found and loaded
|                    FALSE - no previous value loaded
+--------------------------------------------------------------------------*/
static bool Previous_Enum(FIELD * field, const void * argp)
{
  const enumARG *args = (const enumARG *)argp;
  int cnt       = args->count;
  char **kwds   = &args->kwds[cnt-1];
  bool ccase    = args->checkcase;
  unsigned char *bp = (unsigned char *)field_buffer(field,0);

  if (kwds) {
    while(cnt--)
      {
	if (Compare((unsigned char *)(*kwds--),bp,ccase)==EXACT) 
	  break;
      }
    
    if (cnt<=0)
      kwds  = &args->kwds[args->count-1];
    
    if ((cnt>=0) || (Compare((const unsigned char *)dummy,bp,ccase)==EXACT))
      {
	set_field_buffer(field,0,*kwds);
	return TRUE;
      }
  }
  return FALSE;
}


static FIELDTYPE typeENUM = {
  _HAS_ARGS | _HAS_CHOICE | _RESIDENT,
  1,                           /* this is mutable, so we can't be const */
  (FIELDTYPE *)0,
  (FIELDTYPE *)0,
  Make_Enum_Type,
  Copy_Enum_Type,
  Free_Enum_Type,
  Check_Enum_Field,
  NULL,
  Next_Enum,
  Previous_Enum
};

NCURSES_EXPORT_VAR(FIELDTYPE*) TYPE_ENUM = &typeENUM;

/* fty_enum.c ends here */