/*
Copyright (c) 2000-2010, Dirk Krause
All rights reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.
* Redistributions in binary form must reproduce the above 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/



/**	@file	stc.c	The stc program.
*/



#include <stdio.h>
#include "dk.h"
#include "dksf.h"
#include "dkmem.h"
#include "dkenc.h"
#include "dksto.h"
#include "dkstr.h"
#include "dkapp.h"
#include "dkstream.h"
#include "dklogc.h"
#include "dkerror.h"

#if DK_HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if DK_HAVE_UNISTD_H
#include <unistd.h>
#endif
#if DK_HAVE_CTYPE_H
#include <ctype.h>
#endif

#include "dklic.h"

#include "dktools-version.h"




#line 73 "stc.ctr"




#ifndef INBUFFERSIZE
/**	Size of input buffer.
*/
#define INBUFFERSIZE 16384
#endif



/**	Separator for parts of a path name.
*/
#if DK_HAVE_FEATURE_BACKSLASH
static char fn_sep[] = { "\\" };
#else
static char fn_sep[] = { "/" };
#endif



/**	System configuration directory.
*/
static char sysconfdir[] = { DK_SYSCONFDIR };



/**	Program group name.
*/
static char packagename[] = { "dktools" };



/**	Error messages (filled using string finder).
*/
static char *error_messages[24];


/**	String finder entries.
*/
static dk_string_finder_t error_finder[] = {
  {
    (char *)"/e/00",
    &(error_messages[0]),  
    (char *)"No entry for key\"" 
  },
  {
    (char *)"/e/01",
    &(error_messages[1]),  
    (char *)"\" in language \"" 
  },
  {
    (char *)"/e/02",
    &(error_messages[2]),  
    (char *)"\"!" 
  },
  {
    (char *)"/e/03",
    &(error_messages[3]),  
    (char *)"Not enough memory!" 
  },
  {
    (char *)"/e/04",
    &(error_messages[4]),  
    (char *)"Empty language spec!" 
  },
  {
    (char *)"/e/05",
    &(error_messages[5]),  
    (char *)"" 
  },
  {
    (char *)"/e/06",
    &(error_messages[6]),  
    (char *)"No value!" 
  },
  {
    (char *)"/e/07",
    &(error_messages[7]),  
    (char *)"" 
  },
  {
    (char *)"/e/08",
    &(error_messages[8]),  
    (char *)"Missing \"=\"!" 
  },
  {
    (char *)"/e/09",
    &(error_messages[9]),  
    (char *)"" 
  },
  {
    (char *)"/e/10",
    &(error_messages[10]), 
    (char *)"Failed to write to file \"" 
  },
  {
    (char *)"/e/11",
    &(error_messages[11]), 
    (char *)"\"!" 
  },
  {
    (char *)"/e/12",
    &(error_messages[12]), 
    (char *)"Failed to create directory \"" 
  },
  {
    (char *)"/e/13",
    &(error_messages[13]), 
    (char *)"\"!" 
  },
  {
    (char *)"/e/14",
    &(error_messages[14]), 
    (char *)"Unfinished entry name." 
  },
  {
    (char *)"/e/15",
    &(error_messages[15]), 
    (char *)"Entry value should be quoted." 
  },
  {
    (char *)"/e/16",
    &(error_messages[16]), 
    (char *)"Missing final quote sign." 
  },
  {
    (char *)"/m/cc",
    &(error_messages[17]), 
    (char *)"Current configuration:" 
  },
  {
    (char *)"/m/on",
    &(error_messages[18]), 
    (char *)"on" 
  },
  {
    (char *)"/m/off",
    &(error_messages[19]), 
    (char *)"off" 
  },
  {
    (char *)"/m/plain",
    &(error_messages[20]), 
    (char *)"force plain output" 
  },
  {
    (char *)"/e/17",
    &(error_messages[21]), 
    (char *)"Language_Region.Encoding string too long!" 
  },
  {
    (char *)"/e/18",
    &(error_messages[22]), 
    (char *)"Failed to UTF-8 decode string!" 
  },
  {
    (char *)"/e/19",
    &(error_messages[23]), 
    (char *)"UTF-8 encoded string contains characters above 0xFF!" 
  },
  { NULL, NULL, NULL}
};



/**	Preference key: Force plain output.
*/
static char str_pref_plain[] = { "/plain" };



/**	Boolean value: on.
*/
static char str_pref_on[] = { "on" };

/**	Boolean value: off.
*/
static char str_pref_off[] = { "off" };



/**	Preference key to retrieve languages for automatic completion.
*/
static char str_pref_auto_lang[] = { "/languages/mostly-ascii7" };



/**	File open mode.
*/
static char str_r[] = { "r" };



/**	Exit status code.
*/
static int exval = 0;



/**	Application.
*/
dk_app_t *app = NULL;


/**	Input buffer.
*/
static char inbuffer[INBUFFERSIZE];



/**	Table entry for one language.
*/
typedef struct {
  char *lang;		/**< Language. */
  char *value;		/**< Entry value. */
  unsigned long lineno;	/**< Line number in source file. */
} entry_for_lang;



/**	Convert string to lower-case.
	@param	str	String to convert.
*/
static
void
string_lower DK_P1(char *,str)
{
  char *ptr;
  ptr = str;
  while(*ptr) {
    if(isupper(*ptr)) { *ptr = tolower(*ptr); }
    ptr++;
  }
}



/**	Release one entry for a language.
	@param	e	Entry to release.
*/
static
void
free_entry_for_lang DK_P1(entry_for_lang *,e)
{
  char *cptr;
  cptr = e->value;
  
  if(cptr) dk_delete(cptr) ;
  dk_delete(e);
}



/**	Entry for a key.
*/
typedef struct {
  char *key;			/**< Key (entry name). */
  dk_storage_t *e;		/**< Container of entry_for_lang entries. */
  dk_storage_iterator_t *ei;	/**< Iterator for \a e. */
  entry_for_lang *current;	/**< Current language-entry to process. */
  unsigned long lineno;		/**< Source file line number. */
} entry_for_key;



/**	Free entry for key.
	@param	e	Entry to release.
*/
static
void
free_entry_for_key DK_P1(entry_for_key *,e)
{
  char *cptr;
  entry_for_lang *el;
  cptr = e->key; 
  if(cptr) dk_delete(cptr) ;
  if((e->e) && (e->ei)) {
    dksto_it_reset(e->ei);
    while((el = (entry_for_lang *)dksto_it_next(e->ei)) != NULL) {
      free_entry_for_lang(el);
    }
  }
  if(e->e) {
    dksto_close(e->e);
  }
  e->key = NULL;
  e->e = NULL;
  e->ei = NULL;
  e->current = NULL;
  dk_delete(e) ;
}



/**	Compare two language names (language/region/encoding).
	@param	p1	Left language.
	@param	p2	Right language.
	@param	cr	Comparison criteria.
	@return	Comparison result.
*/
int
compare_languages DK_P3(void *,p1, void *,p2, int,cr)
{
  int back = 0;
  if(p1 && p2) {
    back = strcmp((char *)p1, (char *)p2);
    if(back < 0) back = -1;
    if(back > 0) back = 1;
  }
  return back;
}



/**	Compare entries by language.
	@param	p1	Left entry.
	@param	p2	Right entry.
	@param	cr	Comparison criteria (ignored).
	@return	Comparison result.
*/
int
compare_entries_for_lang DK_P3(void *,p1, void *,p2, int,cr)
{
  int back = 0;
  entry_for_lang *e1, *e2;
  if(p1 && p2) {
    switch(cr) {
      case 1: {
        e1 = (entry_for_lang *)p1;
	if(e1->lang) {
	  back = strcmp((e1->lang), (char *)p2);
	}
      } break;
      default: {
	e1 = (entry_for_lang *)p1; e2 = (entry_for_lang *)p2;
	if((e1->lang) && (e2->lang)) {
	  back = strcmp((e1->lang), (e2->lang));
	}
      } break;
    }
  }
  if(back > 0) back = 1;
  if(back < 0) back = -1;
  return back;
}



/**	Compare entries by key name.
	@param	p1	Left entry.
	@param	p2	Right entry.
	@param	cr	Comparison criteria (ignored).
	@return	Comparison result.
*/
int
compare_entries_for_key DK_P3(void *,p1, void *,p2, int,cr)
{
  int back = 0;
  entry_for_key *e1, *e2;
  if(p1 && p2) {
    switch(cr) {
      case 1: {
	e1 = (entry_for_key *)p1;
	if(e1->key) {
	  back = strcmp((e1->key),(char *)p2);
	}
      } break;
      default: {
	e1 = (entry_for_key *)p1; e2 = (entry_for_key *)p2;
	if((e1->key) && (e2->key)) {
	  back = strcmp((e1->key),(e2->key));
	}
      } break;
    }
  }
  if(back > 0) back = 1;
  if(back < 0) back = -1;
  return back;
}



/**	Create new entry for a key.
	@param	name	Key name.
	@param	lineno	Line number in source file.
	@return	Entry pointer on success, NULL on error.
*/
static
entry_for_key *
new_entry_for_key DK_P2(char *,name, unsigned long,lineno)
{
  entry_for_key *back = NULL;
  back = dk_new(entry_for_key,1);
  if(back) {
    back->key = dkstr_dup(name);
    back->e = dksto_open(0);
    back->ei = NULL;
    back->current = NULL;
    back->lineno = lineno;
    if(back->e) {
      back->ei = dksto_it_open(back->e);
      if(back->ei) {
	dksto_set_comp(back->e, compare_entries_for_lang, 0);
      }
    }
    back->current = NULL;
    if(!((back->key) && (back->e) && (back->ei))) {
      free_entry_for_key(back);
      back = NULL;
    }
  }
  return back;
}



/**	Stc job.
*/
typedef struct {
  char *filename;		/**< Source file name. */
  char *dirname;		/**< Output directory name. */
  dk_storage_t *l;		/**< Languages container. */
  dk_storage_iterator_t *li;	/**< Languages iterator. */
  dk_storage_t *e;		/**< Entries sorted by key container. */
  dk_storage_iterator_t *ei;	/**< Entries sorted by key iterator. */
  FILE *inputfile;		/**< Input file. */
  dk_uword	vers_major;	/**< Major version number of input file. */
  dk_uword	vers_minor;	/**< Minor version number of input file. */
  dk_uword	number;		/**< ?. */
  entry_for_key *cefk;		/**< Current entry for key to process. */
  size_t        output_filename_length;	/**< Output file name length. */
  size_t        output_language_length;	/**< Output language string length. */
  char         *output_filename_buffer;	/**< Output file name buffer. */
  char         *language_buffer;	/**< Language name buffer. */
  char	       *current_language;	/**< Current language processed. */
  char         *language_test;		/**< Language for test. */
  char         *language_comp;		/**< Language for comparison. */
  int           force_plain;		/**< Flag: Force plain output. */
  char	       *language_auto;		/**< Language for automatic compl. */
  char	       *language_aut2;		/**< Language for autoamtic compl 2. */
} StcCommand;



/**	Clean up stc job.
	@param	c	Stc job.
*/
static
void
cleanup DK_P1(StcCommand *,c)
{
  entry_for_key *efk;
  char *cptr;
  if(c->language_auto) {
    dk_delete(c->language_auto);
  }
  if(c->language_aut2) {
    dk_delete(c->language_aut2);
  }
  c->language_auto = c->language_aut2 = NULL;
  if(c->e) {
    if(c->ei) {
      dksto_it_reset(c->ei);
      while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) {
	free_entry_for_key(efk);
      }
    }
    dksto_close(c->e);
  }
  if(c->l) {
    if(c->li) {
      dksto_it_reset(c->li);
      while((cptr = (char *)dksto_it_next(c->li)) != NULL) {
	
	dk_delete(cptr);
      }
    }
    dksto_close(c->l);
  }
}



/**	Languages we can attempt to auto complete.
	I.e. create la/re/UTF-8 entries from la/re
	or la/re entries from la/re/UTF-8.
	Only possible for languages with all characters
	in character set 0x00 ... 0xFF.
*/
static char auto_complete_languages[] = {
  "de en fr nl be sp pt pl cs hu sv no da"
};



/**	Initialize stc job.
	@param	c	Stc job.
	@return	1 on success, 0 on error.
*/
static
int
initialize DK_P1(StcCommand *,c)
{
  int back = 0;
  char auto_lang_buffer[4096];
  c->l = dksto_open(0);
  if(c->l) {
    c->li = dksto_it_open(c->l);
    dksto_set_comp(c->l, compare_languages, 0);
  }
  c->e = dksto_open(0);
  if(c->e) {
    c->ei = dksto_it_open(c->e);
    dksto_set_comp(c->e, compare_entries_for_key, 0);
  }
  if(dkapp_get_pref(app, str_pref_auto_lang, auto_lang_buffer, sizeof(auto_lang_buffer), 0)) {
    c->language_auto = dkstr_dup(auto_lang_buffer);
    c->language_aut2 = dkstr_dup(auto_lang_buffer);
  } else {
    c->language_auto = dkstr_dup(auto_complete_languages);
    c->language_aut2 = dkstr_dup(auto_complete_languages);
  }
  if((c->l) && (c->li) && (c->e) && (c->ei)) {
    if((c->language_auto) && (c->language_aut2)) {
      back = 1;
      c->vers_major = c->vers_minor = 1;
      
    }
  }
  if(!back) {
    /* ERROR: Not enough memory */
    dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1);
  }
  return back;
}



/**	Copy contents of string one place to the left.
	@param	str	String to modify.
*/
static
void
copy_to_left DK_P1(char *,str)
{
  char *xptr, *yptr;
  xptr = str;
  if(*xptr) {
    yptr = xptr;
    yptr++;
    while(*yptr) {
      *xptr = *yptr;
      xptr++; yptr++;
    }
    *xptr = '\0';
  }
}



/**	Squeeze a string.
	@param	str	String to squeeze.
*/
static
void
squeeze_string DK_P1(char *,str)
{
  char *ptr;
  ptr = str;
  while(*ptr) {
    if(*ptr == '\\') {
      switch(ptr[1]) {
	case '\0' : {
	  *ptr = '\0';
	} break;
	case 'n' : {
	  *ptr = '\n'; ptr++; copy_to_left(ptr);
	} break;
	case 't' : {
	  *ptr = '\t'; ptr++; copy_to_left(ptr);
	} break;
	case 'b' : {
	  *ptr = '\b'; ptr++; copy_to_left(ptr);
	} break;
	case 'r' : {
	  *ptr = '\r'; ptr++; copy_to_left(ptr);
	} break;
	default : {
	  *ptr = ptr[1]; ptr++; copy_to_left(ptr);
	} break;
      }
    } else {
      ptr++;
    }
  }
}




/**	String: opening bracket.
*/
static char str_bropen[] = { "(" };

/**	String: Closing bracket.
*/
static char str_brclose[] = { ")" };



/**	String: space.
*/
static char str_space[] = { " " };



/**	Set of separators.
*/
static char str_separators[] = { " \t.," };



/**	Read input file.
	@param	c	Stc job.
	@return	1 on success, 0 on error.
*/
static
int
read_input DK_P1(StcCommand *,c)
{
  int back = 0;
  int can_continue;
  char *cptr, *linestart, *valstart;
  entry_for_key *efk;
  entry_for_lang *efl;
  unsigned long lineno;
  char *errmsgs[5];
  if(app) {
    c->inputfile = dkapp_fopen(app, c->filename, str_r);
  } else {
    c->inputfile = dksf_fopen(c->filename, str_r);
  }
  if(c->inputfile) {
    lineno = 0UL;
    back = 1; can_continue = 1;
    c->cefk = NULL;
    dkapp_set_source_filename(app, c->filename);
    while(can_continue && back) {
      if(fgets(inbuffer,sizeof(inbuffer),(c->inputfile))) {
	lineno++;
	dkapp_set_source_lineno(app, lineno);
	cptr = dkstr_chr(inbuffer, '#');
	if(cptr) *cptr = '\0';
	linestart = dkstr_start(inbuffer, NULL);
	if(linestart) {
	  switch(*linestart) {
	    case '$' : {	/* special instruction */
	      linestart++;
	      linestart = dkstr_start(linestart, NULL);
	      if(linestart) {
		cptr = dkstr_next(linestart, NULL);
		if(strcmp(linestart, "version") == 0) {
		  linestart = dkstr_next(cptr, str_separators);
		  if(linestart) {
		    unsigned u1;
		    if(sscanf(cptr, "%u", &u1) == 1) {
		      c->vers_major = u1;
		    }
		    if(sscanf(linestart, "%u", &u1) == 1) {
		      c->vers_minor = u1;
		    }
		  }
		}
	      }
	    } break;
	    case '"' : {	/* new key */
	      dkstr_chomp(linestart, NULL);
	      linestart++;
	      cptr = dkstr_rchr(linestart, '"');
	      if(cptr) {
	        *cptr = '\0';
	      } else {
	        errmsgs[0] = error_messages[14];
		errmsgs[1] = str_space;
		errmsgs[2] = str_bropen;
		errmsgs[3] = dkstr_start(inbuffer, NULL);
		if(!(errmsgs[3])) { errmsgs[3] = str_space; }
		errmsgs[4] = str_brclose;
		dkapp_log_msg(app,DK_LOG_LEVEL_WARNING,errmsgs,5);
	      }
	      squeeze_string(linestart);
	      efk = (entry_for_key *)dksto_it_find_like(c->ei, linestart, 1);
	      if(!efk) {
	        efk = new_entry_for_key(linestart, lineno); 
		if(efk) {
		  efk->lineno = lineno;
		  if(dksto_add((c->e),(void *)efk)) {
		    c->cefk = efk;
		  } else {
		    /* ERROR NOT ENOUGH MEMORY */
		    errmsgs[0] = error_messages[3];
		    dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
		    back = 0;
		  }
	        } else {
		  /* ERROR NOT ENOUGH MEMORY */
		  errmsgs[0] = error_messages[3];
		  dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
		  back = 0;
	        }
	      }
	    } break;
	    default : {		/* value */
	      valstart = dkstr_chr(linestart, '=');
	      if(valstart) {
		*(valstart++) = '\0';
		valstart = dkstr_start(valstart, NULL);
		if(valstart) {
		  linestart = dkstr_start(linestart, NULL);
		  if(linestart) {
		    dkstr_chomp(linestart, NULL);
		    dkstr_chomp(valstart, NULL);
		    if(*valstart == '"') {
		      valstart++;
		      cptr = dkstr_rchr(valstart, '"');
		      if(cptr) { *cptr = '\0'; }
		      else {
		        errmsgs[0] = error_messages[16];
			dkapp_log_msg(app,DK_LOG_LEVEL_WARNING,errmsgs,1);
		      }
		    } else {
		      errmsgs[0] = error_messages[15];
		      dkapp_log_msg(app,DK_LOG_LEVEL_WARNING,errmsgs,1);
		    }
		    string_lower(linestart);
		    cptr = (char *)dksto_it_find_like(c->li, linestart, 1);
		    if(!cptr) {
		      cptr = dkstr_dup(linestart);
		      if(cptr) {
			if(!(dksto_add(c->l, (void *)cptr))) {
			  /* ERROR NOT ENOUGH MEMORY */
			  errmsgs[0] = error_messages[3];
			  dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
			  back = 0;
			}
		      } else {
			/* ERROR NOT ENOUGH MEMORY */
			errmsgs[0] = error_messages[3];
			dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
			back = 0;
		      }
		    }
		    if(back) {
		      squeeze_string(valstart);
                      efl = dk_new(entry_for_lang,1);
		      if(efl) {
			efl->lang = cptr;
			efl->value = dkstr_dup(valstart);
			efl->lineno = lineno;
			if(efl->value) {
			  if(!(dksto_add((c->cefk)->e, (void *)efl))) {
			    /* ERROR NOT ENOUGH MEMORY */
			    errmsgs[0] = error_messages[3];
			    dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
			    back = 0;
			    dk_delete(efl->value);
			    efl->value = NULL; efl->lang = NULL;
			    dk_delete(efl); efl = NULL;
			  }
			} else {
			  /* ERROR NOT ENOUGH MEMORY */
			  errmsgs[0] = error_messages[3];
			  dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
			  back = 0;
			  efl->lang = NULL; dk_delete(efl); efl = NULL;
			}
		      } else {
			/* ERROR NOT ENOUGH MEMORY */
			errmsgs[0] = error_messages[3];
			dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
			back = 0;
		      }
		    }
		  } else {
		    /* ERROR EMPTY KEY */
		    errmsgs[0] = error_messages[4];
		    dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
		    back = 0;
		  }
		} else {
		  /* ERROR NO VALUE IN LINE */
		  errmsgs[0] = error_messages[6];
		  dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
		  back = 0;
		}
	      } else {
		/* ERROR WRONG INPUT LINE, MISSING = */
		errmsgs[0] = error_messages[8];
		dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
		back = 0;
	      }
	    } break;
	  }
	}
      } else {
	can_continue = 0;
      }
    }
    dkapp_set_source_lineno(app, 0UL);
    dkapp_set_source_filename(app, NULL);
    fclose(c->inputfile);
  } else {
    dkapp_err_fopenr(app, c->filename);
  }
  return back;
}



/**	Find needed buffer length (length of larget entry).
	@param	c	Stc job.
	@return	Needed buffer length.
*/
static
size_t
find_buffer_length DK_P1(StcCommand *,c)
{
  size_t back = 0;
  size_t lgt;
  char *x;
  dksto_it_reset(c->li);
  while((x = (char *)dksto_it_next(c->li)) != NULL) {
    lgt = strlen(x);
    if(lgt > back) {
      back = lgt;
    }
  }
  c->output_language_length = (back + 1);
  x = dkstr_rchr(c->filename, fn_sep[0]);
  if(!x) x = c->filename;
  else x++;
  back += strlen(x) + 4;
  back += strlen(c->dirname) + 1;
  return back;
}



/**	Check whether directory \a s exists, create if necessary.
	@param	s	Directory name.
	@return	1 on success (directory can be used), 0 on error.
*/
static
int
check_directory DK_P1(char *,s)
{
  int back = 1;
  dk_stat_t *st;
  char *errmsgs[5];
  st = dkstat_open(s);
  if(st) { 
    if(((st->filetype) & (~(DK_FT_SYMLINK))) != DK_FT_DIR) {
      
      /* ERROR WRONG FILETYPE */
      back = 0;
    }
  } else { 
    if(!dksf_mkdir(s, 0755)) { 
      /* ERROR MKDIR FAILED */
      back = 0;
    }
  }
  if(!back) {
    errmsgs[0] = error_messages[12];
    errmsgs[1] = s;
    errmsgs[2] = error_messages[13];
    dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,3);
  }
  
  return back;
}



/**	Create directories for language/name/encoding.
	@param	c	Stc job.
	@return	1 on success, 0 on error.
*/
static
int
create_directories DK_P1(StcCommand *,c)
{
  int back = 1;
  char *buffer, *lbuffer;
  char *x;
  char *lang, *reg, *enc;
  
  if(!check_directory(c->dirname)) {
    back = 0;
  }
  buffer = c->output_filename_buffer;
  lbuffer = c->language_buffer;
  if(back) {
  dksto_it_reset(c->li);
  while((x = (char *)dksto_it_next(c->li)) != NULL) {
    strcpy(lbuffer,x);
    lang = lbuffer;
    enc  = dkstr_chr(lang, '.');
    if(enc) {
      *(enc++) = '\0';
    }
    reg  = dkstr_chr(lang, '_');
    if(reg) {
      *(reg++) = '\0';
    }
    if(lang) {
      strcpy(buffer, c->dirname);
      strcat(buffer, fn_sep);
      strcat(buffer, lang);
      if(!check_directory(buffer)) { back = 0; }
      if(reg) {
	strcpy(buffer, c->dirname);
	strcat(buffer, fn_sep);
	strcat(buffer, lang);
	strcat(buffer, fn_sep);
	strcat(buffer, reg);
	if(!check_directory(buffer)) { back = 0; }
	if(enc) {
	  strcpy(buffer, c->dirname);
	  strcat(buffer, fn_sep);
	  strcat(buffer, lang);
	  strcat(buffer, fn_sep);
	  strcat(buffer, reg);
	  strcat(buffer, fn_sep);
	  strcat(buffer, enc);
	  if(!check_directory(buffer)) { back = 0; }
	}
      } else {
	if(enc) {
	  strcpy(buffer, c->dirname);
	  strcat(buffer, fn_sep);
	  strcat(buffer, lang);
	  strcat(buffer, fn_sep);
	  strcat(buffer, enc);
	  if(!check_directory(buffer)) { back = 0; }
	}
      }
    }
  }
  } 
  return back;
}



/**	Retrieve file name component from a full path name.
	@param	fullname	Full path name.
	@return	File name or NULL.
*/
static
char *
get_filename DK_P1(char *,fullname)
{
  char *back = NULL;
  back = dkstr_rchr(fullname, fn_sep[0]);
  if(back) back++;
  else back = fullname;
  return back;
}



/**	File header for the binary string tables.
*/
static char file_header[] = { "STRT-2" };



/** Output file name suffix.
*/
static char stt_suffix[] = { ".stt" };


/**	Empty string.
*/
static char empty_string[] = { "" };



/**	File open mode.
*/
static char str_wb[] = { "wb" };



/**	Write entries for the current language.
	@param	c	Stc job.
	@return	1 on success, 0 on error.
*/
static
int
write_language DK_P1(StcCommand *,c)
{
  int back = 1;
  char *filename, *lc, *lt, *currentlang, *lang, *reg, *enc;
  entry_for_key *efk;
  dk_stream_t *strm;
  dk_uword uw;
  char *errmsgs[5];
  /*
    Prepare the filename
  */
  strcpy(c->output_filename_buffer, c->dirname);
  strcat(c->output_filename_buffer, fn_sep);
  filename = c->output_filename_buffer;
  while(*filename) filename++;
  strcpy(filename, c->current_language);
  while(*filename) {
    if(*filename == '_') *filename = fn_sep[0];
    if(*filename == '.') *filename = fn_sep[0];
    filename++;
  }
  filename = get_filename(c->filename);
  strcat(c->output_filename_buffer, fn_sep);
  strcat(c->output_filename_buffer, filename);
  filename = dksf_get_file_type_dot(c->output_filename_buffer);
  if(filename) {
    strcpy(filename, stt_suffix);
  } else {
    strcat(c->output_filename_buffer, stt_suffix);
  }
  /*
    Set current pointer for all entries
  */
  dksto_it_reset(c->ei);
  lc = c->language_comp; lt = c->language_test;
  currentlang = c->current_language;
  strcpy(lc, currentlang);
  lang = lc;
  enc = dkstr_chr(lang, '.');
  if(enc) {
    *(enc++) = '\0';
  }
  reg = dkstr_chr(lang, '_');
  if(reg) {
    *(reg++) = '\0';
  }
  while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) {
    efk->current =
    (entry_for_lang *)dksto_it_find_like((efk->ei),currentlang,1);
    if(!(efk->current)) { /* lang/reg */
      if(lang && reg) {
	strcpy(lt, lang);
	strcat(lt, "_");
	strcat(lt, reg);
	efk->current = (entry_for_lang *)dksto_it_find_like((efk->ei),lt,1);
      }
    }
    if(!(efk->current)) { /* lang/enc */
      if(lang && enc) {
	strcpy(lt, lang);
	strcat(lt, ".");
	strcat(lt, enc);
	efk->current = (entry_for_lang *)dksto_it_find_like((efk->ei),lt,1);
      }
    }
    if(!(efk->current)) { /* lang */
      if(lang) {
	strcpy(lt, lang);
	efk->current = (entry_for_lang *)dksto_it_find_like((efk->ei),lt,1);
      }
    }
    if(!(efk->current)) { /* any */
      dksto_it_reset(efk->ei);
      efk->current = (entry_for_lang *)dksto_it_next(efk->ei);
      /* WARNING NO MATCHING ENTRY FOUND */
      errmsgs[0] = error_messages[0];
      errmsgs[1] = efk->key;
      errmsgs[2] = error_messages[1];
      errmsgs[3] = c->current_language;
      errmsgs[4] = error_messages[2];
      dkapp_set_source_lineno(app, efk->lineno);
      dkapp_set_source_filename(app, c->filename);
      dkapp_log_msg(app,DK_LOG_LEVEL_WARNING,errmsgs,5);
      dkapp_set_source_lineno(app, 0UL);
      dkapp_set_source_filename(app, NULL);
    }
  }
  /*
    Now write
  */
  if(c->force_plain) {
    strm = dkapp_stream_openfile(app, c->output_filename_buffer, str_wb);
  } else {
    int reason;
    reason = 0;
    strm = dkapp_write_file(app, c->output_filename_buffer);
  }
  if(strm) {
    dkstream_write(strm, file_header, sizeof(file_header));
    uw = c->vers_major;
    dkstream_wb_uword(strm, uw);
    uw = c->vers_minor;
    dkstream_wb_uword(strm, uw);
    uw = c->number;
    dkstream_wb_uword(strm, uw);
    uw = 0;
    dkstream_wb_uword(strm, uw);
    dksto_it_reset(c->ei);
    while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) {
      dkstream_wb_string(strm, efk->key);
    }
    dksto_it_reset(c->ei);
    while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) {
      filename = empty_string;
      if(efk->current) {
	if((efk->current)->value) {
	  filename = (efk->current)->value;
	}
      } 
      dkstream_wb_string(strm, filename);
    }
    dkstream_close(strm);
  } else {
    /* ERROR FAILED TO WRITE TO FILE */
    errmsgs[0] = error_messages[10];
    errmsgs[1] = c->output_filename_buffer;
    errmsgs[2] = error_messages[11];
    dkapp_log_msg(app, DK_LOG_LEVEL_ERROR,errmsgs,3);
    back = 0;
  }
  return back;
}



/**	Write entries for all languages to output.
	@param	c	Stc job.
	@return	1 on success, 0 on errors.
*/
static
int
write_all_languages DK_P1(StcCommand *,c)
{
  int back = 1;
  dk_uword uw;
  char *efl;
  uw = 0;
  dksto_it_reset(c->ei);
  while(dksto_it_next(c->ei)) {
    uw++;
  }
  c->number = uw;
  dksto_it_reset(c->li);
  while((efl = (char *)dksto_it_next(c->li)) != NULL) {
    c->current_language = efl;
    if(!write_language(c)) {
      back = 0;
    }
  }
  return back;
}



/**	Write output file.
	@param	c	Stc job.
	@return	1 on success, 0 on error.
*/
static
int
write_output DK_P1(StcCommand *,c)
{
  int back = 0;
  size_t lgt;
  char *buffer, *langbuffer, *langtest, *langcomp, *errmsgs[5];
  lgt = c->output_filename_length = find_buffer_length(c);
  buffer = dk_new(char,lgt);
  if(buffer) {
    c->output_filename_buffer = buffer;
    lgt = c->output_language_length;
    langbuffer = dk_new(char,lgt);
    langtest   = dk_new(char,lgt);
    langcomp   = dk_new(char,lgt);
    if(langbuffer && langtest && langcomp) {
      c->language_buffer = langbuffer;
      c->language_test = langtest;
      c->language_comp = langcomp;
      if(create_directories(c)) {
	back = write_all_languages(c);
      }
      c->language_buffer = NULL;
      c->language_test   = NULL;
      c->language_comp   = NULL;
    } else {
      errmsgs[0] = error_messages[3];
      dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
    }
    if(langbuffer) dk_delete(langbuffer); 
    if(langtest)   dk_delete(langtest);
    if(langcomp)   dk_delete(langcomp);
    c->output_filename_buffer = NULL;
    dk_delete(buffer);
  } else {
    errmsgs[0] = error_messages[3];
    dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1);
  }
  return back;
}



/**	Encoding suffix for UTF-8.
*/
static char str_utf8[] = { ".utf-8" };



/**	Check whether a language/region/encoding needs handling
	(data is incomplete).
	@param	c	Stc command.
	@param	n	Language/region/encoding.
	@return	1=yes, 0=no.
*/
static
int
language_needs_handling DK_P2(StcCommand *,c, char *,n)
{
  int back = 0;
  char buffer[32]; /* 12 should be sufficient */
  char *cptr, *cp1, *cp2;
  
  if(n) {
    if(strlen(n) < sizeof(buffer)) {
      strcpy(buffer, n);
      cptr = dkstr_chr(buffer, '_');
      if(cptr) { *cptr = '\0'; }
      cptr = dkstr_chr(buffer, '.');
      if(cptr) { *cptr = '\0'; }
      
      strcpy(c->language_aut2, c->language_auto);
      
      cp1 = dkstr_start(c->language_aut2, NULL);
      while((!back) && (cp1)) {
        cp2 = dkstr_next(cp1, NULL);
	
        if(dkstr_casecmp(cp1, buffer) == 0) {
	  back = 1;	
	}
        if(!back) { cp1 = cp2; }
      }
    } else {
      
      /* ERROR: language/region/encoding string too long */
      dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[21]), 1);
    }
  } 
  return back;
}



/**	Create counterpart name for language/region/encoding.
	Examples: de_DE.UTF-8 -> de_DE, de_DE -> de_DE.UTF-8.
	@param	l	Original language/region/encoding.
	@param	b	Result buffer.
	@param	sz	Size of \a b (bytes).
	@param	enc	Current encoding.
	@return	1 on success, 0 on error.
*/
static
int
create_counterpart_name DK_P4(char *,l, char *,b, size_t, sz, int *,enc)
{
  int back = 0;
  size_t sz_appendix, sz_buffer;
  
  if(strlen(l) < sz) {
    strcpy(b, l);
    back = 1;
    sz_appendix = strlen(str_utf8);
    sz_buffer = strlen(b);
    *enc = 0;
    if(sz_buffer > sz_appendix) {
      if(dkstr_casecmp(&(b[sz_buffer - sz_appendix]),str_utf8) == 0) {
        *enc = 1;
      }
    }
    if(*enc) {
      b[sz_buffer - sz_appendix] = '\0';
    } else {
      if((sz_buffer + sz_appendix) < sz) {
        strcat(b, str_utf8);
      } else {
        back = 0;
	/* ERROR: language/encoding/region string too long */
	
        dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[21]), 1);
      }
    }
  } 
  return back;
}



/**	Add counter part in other encoding.
	@param	c	Stc job.
	@param	efk	Entry for key.
	@param	el	Entry for language
	@param	nc	Counterpart language/region/encoding name.
	@param	se	Source encoding.
	@return	1 on success, 0 on error.
*/
static
int
add_counterpart DK_P5(StcCommand *,c, entry_for_key *,efk, entry_for_lang *,el, char *,nc, int,se)
{
  int back = 1;			/* function result */
  char *newname;		/* new name, new string */
  char *languagename;		/* the language processed */
  int ec;			/* error code from conversion */
  
  /* add counterpart language to list of languages if necessary */
  if((languagename = (char *)dksto_it_find_like(c->li, nc, 1)) == NULL) {
    
    newname = dkstr_dup(nc);
    if(newname) {
      if(!dksto_add(c->l, (void *)newname)) {
        back = 0;
	/* ERROR: Not enough memory */
	dk_delete(newname);
        dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1);
	
      } else {
        
	languagename = newname;
      }
    } else {
      back = 0;
      /* ERROR: Not enough memory */
      
      dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1);
    }
  }
  /* create and add new entry */
  if((back) && (languagename)) {
    newname = NULL;
    ec = 0;
    if(se) {
      newname = dkenc_str_utf82bits8(el->value, &ec);
    } else {
      newname = dkenc_str_bits82utf8(el->value);
      if(!newname) { ec = DK_ERR_NOMEM; }
    }
    if(newname) {
      entry_for_lang *newefl;
      if(ec == DK_ERR_MATH_OOR) {
        dkapp_log_msg(app, DK_LOG_LEVEL_WARNING, &(error_messages[23]), 1);
      }
      newefl = dk_new(entry_for_lang,1);
      if(newefl) {
        newefl->lang = languagename;
	newefl->value = newname;
	newefl->lineno = el->lineno;
	if(!dksto_add(efk->e, (void *)newefl)) {
	   newefl->lang = NULL; newefl->value = NULL;
	   dk_delete(newname);
	   dk_delete(newefl);
	   newname = NULL; newefl = NULL;
	}
      } else {
        back = 0;
	/* ERROR: Not enough memory */
	
	dk_delete(newname); newname = NULL;
        dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1);
      }
    } else {
      back = 0;
      
      switch(ec) {
        case DK_ERR_INVALID_ARGS: {
	  dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[22]), 1);
	} break;
	case DK_ERR_MATH_OOR: {
	  dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[23]), 1);
	} break;
	default: {
	  dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1);
	} break;
      }
    }
  }
  
  return back;
}



/**	Handle one language for an entry.
	@param	c	Stc job.
	@param	efk	Entry for key.
	@param	el	Entry for language.
	@return	1 on success, 0 on error.
*/
static
int
handle_language_for_entry DK_P3(StcCommand *,c, entry_for_key *,efk, entry_for_lang *,el)
{
  int back = 1;
  char buffer[32];
  int src_encoding;
  
  src_encoding = 0;
  if(create_counterpart_name(el->lang, buffer, sizeof(buffer), &src_encoding)) {
    
    if(!dksto_it_find_like(efk->ei,buffer,1)) {
      
      if(!add_counterpart(c,efk,el,buffer,src_encoding)) {
        back = 0;
      }
    } else {
      
    }
  } else {
    back = 0;
    /* ERROR: language/encoding/region string too long */
    dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[21]), 1);
  }
  
  return back;
}



/**	Automatically complete an entry if necessary and possible.
	@param	c	Stc job.
	@param	efk	Entry for one key.
	@return	1 on success, 0 on error.
*/
static
int
auto_complete_entry DK_P2(StcCommand *,c, entry_for_key *,efk)
{
  int back = 0;
  dk_storage_t *e;		/* list of entries */
  dk_storage_iterator_t	*ei;	/* iterator for all entries */
  entry_for_lang *el;
  
  e = dksto_open(0); ei = NULL;
  if(e) {
    ei = dksto_it_open(e);
    if(ei) {
      back = 1;
      dksto_set_comp(e, compare_entries_for_lang, 0);
      dksto_it_reset(efk->ei);
      while((el = (entry_for_lang *)dksto_it_next(efk->ei)) != NULL) {
        
        if(!dksto_add(e, (void *)el)) {
	  back = 0;
	}
      }
      if(back) {
        dksto_it_reset(ei);
	while((el = (entry_for_lang *)dksto_it_next(ei)) != NULL) {
	  dkapp_set_source_lineno(app, el->lineno);
	  if(language_needs_handling(c, el->lang)) {
	    if(!handle_language_for_entry(c,efk,el)) {
	      back = 0;
	    }
	  }
	}
      } else {
        /* ERROR: Not enough memory to register language entry */
        dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1);
      }
      dksto_it_close(ei);
    } else {
      /* ERROR: Not enough memory for iterator */
      dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1);
    }
    dksto_close(e); e = NULL;
  } else {
    /* ERROR: Not enough memory for list */
    dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1);
  } 
  return back;
}



/**	Complete missing encodings if necessary and possible.
	@param	c	Stc job.
	@return	1 on success, 0 on error.
*/
static
int
auto_complete_encoding DK_P1(StcCommand *,c)
{
  int back = 0;
  entry_for_key *efk;
  if((c->e) && (c->ei)) {
    back = 1;
    dkapp_set_source_filename(app, c->filename);
    dksto_it_reset(c->ei);
    while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) {
      
      dkapp_set_source_lineno(app, efk->lineno);
      if(!auto_complete_entry(c, efk)) {	
        back = 0;
      }
    }
    dkapp_set_source_filename(app, NULL);
    dkapp_set_source_lineno(app, 0UL);
  } else {				
  }
  return back;
}



/**	Run normally.
	@param	filename	Input file name.
	@param	dirname		Output directory name.
	@param	force_plain	Flag: force plain output.
*/
static
void
run DK_P3(char *,filename, char *,dirname, int,force_plain) {
  StcCommand c;
  c.filename = filename;
  c.dirname = dirname;
  c.l = NULL; c.li = NULL; c.e = NULL; c.ei = NULL; c.inputfile = NULL;
  c.force_plain = force_plain;
  if(initialize(&c)) {
    if(read_input(&c)) { 
      if(auto_complete_encoding(&c)) {
        write_output(&c);
      } else {
        
      }
    }
  }
  cleanup(&c);
}



/**	Version number.
*/
static char the_version_number[] = { VERSNUMB } ;



/**	License terms.
*/
static char *license_terms[] = {
"Redistribution and use in source and binary forms, with or without",
"modification, are permitted provided that the following conditions are met:",
"* Redistributions of source code must retain the above copyright notice, this",
"  list of conditions and the following disclaimer.",
"* Redistributions in binary form must reproduce the above copyright notice,",
"  this list of conditions and the following disclaimer in the documentation",
"  and/or other materials provided with the distribution.",
"* Neither the name of the Dirk Krause nor the names of other contributors may",
"  be used to endorse or promote products derived from this software without",
"  specific prior written permission.",
"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"",
"AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE",
"IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE",
"ARE DISCLAIMED.",
"IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY",
"DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES",
"(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;",
"LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND",
"ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT",
"(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS",
"SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
NULL
};



/**	Print version number and license information.
*/
static
void
print_version DK_P0() {
  char **ptr;
  printf("\n");
  printf(
  "stc 2 - string table compiler (part of the dklibs collection, version %s)\n",
  the_version_number
  );
  printf("Copyright (C) 2001-2010 Dipl.-Ing. D. Krause\n");
  printf("http://dktools.sourceforge.net/stc.html\n\n");
  ptr = license_terms;
  while(*ptr) {
    printf("%s\n", *(ptr++));
  }
  printf("\nLibraries used:\n\n");
  ptr = dklic_get();
  while(*ptr) {
    printf("%s\n", *(ptr++));
  }
  printf("\n");
}



/**	Default help text if help text file is not found.
*/
static char *help_strings[] = {
  (char *)"stc <infilename> <outdirname>",
  (char *)"",
  (char *)"This program compiles a text-form string table into a set",
  (char *)"of binary string tables.",
  (char *)"The binary string tables are stored in <outdirname> and its",
  (char *)"subdirectories depending on languages, regions and encodings",
  (char *)"used in the string tables.",
  (char *)"",
  NULL
};



/**	Name of help text file.
*/
static char str_filename_stc_txt[] = { "stc.txt" };



/**	Print help text.
*/
static
void
print_help DK_P0() {
  dkapp_help(app, str_filename_stc_txt, help_strings);
}



/**	String table name.
*/
static char str_table_name_stc[] = { "stc" };



/**	Long options handled by the program.
*/
static char *long_options[] = {
  /* 00 */ "p$lain",
  /* 01 */ "c$onfigure",
  /* 02 */ "u$nconfigure",
  /* 03 */ "r$eset",
  /* 04 */ "sh$ow-configuration",
  /* 05 */ "v$ersion",
  /* 06 */ "h$elp",
  NULL
};



/**	Convert text to boolean value.
	@param	str	Text to convert.
	@return	1 for true, 0 for false.
*/
static
int
decide DK_P1(char *,str)
{
  int back = 0;
  if(str) {
    switch(*str) {
      case '\0':
      case '+':
        back = 1;
      break;
      case '-':
        back = 0;
      break;
      default:
        back = dkstr_is_on(str);
      break;
    }
  }
  return back;
}



/**	Show current configuration.
	@param	app	Application
	@param	fp	Flag: Force plain output.
*/
static
void
show_configuration DK_P2(dk_app_t *,app, int, fp)
{
  dkapp_stdout(app, error_messages[17]);
  fputc('\n', stdout);
  dkapp_stdout(app, "-p   ");
  dkapp_stdout(app, error_messages[fp ? 18 : 19]);
  dkapp_stdout(app, "   ");
  dkapp_stdout(app, error_messages[20]);
  fputc('\n', stdout);
}



/**	The main() function of the stc program.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
	@return	0 on success, any other value indicates an error.
*/
#if DK_HAVE_PROTOTYPES
int main(int argc, char *argv[])
#else
int main(argc, argv) int argc; char *argv[];
#endif
{
  int xargc, lfd, force_plain;
  char *thisarg, **xargv, **argptr;
  int helponly, versiononly, found, do_configure, do_unconfigure, do_showconf;
  char *infilename, *dirname;
  dk_fne_t *fne;
  int a_filename_was_found;
  
#line 1803 "stc.ctr"

  app = dkapp_open_ext1(argc,argv,packagename,sysconfdir,0,0);
  do_configure = do_unconfigure = do_showconf = 0;
  if(app) {
    force_plain = 0;
    if(dkapp_get_pref(app, str_pref_plain, inbuffer, sizeof(inbuffer),0)) {
      if(dkstr_is_on(inbuffer)) {
	force_plain = 1;
      } else {
	force_plain = 0;
      }
    }
    xargc = dkapp_get_argc(app);
    xargv = dkapp_get_argv(app);
    dkapp_find_multi(app,error_finder,str_table_name_stc);
    helponly = versiononly = 0;
    infilename = dirname = NULL;
    argptr = xargv; lfd = 0;
    argptr++; lfd++;
    while(lfd < xargc) {
      thisarg = *argptr;
      if(*thisarg == '-') {
        thisarg++;
        switch(*thisarg) {
	  case '-': {
	    char *optarg;
	    
	    thisarg++;
	    optarg = dkstr_chr(thisarg, '=');
	    if(optarg) { *(optarg++) = '\0'; }
            found = dkstr_array_abbr(long_options, thisarg, '$', 1);
	    
	    switch(found) {
	      case 0: {		/* plain */
	        if(optarg) force_plain = decide(optarg);
		else force_plain = 1;
	      } break;
	      case 1: {		/* configure */
	        if(optarg) do_configure = decide(optarg);
		else do_configure = 1;
	      } break;
	      case 2: {		/* unconfigure */
	        if(optarg) do_unconfigure = decide(optarg);
		else do_unconfigure = 1;
		if(do_unconfigure) do_configure = 1;
	      } break;
	      case 3: {		/* reset */
	        if(optarg) {
		  if(decide(optarg)) force_plain = 0;
		} else {
		  force_plain = 0;
		}
	      } break;
	      case 4: {		/* show-configura */
	        if(optarg) do_showconf = decide(optarg);
		else	   do_showconf = 1;
	      } break;
	      case 5: {		/* version */
	        
	        if(optarg) versiononly = decide(optarg);
		else versiononly = 1;
	      } break;
	      case 6: {		/* help */
	        if(optarg) helponly = decide(optarg);
		else helponly = 1;
	      } break;
	    }
	  } break;
	  case 'c': {
	    do_configure = decide(++thisarg);
	  } break;
	  case 'u': {
	    do_unconfigure = decide(++thisarg);
	    if(do_unconfigure) do_configure = 1;
	  } break;
	  case 'r': {
	    force_plain = 0;
	  } break;
	  case 'C': {
	    do_showconf = decide(++thisarg);
	  } break;
	  case 'h': {
	    helponly = decide(++thisarg);
	  } break;
	  case 'v': {
	    versiononly = decide(++thisarg);
	  } break;
	  case 'p': {
	    force_plain = decide(++thisarg);
	  } break;
	  default: {
	  } break;
	}
      } else {
	if(infilename) {
	  if(!dirname) {
	    dirname = thisarg;
	  } else {
	    helponly = 1;
	  }
	} else {
	  infilename = thisarg;
	}
      }
      lfd++; argptr++;
    }
    if(!(infilename && dirname)) {
      if(!(helponly || versiononly)) {
        helponly = 1;
      }
    }
    if(!(helponly || versiononly || do_configure || do_showconf)) {
      if(dksf_must_expand_filename(infilename)) {
        a_filename_was_found = 0;
        fne = dkfne_open(infilename,1,0);
        if(fne) {
	  while(dkfne_next(fne)) {
	    a_filename_was_found = 1;
	    run(dkfne_get_fullname(fne), dirname, force_plain);
	  }
	  dkfne_close(fne);
	  if(!a_filename_was_found) {
	    dkapp_err_matchfile(app, infilename);
	  }
        } else {
          run(infilename, dirname, force_plain);
        }
      } else {
        run(infilename, dirname, force_plain);
      }
    } else {
      if(do_configure || do_showconf) {
        if(do_unconfigure) {
	  if(app) dkapp_unconfigure(app);
	} else {
	  if(do_configure) {
	    char *k;
	    k = str_pref_off;
	    if(force_plain) k = str_pref_on;
	    if(app) dkapp_set_pref(app, str_pref_plain, k);
	  }
	  show_configuration(app, force_plain);
	}
      } else {
        print_version();
        if(helponly) {
	  print_help();
        }
      }
    }
    dkapp_close(app);
  } else {
    exval = 1;
  } 
  
#line 1957 "stc.ctr"

  exit(exval); return exval;
}





