/*
 * Copyright (c) 1998 Sasha Vasko <sashav@sprintmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*#define DO_CLOCKING      */

#define TRUE 1
#define FALSE

#include "../configure.h"

#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>

#ifdef DO_CLOCKING
#include <time.h>
#endif

#ifdef ISC			/* Saul */
#include <sys/bsdtypes.h>	/* Saul */
#endif /* Saul */

#include <stdlib.h>
#if defined ___AIX || defined _AIX || defined __QNX__ || defined ___AIXV3 || defined AIXV3 || defined _SEQUENT_
#include <sys/select.h>
#endif

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xproto.h>
#include <X11/Xatom.h>
#include <X11/Intrinsic.h>
#ifdef I18N
#include <X11/Xlocale.h>
#endif

#include "../include/aftersteplib.h"
#include "../include/parser.h"

#ifdef DEBUG_PARSER
#define LOG1(a)       fprintf( stderr, a );
#define LOG2(a,b)    fprintf( stderr, a, b );
#define LOG3(a,b,c)    fprintf( stderr, a, b, c );
#define LOG4(a,b,c,d)    fprintf( stderr, a, b, c, d );
#else
#define LOG1(a)
#define LOG2(a,b)
#define LOG3(a,b,c)
#define LOG4(a,b,c,d)
#endif


char *_disabled_keyword = DISABLED_KEYWORD;

void
PrepareSyntax (SyntaxDef * syntax)
{
  if (syntax)
    {
      register int i;

      InitHash (syntax);
      BuildHash (syntax);
      for (i = 0; syntax->terms[i].keyword; i++)
	if (syntax->terms[i].sub_syntax)
	  PrepareSyntax (syntax->terms[i].sub_syntax);
    }
}

void
FreeSyntaxHash (SyntaxDef * syntax)
{
  if (syntax)
    {
      register int i;
      if (syntax->term_hash)
	free (syntax->term_hash);
      syntax->term_hash = NULL;

      for (i = 0; syntax->terms[i].keyword; i++)
	if (syntax->terms[i].sub_syntax)
	  FreeSyntaxHash (syntax->terms[i].sub_syntax);
    }
}

/* Syntax and storage stack operations */
void
PushSyntax (ConfigDef * config, SyntaxDef * syntax)
{
  SyntaxStack *pnew = (SyntaxStack *) safemalloc (sizeof (SyntaxStack));

  pnew->next = config->current_syntax;
  pnew->syntax = syntax;
  config->current_syntax = pnew;
  config->syntax = syntax;
}

int
PopSyntax (ConfigDef * config)
{
  if (config->current_syntax->next)
    {
      SyntaxStack *pold = config->current_syntax;
      config->current_syntax = config->current_syntax->next;
      config->syntax = config->current_syntax->syntax;
      free (pold);
      return 1;
    }
  return 0;
}

void
PushStorage (ConfigDef * config, FreeStorageElem ** tail)
{
  StorageStack *pnew = (StorageStack *) safemalloc (sizeof (StorageStack));
  pnew->tail = tail;
  pnew->next = config->current_tail;
  config->current_tail = pnew;
}

int
PopStorage (ConfigDef * config)
{
  if (config->current_tail->next)
    {
      StorageStack *pold = config->current_tail;
      config->current_tail = config->current_tail->next;
      free (pold);
      return 1;
    }
  return 0;
}


/* Creating and Initializing new ConfigDef */
ConfigDef *
NewConfig (char *myname, SyntaxDef * syntax, ConfigDataType type, void *source, SpecialFunc special)
{
  ConfigDef *new_conf;

  if (myname == NULL)
    return NULL;

  new_conf = (ConfigDef *) safemalloc (sizeof (ConfigDef));
  new_conf->special = special;
  new_conf->fd = -1;
  new_conf->fp = NULL;
  new_conf->bNeedToCloseFile = 0;
  if (source)
    switch (type)
      {
      case CDT_Filename:
	{
	  char *realfilename = PutHome ((char *) source);
	  if (!realfilename)
	    {
	      free (new_conf);
	      return NULL;
	    }
	  new_conf->fd = open (realfilename, O_RDONLY);
	  free (realfilename);
	  new_conf->bNeedToCloseFile = 1;
	}
	break;
      case CDT_FilePtr:
	new_conf->fp = (FILE *) source;
	new_conf->fd = fileno (new_conf->fp);
	break;
      case CDT_FileDesc:
	new_conf->fd = *((int *) source);
	break;
      case CDT_Data:
	break;
      }

  if (new_conf->fd != -1 && new_conf->fp == NULL)
    new_conf->fp = fdopen (new_conf->fd, "rt");

  new_conf->myname = (char *) safemalloc (strlen (myname) + 1);
  strcpy (new_conf->myname, myname);
  new_conf->current_syntax = NULL;
  new_conf->current_tail = NULL;
  PushSyntax (new_conf, syntax);

  PrepareSyntax (syntax);

  /* allocated to store lines read from the file */
  new_conf->buffer = NULL;
  new_conf->buffer_size = 0;

  /* this is the current parsing information */
  new_conf->tline = new_conf->tdata = new_conf->tline_start = NULL;
  new_conf->current_term = NULL;
  new_conf->current_data_size = MAXLINELENGTH + 1;
  new_conf->current_data = (char *) safemalloc (new_conf->current_data_size);
  new_conf->current_data_len = 0;
  new_conf->cursor = NULL;
  return new_conf;
}

/* reader initialization */
ConfigDef *
InitConfigReader (char *myname, SyntaxDef * syntax, ConfigDataType type, void *source, SpecialFunc special)
{
  ConfigDef *new_conf = NewConfig (myname, syntax, type, source, special);

  if (new_conf == NULL)
    return NULL;
  if (source == NULL)
    {
      DestroyConfig (new_conf);
      return NULL;
    }

  if (type == CDT_Data)
    {
      /* allocate to store entire data */
      new_conf->buffer = (char *) safemalloc (strlen ((char *) source) + 1);
      strcpy (new_conf->buffer, (char *) source);
    }
  else
    {
      new_conf->buffer = (char *) safemalloc (MAXLINELENGTH + 1);
      new_conf->buffer[0] = '\0';
    }

  /* this is the current parsing information */
  new_conf->cursor = &(new_conf->buffer[0]);

  return new_conf;
}

/* debugging stuff */
#ifdef DEBUG_PARSER
void
PrintSyntax (SyntaxDef * syntax)
{
  int i;
  fprintf (stderr, "\nSentence Terminator: [0x%2.2x]", syntax->terminator);
  fprintf (stderr, "\nConfig Terminator:   [0x%2.2x]", syntax->file_terminator);
  fprintf (stderr, "\nTerm's hash table:");
  for (i = 0; i < syntax->term_hash_size; i++)
    {
      TermDef *pterm = syntax->term_hash[i];
      if (pterm)
	fprintf (stderr, "\n  Hash value %d:", i);
      while (pterm)
	{
	  fprintf (stderr, "\n\t\t[%lx][%d][%d][%s]", pterm->flags, pterm->type, pterm->id, pterm->keyword);
	  if (pterm->sub_syntax)
	    {
	      fprintf (stderr, "\n SubSyntax :");
	      PrintSyntax (pterm->sub_syntax);
	    }
	  pterm = pterm->brother;
	}
    }
  fprintf (stderr, "\nEnd of Term's hash table.");
}


void
PrintConfigReader (ConfigDef * config)
{
  PrintSyntax (config->syntax);
}

void
PrintFreeStorage (FreeStorageElem * storage)
{
  int i;
  while (storage)
    {
      fprintf (stderr, "\nTerm's keyword: [%s]", storage->term->keyword);
      fprintf (stderr, "\nData:");
      for (i = 0; i < storage->argc; i++)
	fprintf (stderr, "\n  %d:\t[%s]", i, storage->argv[i]);
      PrintFreeStorage (storage->sub);
      storage = storage->next;
    }
}
#endif


/* reader de-initialization */
void
DestroyConfig (ConfigDef * config)
{
  free (config->myname);
  if (config->buffer)
    free (config->buffer);
  if (config->current_data)
    free (config->current_data);
  while (PopSyntax (config));
  if (config->current_syntax);
  free (config->current_syntax);
  while (PopStorage (config));
  if (config->current_tail)
    free (config->current_tail);
  if (config->syntax)
    FreeSyntaxHash (config->syntax);
  if (config->bNeedToCloseFile && config->fd != -1)
    close (config->fd);
  free (config);
}

char *
GetToNextLine (ConfigDef * config)
{
  for (; *(config->cursor) != '\0' &&
       *(config->cursor) != config->syntax->terminator; config->cursor++)
    if (*(config->cursor) == config->syntax->file_terminator)
      return NULL;

  if (*(config->cursor) != '\0')
    config->cursor++;
  if (*(config->cursor) == '\0')
    {
      if (config->fp)
	{
	  if (!fgets (config->buffer, MAXLINELENGTH, config->fp))
	    return NULL;
	  config->cursor = &(config->buffer[0]);
	}
      else
	return NULL;
    }

  return (config->cursor);
}
/* this function finds next valid statement:
   - not comments, 
   - not an empty line, 
   - if prepended with * - should have MyName at the beginning
   and :
   - sets tline to the beginning of the keyword (not MyName),
   - copies arguments into the current_data
   - sets current_data_len
   it will read data from the file as needed untill the end of file 
   or config->syntax.file_terminator or EOF is reached
   Return: NULL if end of config reached, otherwise same as tline.
 */

char *
GetNextStatement (ConfigDef * config, int my_only)
{
  char *cur = config->cursor;	/* don't forget to save it back ! */

  while (1)
    {
      config->tline_start = cur;	/* remember beginning of the entire config option */
      for (; *cur != '\0' && *cur != config->syntax->terminator; cur++)
	{
	  if (*cur == config->syntax->file_terminator)
	    return NULL;
	  if (!isspace (*cur))
	    {
	      register int i;
	      int i_saved;

	      config->current_flags = CF_NONE;

	      if (*cur == COMMENTS_CHAR)
		{		/* let's check for DISABLE keyword */
		  for (i = 1; i < DISABLED_KEYWORD_SIZE; i++)
		    if (*(cur + i) == '\0' || *(cur + i) != _disabled_keyword[i])
		      break;
		  if (i < DISABLED_KEYWORD_SIZE)
		    break;	/* skip entire line */
		  config->current_flags |= CF_DISABLED_OPTION;
		  /* let's skip few spaces here */
		  for (cur = cur + i; isspace (*cur) && *cur != config->syntax->terminator; cur++);
		  if (*cur == '\0' || *cur == config->syntax->terminator)
		    break;	/* not a valid option */
		}

	      if (*cur == MYNAME_CHAR)
		{		/* check if we have MyName here */
		  for (i = 0; *cur != '\0' && config->myname[i] != '\0'; i++)
		    if (tolower (config->myname[i]) != tolower (*(++cur)))
		      break;
		  if (config->myname[i] != '\0')
		    {		/* that was a foreign optiion - belongs to the other executable */
		      if (my_only)
			break;
		      config->current_flags |= CF_FOREIGN_OPTION;
		    }
		  cur++;
		}
	      else
		config->current_flags |= CF_PUBLIC_OPTION;

	      config->tline = cur;	/*that will be the begginnig of the term */

	      /* now we should copy everything from after the first space to
	         config->current_data and set current_data_len ; */
	      for (; !isspace (*cur) && *cur != config->syntax->terminator && (*cur); cur++);
	      for (; isspace (*cur) && *cur && *cur != config->syntax->terminator; cur++);
	      config->tdata = cur;	/* that will be the beginning of our data */
	      for (i = 0; *(cur + i) && *(cur + i) != config->syntax->terminator; i++)
		{
		  /* buffer overrun prevention */
		  if (i >= config->current_data_size)
		    {
		      char *new_data;
		      register int k = i;
		      config->current_data_size += MAXLINELENGTH >> 3;
		      new_data = (char *) safemalloc (config->current_data_size);
		      for (; k; k--)
			new_data[k] = config->current_data[k];
		      free (config->current_data);
		      config->current_data = new_data;
		    }
		  config->current_data[i] = *(cur + i);
		}
	      /* now let's go back and remove trailing spaces */
	      i_saved = i;
	      for (i--; i >= 0; i--)
		if (!isspace (config->current_data[i]))
		  break;
	      i++;
	      config->current_data_len = i;
	      config->current_data[i] = '\0';
	      i = i_saved;
	      if (*(cur + i) && *(cur + i) == config->syntax->terminator)
		i++;
	      config->cursor = cur + i;		/* Saving position for future use */
	      return config->tline;
	    }
	}
      /* reading file here */
      if ((cur = GetToNextLine (config)) == NULL)
	return NULL;
    }
  return NULL;
}



void
ProcessSubSyntax (ConfigDef * config, FreeStorageElem ** tail, SyntaxDef * syntax)
{
  PushStorage (config, tail);

  if (config->syntax->terminator == syntax->file_terminator)
    {				/* need to push back term's data into config buffer */
      config->cursor = config->tdata;
    }
  else if (config->syntax->terminator == syntax->terminator)
    {				/* need to push back entire term's line into config buffer */
      config->cursor = config->tline_start;
    }
  PushSyntax (config, syntax);
}

/* create and format freestorage elem from the statement found */
void
ProcessStatement (ConfigDef * config)
{
  FreeStorageElem *pNext;
  TermDef *pterm = config->current_term;

  if (IsForeignOption (config))
    return;

  if ((pNext = AddFreeStorageElem (config->syntax, config->current_tail->tail, pterm, ID_ANY)) == NULL)
    return;

  if (config->current_data_len <= 0)
    {
      pNext->argv = NULL;
      pNext->argc = 0;
    }
  else
    {
      int i, count;
      char *cur;

      pNext->flags = config->current_flags;

      if (!(pterm->flags & TF_DONT_SPLIT))
	{
	  cur = config->current_data;
	  for (pNext->argc = 0; *cur;)
	    {
	      for (count = 0; !isspace (*cur) && *cur; count++)
		cur++;
	      while (isspace (*cur) && *cur)
		cur++;
	      if (count)
		pNext->argc++;
	    }
	}
      else if (pterm->flags & TF_INDEXED)
	pNext->argc = 2;
      else
	pNext->argc = 1;

      if (pNext->argc == 0)
	pNext->argv = NULL;
      else
	{
	  pNext->argv = CreateStringArray (pNext->argc);
	  pNext->argv[0] = (char *) safemalloc (config->current_data_len + 1);

	  cur = config->current_data;
	  i = 0;
	  if (pterm->flags & TF_DONT_SPLIT)
	    {
	      if (pterm->flags & TF_INDEXED)
		{		/* first token should be index thou */
		  for (count = 0; !isspace (*cur) && *cur; count++)
		    pNext->argv[0][count] = *(cur++);
		  while (isspace (*cur) && *cur)
		    cur++;
		  pNext->argv[0][count] = '\0';
		  i++;
		  pNext->argv[i] = &(pNext->argv[0][count + 1]);
		}
	      /* the rest of the text is treated as single token */
	      strcpy (pNext->argv[i], cur);
	    }
	  else
	    {
	      for (; i < pNext->argc; i++)
		{
		  for (count = 0; !isspace (*cur) && *cur; count++)
		    pNext->argv[i][count] = *(cur++);
		  while (isspace (*cur) && *cur)
		    cur++;
		  if (!count)
		    break;

		  pNext->argv[i][count] = '\0';
		  if ((i + 1) < pNext->argc)
		    pNext->argv[i + 1] = &(pNext->argv[i][count + 1]);
		}
	    }
	}
    }
  config->current_tail->tail = &(pNext->next);
  if (pterm->sub_syntax)
    ProcessSubSyntax (config, &(pNext->sub), pterm->sub_syntax);
  return;
}



/* main parsing procedure */
int
ParseConfig (ConfigDef * config, FreeStorageElem ** tail)
{
  int TopLevel = 0;

  PushStorage (config, tail);
  /* get line */
  while (!TopLevel)
    {
      while (GetNextStatement (config, 1))
	{			/* untill not end of text */
#ifdef DEBUG_PARSER
	  fprintf (stderr, "\nSentence Found:[%s]\n,\tData=\t[%s]", config->tline, config->current_data);
	  fprintf (stderr, "\nLooking for the Term...");
#endif
	  /* find term */
	  if ((config->current_term = FindStatementTerm (config->tline, config->syntax)))
	    {
#ifdef DEBUG_PARSER
	      fprintf (stderr, "\nTerm Found:[%s]", config->current_term->keyword);
#endif
	      if (config->current_term->flags & TF_SPECIAL_PROCESSING)
		{
		  if (config->special)
		    {
		      FreeStorageElem **ctail = config->current_tail->tail;
		      if (!(*(config->special)) (config, ctail))
			break;
		      for (; (*ctail); tail = &((*ctail)->next));
		      config->current_tail->tail = ctail;
		    }
		}
	      else
		ProcessStatement (config);
	      if (config->current_term->flags & TF_SYNTAX_TERMINATOR)
		break;
	    }
#ifdef DEBUG_PARSER
	  else
	    fprintf (stderr, "\nTerm Not Found!");
#endif

	}			/* end while( GetNextStatement() ) */
      /* trying to see if we can get to higher level syntax */
      if (!PopSyntax (config))
	TopLevel = 1;
      if (!PopStorage (config))
	TopLevel = 1;
    }				/* end while( !TopLevel ) */

  return 1;
}

/*********************************************************************************************/
/*                                     FreeStorage management                                */
/*********************************************************************************************/
/* Create new FreeStorage Elem and add it to the supplied storage's tail */
FreeStorageElem *
AddFreeStorageElem (SyntaxDef * syntax, FreeStorageElem ** tail, TermDef * pterm, int id)
{
  FreeStorageElem *new_elem = NULL;

  if (pterm == NULL)
    pterm = FindTerm (syntax, TT_ANY, id);

  if (pterm)
    {
      new_elem = (FreeStorageElem *) safemalloc (sizeof (FreeStorageElem));
      new_elem->term = pterm;
      new_elem->argc = 0;
      new_elem->argv = NULL;
      new_elem->flags = 0;
      new_elem->next = *tail;
      new_elem->sub = NULL;
      *tail = new_elem;
    }
  return new_elem;
}

/* Duplicate existing FreeStorage Elem */
FreeStorageElem *
DupFreeStorageElem (FreeStorageElem * source)
{
  FreeStorageElem *new_elem = NULL;

  if (source)
    {
      new_elem = (FreeStorageElem *) safemalloc (sizeof (FreeStorageElem));
      new_elem->term = source->term;
      new_elem->argc = source->argc;
      /* duplicating argv here */
      new_elem->argv = DupStringArray (source->argc, source->argv);
      new_elem->flags = source->flags;
      new_elem->next = NULL;
      new_elem->sub = NULL;
      if (new_elem->sub)
	{
	  FreeStorageElem *psub, **pnew_sub = &(new_elem->sub);
	  for (psub = new_elem->sub; psub; psub = psub->next)
	    {
	      *pnew_sub = DupFreeStorageElem (psub);
	      pnew_sub = &((*pnew_sub)->next);
	    }
	}
    }
  return new_elem;
}

void
ReverseFreeStorageOrder (FreeStorageElem ** storage)
{
  FreeStorageElem *pNewHead = NULL, *pNext;
  for (; *storage; *storage = pNext)
    {
      pNext = (*storage)->next;
      (*storage)->next = pNewHead;
      pNewHead = *storage;
    }
  *storage = pNewHead;
}

void
CopyFreeStorage (FreeStorageElem ** to, FreeStorageElem * from)
{
  FreeStorageElem *pNew;
  if (to == NULL)
    return;
  for (; from; from = from->next)
    {
      if ((pNew = DupFreeStorageElem (from)) == NULL)
	continue;
      pNew->next = *to;
      *to = pNew;
    }
}

int
CountFreeStorageElems (FreeStorageElem * storage)
{
  int count = 0;
  for (; storage; storage = storage->next)
    count++;
  return count;
}

/* this one will scan list of FreeStorage elements and will move all elements with
   specifyed flags mask into the garbadge_bin
 */

void
StorageCleanUp (FreeStorageElem ** storage, FreeStorageElem ** garbadge_bin, unsigned long mask)
{
  FreeStorageElem **ppCurr, *pToRem;

  for (ppCurr = storage; *ppCurr; ppCurr = &((*ppCurr)->next))
    {
      while ((*ppCurr)->flags & mask)
	{
	  pToRem = *ppCurr;
	  *ppCurr = pToRem->next;
	  pToRem->next = *garbadge_bin;
	  *garbadge_bin = pToRem;
	}
      if ((*ppCurr)->sub)
	StorageCleanUp (&((*ppCurr)->sub), garbadge_bin, mask);
    }
}

/* memory deallocation */
void
DestroyFreeStorage (FreeStorageElem ** storage)
{
  if (storage)
    if (*storage)
      {
	DestroyFreeStorage (&((*storage)->next));
	DestroyFreeStorage (&((*storage)->sub));
	/* that will deallocate everything */
	if ((*storage)->argc && (*storage)->argv)
	  {
	    if ((*storage)->argv[0])
#ifdef DEBUG_PARSER
	      {
		fprintf (stderr, "\n DestroyFreeStorage: deallocating [%s].", (*storage)->argv[0]);
#endif
		free ((*storage)->argv[0]);
#ifdef DEBUG_PARSER
	      }
	    else
	      fprintf (stderr, "\n DestroyFreeStorage: no data to deallocate.");
#endif
	    free ((*storage)->argv);
	  }
	free (*storage);
	*storage = NULL;
      }
}

/* free storage post processing code */
int
ReadConfigItem (ConfigItem * item, FreeStorageElem * stored)
{
  if (item->memory && item->ok_to_free)
    free (item->memory);
  item->memory = NULL;

  if (stored)
    {
      int pos = 0;
      item->memory = NULL;
      item->ok_to_free = 0;
      if (stored->argv == NULL || stored->argc == 0)
	return 0;
      if (stored->argv[pos] == NULL)
	return 0;
      if (stored->term->flags & TF_INDEXED)
	{
	  item->index = atoi (stored->argv[pos++]);
	  if (stored->argc < 2)
	    return 0;
	  if (!stored->argv[pos])
	    return 0;
	}

      switch (stored->term->type)
	{
	case TT_INTEGER:
	  item->data.integer = atol (stored->argv[pos++]);
	  break;
	case TT_COLOR:
	case TT_FONT:
	case TT_FILENAME:
	case TT_TEXT:
	case TT_PATHNAME:
	  item->memory = safemalloc (strlen (stored->argv[pos]) + 1);
	  item->data.string = (char *) (item->memory);
	  strcpy (item->data.string, stored->argv[pos++]);
	  break;
	case TT_QUOTED_TEXT:
	  {
	    register int i = 1;
	    int len = strlen (stored->argv[pos]);
	    char *ptr = stored->argv[pos];
	    for (; *ptr != '\0'; ptr++)
	      if (*ptr == '"')
		break;
	    for (; *(ptr + i) != '\0'; i++)
	      {
		if (*(ptr + i) == '"')
		  break;
		if (*(ptr + i) == '\\')
		  if (*(ptr + (++i)) == '\0')
		    break;
	      }
	    if (i < 2 || *ptr != '"')
	      {
		fprintf (stderr, "\n%s: bad quoted string [%s]. Ignoring!", MyName, stored->argv[pos]);
		return 0;
	      }
	    if (*(ptr + i) == '\0')
	      {
		fprintf (stderr, "\n%s: terminating quote missing in [%s]. Ignoring!", MyName, stored->argv[pos]);
		return 0;
	      }
	    len = i;
	    item->memory = safemalloc (len);
	    item->data.string = (char *) (item->memory);
	    len--;
	    for (ptr++, i = 0; i < len; i++)
	      item->data.string[i] = *(ptr + i);
	    item->data.string[i] = '\0';
	    pos++;
	  }
	  break;
	case TT_GEOMETRY:
	  item->data.geometry.flags =
	    XParseGeometry (stored->argv[pos++],
			    &(item->data.geometry.x),
			    &(item->data.geometry.y),
			    &(item->data.geometry.width),
			    &(item->data.geometry.height));
	  break;
	}
      return 1;
    }
  return 0;
}

/****************************************************************************************/
/*                        Assorted utility functions                                    */
/****************************************************************************************/
/* service functions */
void
FlushConfigBuffer (ConfigDef * config)
{
  config->buffer[0] = '\0';
  config->cursor = &(config->buffer[0]);
}

char **
CreateStringArray (size_t elem_num)
{
  char **array = (char **) safemalloc (elem_num * sizeof (char *));
  int i;
  for (i = 0; i < elem_num; i++)
    array[i] = NULL;
  return array;
}

size_t
GetStringArraySize (int argc, char **argv)
{
  size_t size = 0;

  for (argc--; argc >= 0; argc--)
    if (argv[argc])
      size += strlen (argv[argc]) + 1;
  return size;
}

char **
DupStringArray (int argc, char **argv)
{
  int i;
  size_t data_size;
  char **array = CreateStringArray (argc);

  data_size = GetStringArraySize (argc, argv);
  if (data_size && array)
    {
      array[0] = (char *) safemalloc (data_size);
      for (i = 0; i < data_size; i++)
	array[0][i] = argv[0][i];
      for (i = 1; i < argc; i++)
	if (argv[i])
	  array[i] = array[0] + (argv[i] - argv[0]);
    }
  return array;
}

void
AddStringToArray (int *argc, char ***argv, char *new_string)
{
  int i;
  size_t data_size;
  char **array;

  if (new_string == NULL)
    return;

  array = CreateStringArray (*argc + 1);
  data_size = GetStringArraySize (*argc, *argv);
  if (data_size && array)
    {
      array[0] = (char *) safemalloc (data_size + strlen (new_string) + 1);
      for (i = 0; i < data_size; i++)
	array[0][i] = (*argv)[0][i];
      for (i = 1; i < *argc; i++)
	if ((*argv)[i])
	  array[i] = array[0] + ((*argv)[i] - (*argv)[0]);
      array[i] = array[0] + data_size;
      strcpy (array[i], new_string);
      (*argc)++;
    }
}

/* end StringArray functionality */


void
InitMyGeometry (MyGeometry * geometry)
{
  geometry->flags = XValue | YValue;
  geometry->x = geometry->y = 1;
  geometry->width = geometry->height = 0;
}

/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
/*                   config Writing stuff                                  */
/***************************************************************************/
/* writer initialization */
ConfigDef *
InitConfigWriter (char *myname, SyntaxDef * syntax, ConfigDataType type, void *source)
{
#ifdef WITH_CONFIG_WRITER
  ConfigDef *new_conf = NewConfig (myname, syntax, type, source, NULL);

  if (new_conf == NULL)
    return NULL;
  if (source != NULL)
    {
      if (type == CDT_Data)
	{
	  if ((new_conf->buffer_size = strlen (source)) > 0)
	    {
	      new_conf->buffer = (char *) safemalloc (++(new_conf->buffer_size));
	      strcpy (new_conf->buffer, (char *) source);
	    }
	}
      else
	{			/* reading entire file in memory here */
	  struct stat file_stats;

	  if (new_conf->fd != -1)
	    if (fstat (new_conf->fd, &file_stats) == 0)
	      if ((new_conf->buffer_size = file_stats.st_size) > 0)
		{
		  int bytes_read;

		  new_conf->buffer = (char *) safemalloc (++new_conf->buffer_size);
		  if ((bytes_read = read (new_conf->fd, new_conf->buffer, file_stats.st_size)) > 0)
		    new_conf->buffer[bytes_read] = '\0';
		  else
		    {
		      free (new_conf->buffer);
		      new_conf->buffer = NULL;
		      new_conf->buffer_size = 0;
		    }
		}
	}
      if (new_conf->buffer == NULL)
	{
	  DestroyConfig (new_conf);
	  return NULL;
	}
      new_conf->cursor = &(new_conf->buffer[0]);
    }

  return new_conf;
#else
  return NULL;
#endif
}
/***************************************************************************/
/* functions used in writing config */
#ifdef WITH_CONFIG_WRITER
/* Finds element in FreeStorage that represents this particular term
 * it then removes it from the list of elems and returns pointer to it 
 */
FreeStorageElem *
FindTermAndRemove (FreeStorageElem ** storage, TermDef * pterm)
{
  FreeStorageElem **ppCurr = storage, *pRes;

  if (storage == NULL)
    return NULL;
  for (; *ppCurr != NULL; ppCurr = &((*ppCurr)->next))
    if ((*ppCurr)->term == pterm)
      break;

  pRes = *ppCurr;
  if (pRes != NULL)
    {
      *ppCurr = pRes->next;
      pRes->next = NULL;
    }

  return pRes;
}

/* writes block of text in to the output buffer, enlarging it if needed.
 * it starts with block_start and end with block_end or '\0' 
 * if block_end is NULL 
 */
struct WriteBuffer
{
  char *buffer;
  size_t allocated, used;
};

void
WriteBlock (struct WriteBuffer *t_buffer, char *block_start, char *block_end)
{
  size_t bytes_to_add;

  if (t_buffer == NULL || block_start == NULL)
    return;
  bytes_to_add = (block_end == NULL) ? strlen (block_start) : block_end - block_start;
  if (bytes_to_add <= 0)
    return;
  /*make sure we always have 1 byte left at the end ( for trailing 0 ) */
  /*                     and 1 byte left for file terminator           */
  if (t_buffer->allocated < t_buffer->used + bytes_to_add + 2)
    {				/* growing buffer by 1/8 of it's size */
      char *tmp;
      size_t add_size = t_buffer->used + bytes_to_add + 2 - t_buffer->allocated;

      if (add_size < (t_buffer->allocated >> 3))
	add_size = (t_buffer->allocated >> 3);
      tmp = (char *) safemalloc (t_buffer->allocated + add_size);
      memcpy (tmp, t_buffer->buffer, t_buffer->used);
      free (t_buffer->buffer);
      t_buffer->buffer = tmp;
      t_buffer->allocated += add_size;
    }
  memcpy (t_buffer->buffer + t_buffer->used, block_start, bytes_to_add);
  t_buffer->used += bytes_to_add;
}

/* 
 * creates string representing freeStorage's elem and writes it into the 
 * output buffer
 */
void
WriteFreeStorageElem (ConfigDef * config, struct WriteBuffer *t_buffer, FreeStorageElem * pElem, int level)
{
  char *elem_buf;
  size_t elem_buf_size = level, params_size;
  int i;

  if (pElem == NULL)
    return;
  if (pElem->term == NULL)
    return;

  if (config->syntax->terminator != '\n')
    elem_buf_size = 0;

  if (pElem->flags & CF_DISABLED_OPTION)
    elem_buf_size += 1 + DISABLED_KEYWORD_SIZE;

  if (!(pElem->term->flags & TF_NO_MYNAME_PREPENDING) && !(pElem->flags & CF_PUBLIC_OPTION))
    elem_buf_size += 1 + strlen (config->myname);
  if (pElem->term->keyword)
    elem_buf_size += pElem->term->keyword_len;

  elem_buf_size++;		/* for separating space */
  params_size = GetStringArraySize (pElem->argc, pElem->argv);
  elem_buf_size += params_size;


  elem_buf_size++;		/* for sentense terminator */
  elem_buf_size++;		/* for terminating '\0'    */

  if (elem_buf_size > 0)
    {
      char *ptr;
      elem_buf = (char *) safemalloc (elem_buf_size);
      i = 0;
      if (config->syntax->terminator == '\n')
	for (; i < level; i++)
	  elem_buf[i] = '\t';

      ptr = &(elem_buf[i]);
      if (pElem->flags & CF_DISABLED_OPTION)
	{
	  strcpy (ptr, _disabled_keyword);
	  ptr += DISABLED_KEYWORD_SIZE;
	  *(ptr++) = ' ';
	}

      if (!(pElem->term->flags & TF_NO_MYNAME_PREPENDING) && !(pElem->flags & CF_PUBLIC_OPTION))
	{
	  *(ptr++) = MYNAME_CHAR;
	  strcpy (ptr, config->myname);
	  ptr += strlen (config->myname);
	}
      if (pElem->term->keyword)
	{
	  strcpy (ptr, pElem->term->keyword);
	  ptr += strlen (pElem->term->keyword);
	}
      /* some beautification is in order here */
      i = 0;
      if (params_size)
	{
	  *(ptr++) = '\t';
	  for (; i < pElem->argc; i++)
	    if (pElem->argv[i])
	      {
		if (i)
		  *(ptr++) = ' ';
		strcpy (ptr, pElem->argv[i]);
		ptr += strlen (pElem->argv[i]);
	      }
	}

      if (*ptr != config->syntax->terminator && (*ptr == '\0' && *(ptr - 1) != config->syntax->terminator))
	*(ptr++) = config->syntax->terminator;
      *ptr = '\0';
      WriteBlock (t_buffer, elem_buf, NULL);
      free (elem_buf);
    }
  if (pElem->sub)
    {
      FreeStorageElem *psub;
      for (psub = pElem->sub; psub; psub = psub->next)
	WriteFreeStorageElem (config, t_buffer, psub, level + 1);
    }
}

void
WriteRemnants (ConfigDef * config, struct WriteBuffer *t_buffer, FreeStorageElem * pCurr)
{
/* > this function is writing options that are acually new for this config */
/* > as the result we want to write them into the begining of the file */
/* > so lets save current buffer contents, write remnants and then write buffer back */
/* I've changed that back to have later options override previous options */
/*                                                      Sasha
 */
/*  struct WriteBuffer rem_buffer ;
 *  int remnants_num = CountFreeStorageElems( pCurr );
 *  
 *    if( remnants_num<=0 ) return ;
 *    rem_buffer.allocated = remnants_num*NORMAL_CONFIG_LINE+t_buffer->used+2 ; 
 *    rem_buffer.buffer = (char*)safemalloc( rem_buffer.allocated );
 *    rem_buffer.used = 0 ;
 */
  for (; pCurr != NULL; pCurr = pCurr->next)
    {
      if (pCurr->term == NULL)
	continue;
/*      WriteFreeStorageElem( config, &rem_buffer, pCurr );   
 */ WriteFreeStorageElem (config, t_buffer, pCurr, 0);
    }

/*    WriteBlock( &rem_buffer, t_buffer->buffer, t_buffer->buffer+t_buffer->used );
 *    free( t_buffer->buffer );
 *    t_buffer->allocated = rem_buffer.allocated ;
 *    t_buffer->used = rem_buffer.used ;
 *    t_buffer->buffer = rem_buffer.buffer ;
 */
}

void
ScanAndWriteExistant (ConfigDef * config, FreeStorageElem ** storage, struct WriteBuffer *t_buffer, unsigned long flags)
{
  char *noise_start = config->cursor;
  FreeStorageElem *pCurr;
  while (GetNextStatement (config, 0))
    {				/* untill not end of text */
      TermDef *pterm;
      if (!(flags & WF_DISCARD_COMMENTS))
	{
	  WriteBlock (t_buffer, noise_start, config->tline_start);
	  noise_start = config->cursor;
	}
      if (IsForeignOption (config))
	{
	  if (!(flags & WF_DISCARD_FOREIGN))
	    WriteBlock (t_buffer, config->tline_start, config->cursor);
	  continue;
	}
      if (IsPublicOption (config))
	{
	  if (!(flags & WF_DISCARD_PUBLIC))
	    WriteBlock (t_buffer, config->tline_start, config->cursor);
	  continue;
	}

      /* find term */
      if ((pterm = FindStatementTerm (config->tline, config->syntax)))
	{
	  pCurr = FindTermAndRemove (storage, pterm);
	  if (pCurr)
	    WriteFreeStorageElem (config, t_buffer, pCurr, 0);
	}
      if ((pterm == NULL || pCurr == NULL) && (!(flags & WF_DISCARD_UNKNOWN)))
	WriteBlock (t_buffer, config->tline_start, config->cursor);

      DestroyFreeStorage (&pCurr);	/* no longer need it */
    }

  if (!(flags & WF_DISCARD_COMMENTS))
    WriteBlock (t_buffer, noise_start, config->cursor);

}

#endif
/***************************************************************************/
/* main writing procedure ( returns size of the data written )             */

long
WriteConfig (ConfigDef * config, FreeStorageElem ** storage, ConfigDataType target_type, void **target, unsigned long flags)
{
#ifdef WITH_CONFIG_WRITER
  struct WriteBuffer t_buffer;
  int t_fd = -1;

  if (config == NULL || storage == NULL || target == NULL)
    return -1;
  if (*storage == NULL)
    return -1;

  if (config->buffer_size <= 0)
    {
      t_buffer.allocated = MAXLINELENGTH;
      flags = WF_DISCARD_EVERYTHING;
    }
  else
    t_buffer.allocated = config->buffer_size;

  t_buffer.buffer = (char *) safemalloc (t_buffer.allocated);
  t_buffer.used = 0;
  /* scanning old file first to preserv comments */
  if (!(flags & WF_DISCARD_COMMENTS) ||
      !(flags & WF_DISCARD_UNKNOWN) ||
      !(flags & WF_DISCARD_FOREIGN) ||
      !(flags & WF_DISCARD_PUBLIC))
    ScanAndWriteExistant (config, storage, &t_buffer, flags);
  /* now writing remaining elements */
  WriteRemnants (config, &t_buffer, *storage);
  DestroyFreeStorage (storage);

  t_buffer.buffer[t_buffer.used] = config->syntax->file_terminator;
  t_buffer.buffer[t_buffer.used + 1] = '\0';

  if (config->syntax->file_terminator != '\0')
    t_buffer.used++;

  /* now saving buffer into file if we need to */
  switch (target_type)
    {
    case CDT_Filename:
      t_fd = open ((char *) (*target), O_WRONLY | O_CREAT | O_TRUNC,
		   S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
      break;
    case CDT_FilePtr:
      t_fd = fileno ((FILE *) (*target));
      break;
    case CDT_FileDesc:
      t_fd = *((int *) (*target));
      break;
    case CDT_Data:
      (*target) = (void *) (t_buffer.buffer);
      return t_buffer.used;
    }
  if (t_fd != -1)
    {
      write (t_fd, t_buffer.buffer, t_buffer.used);
      if (target_type == CDT_Filename)
	close (t_fd);
    }

  free (t_buffer.buffer);

  return t_buffer.used;
#else
  return 0;
#endif
}

/* helper functions for writing config */

char *
MyIntToString (int value)
{
  int charnum, test = value;
  char *string;
  for (charnum = (test < 0) ? 1 : 0; test; charnum++)
    test /= 10;
  if (charnum == 0)
    charnum++;
  charnum++;
  string = (char *) safemalloc (charnum);
  sprintf (string, "%d", value);
  return string;
}

FreeStorageElem **
Integer2FreeStorage (SyntaxDef * syntax, FreeStorageElem ** tail, int value, int id)
{
  FreeStorageElem *new_elem = AddFreeStorageElem (syntax, tail, NULL, id);
  if (new_elem)
    {
      new_elem->argc = 1;
      new_elem->argv = (char **) safemalloc (sizeof (char *));
      new_elem->argv[0] = MyIntToString (value);
      tail = &(new_elem->next);
    }
  return tail;
}

FreeStorageElem **
String2FreeStorage (SyntaxDef * syntax, FreeStorageElem ** tail, char *string, int id)
{
  FreeStorageElem *new_elem;

  if (string)
    if (strlen (string))
      if ((new_elem = AddFreeStorageElem (syntax, tail, NULL, id)) != NULL)
	{
	  new_elem->argc = 1;
	  new_elem->argv = (char **) safemalloc (sizeof (char *));
	  new_elem->argv[0] = (char *) safemalloc (strlen (string) + 1);
	  strcpy (new_elem->argv[0], string);
	  tail = &(new_elem->next);
	}
  return tail;
}

FreeStorageElem **
Geometry2FreeStorage (SyntaxDef * syntax, FreeStorageElem ** tail, MyGeometry * geometry, int id)
{
  char geom_string[MAXLINELENGTH] = "";

  if (geometry)
    {
      if (geometry->flags & WidthValue)
	sprintf (geom_string, "=%d", geometry->width);
      if (geometry->flags & HeightValue)
	sprintf (geom_string + strlen (geom_string), "x%d", geometry->height);
      if (geometry->flags & XValue)
	sprintf (geom_string + strlen (geom_string), "%+d", geometry->x);
      if (geometry->flags & YValue)
	sprintf (geom_string + strlen (geom_string), "%+d", geometry->y);
      tail = String2FreeStorage (syntax, tail, geom_string, id);
    }
  return tail;
}

FreeStorageElem **
StringArray2FreeStorage (SyntaxDef * syntax, FreeStorageElem ** tail, char **strings, int index1, int index2, int id)
{
  FreeStorageElem *new_elem = NULL;
  int i;
  char ind_str[MAXLINELENGTH];
  TermDef *pterm = FindTerm (syntax, TT_ANY, id);

  if (strings && pterm)
    for (i = 0; i < index2 - index1 + 1; i++)
      {
	if (strings[i] == NULL)
	  continue;
	if ((new_elem = AddFreeStorageElem (syntax, tail, pterm, id)) == NULL)
	  continue;
	new_elem->argc = 2;
	new_elem->argv = CreateStringArray (2);
	sprintf (ind_str, "%d", i + index1);
	new_elem->argv[0] = (char *) safemalloc (strlen (ind_str) + 1 + strlen (strings[i]) + 1);
	strcpy (new_elem->argv[0], ind_str);
	new_elem->argv[1] = new_elem->argv[0] + strlen (ind_str) + 1;
	strcpy (new_elem->argv[1], strings[i]);
	tail = &(new_elem->next);
      }
  return tail;
}
