/*
    This file is part of SUPPL - the supplemental library for DOS
    Copyright (C) 1996-2000 Steffen Kaiser

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* $RCSfile: INIFILE.LOC $
   $Locker: ska $	$Name:  $	$State: Exp $

	Implementation of an interface to access INI files.

	See INIFILE.H for general information.

   ---- 

	CFGFILE
		Black-box type of an opened configuration file.

	CFGFILE *cfgOpen(char *fnam, int mode)
		mode is one of: CFG_READ
		fnam is the name of the file to be opened

		This function opens a configuration file and seeks to the very
		first entry.

		Return NULL on failure

	int cfgClose(CFGFILE *cf)
		Close the configuration file
		Return: as cfgError()

	int cfgError(CFGFILE *cf)
		Check and reset the error flag.

		Return: != 0 if any error occured during the last call to
			cfgError() or cfgOpen()

	int cfgSection(CFGFILE *cf, char *key)
		Seek to the key/section. this function resets the cfgNxtValue()
		loop. This is a special version of cfgSeek() function.
		Return: 0 on failure

	int cfgIsValue(CFGFILE *cf, char *value)
		Probe if the current key contains the value named 'value'.
		Return: 0 on failure

	char *cfgNxtValue(CFGFILE *cf)
		Return the name of the next value of the same key. Most probably
		used in conjunction with cfgSeek(), but can also be used after
		any other function.
		Note: There is no specific order in which the values are returned.
		Note 2: After a cfgSeek() a cfgNxtValue() loop will return all
			values, this is not sure for other functions.
		Note 3: When cfgNxtValue() returned NULL once, it will keep returning
			NULL until resetted by another function.

		Return: NULL if no further value is available.

	int cfgHasData(CFGFILE *cf, char *value)
		Probe if the value 'value' of  the current key contains any data.
		Upon return of this function any comment and any leading/trailing
		white spaces are stripped from the data as if read with the cfgValStr()
		function!
		Return: 0 on failure

	int cfgValInt(CFGFILE *cf, char *value, int dflt)
		Return the data as (signed int) of value 'value' of the current
		key. If the value is not available, 'dflt' is returned.
		Supported are (letters are case-insensitive):
			[+-]?0x[0-9a-f]+		... hexadecimal
			[+-]?[0-9][0-9a-f]*h	... hexadecimal
			[+-]?[01]+b				... binary / dual
			[+-]?[0-7]+[oq]			... octal
			[+-]?[0-9]+d			... decimal
			[+-]?[0-9]+				... decimal
		The data may be preceeded or succeeded by any number of whitespaces,
		a comment may be appended.
		The value itself may be quoted by single or double quotes.
		If no value is specified or does not match one of the pattern
		above, 'dflt' is returned.

	int cfgValBool(CFGFILE *cf, char *value, int dflt)
		Return the data as (bool) of value 'value' of the current
		key. If the value is not available, 'dflt' is returned.
		The data may be preceeded or succeeded by any number of whitespaces,
		a comment may be appended.
		Supported are (letters are case-insensitive):
			true/false
			on/off
			yes/no
			[0-9]+	
		The value itself may be quoted by single or double quotes.
		Return:	0 for false
				1 for true (unless dflt is returned)
		If no value is specified or does not match one of the pattern
		above, 'dflt' is returned.

	char *cfgValStr(CFGFILE *cf, char *value, char *dflt)
		Return the data as (char*) of value 'value' of the current
		key. If the value is not available, 'dflt' is returned.
		The data may be preceeded or succeeded by any number of whitespaces,
		a comment may be appended.
		Any character from the first non-whitespace up to the last non-
		whitespace character on the line or preceeding the comment is
		stored within a static buffer, the pointer to this buffer is
		returned on success.
		One can quote preceeding and/or succeeding and/or comment characters
		using single or double quotes. Those quotes are recognized even
		within words and are stripped from the string. Two quotes
		immediately following each other are interpreted as a quoted quote.

		If no non-whitespace character is found, a pointer to "" is
		returned.

	char *cfgValBuf(CFGFILE *cf, char *value)
		Retrun the data of the requested value including the '\n'
		character and any whitespaces and comments.

		If the value is not found, NULL is returned.

		Note: If the last line of the file does not finish with a
			newline character, one such character is added to the
			buffer.

		Return: number of bytes stored into the buffer 'buf'
			including any trailing newline character '\n', but
			excluding the terminator character '\0'.

   ----

	Target compilers:
   		Micro-C v3.14, Borland C++ v3.1+

   ----
	
	Known bugs:

   ----
	
*/

#ifndef __INIFILE__LOC_
#define __INIFILE__LOC_

#ifndef _MICROC_
#include <string.h>
#endif
#include <assert.h>
#include <stdio.h>
#include <portable.h>
#include "inifile.h"

/* shortcuts */
#define CFG_ERR_MEM CFG_ERR_MEMORY
#define CFG_ERR_ACC CFG_ERR_ACCESS

/* currently fixed filesystem parameters */
#define CFG_QUOTE '"'	/* what is used as quote within INI files */
#define CFG_COMMENT ';'	/* comment character within data of values */
#define CFG_ANYCMT "#;"	/* comment characters at start of file */
#define CFG_USERINI	"\\ETC"		/* home dir -> user INI dir */
#define CFG_SYSINI "\\..\\ETC"	/* app path -> system INI dir */
#define CFG_INIEXT ".INI"		/* INI file extension */

/* wording */
#define CFG_TRUE	I_wdTrue
#define CFG_FALSE	I_wdFalse

/* write modes */
#define CFG_WNONE		0	/* not in write mode */
#define CFG_WFILE		1	/* full file update <-> on close nothing
								is automatically copied */
#define CFG_WSECTION	2	/* section update <-> copy all following
								section on close */


struct CFGI_INIFILE_ {
	FILE *cfgi_fp;		/* opened file */
	char *cfgi_fnam;	/* name of this file */
		/* If fnam != NULL, a file is associated with this INI file.
			If fp == NULL, no INI file has been successfully opened.
			Both conditions are fairly independed on each other, but
			if fp != NULL, fnam != NULL.	*/
	fpos_t cfgi_pos;	/* position of the first value of the currently
							selected key/section */
	fpos_t cfgi_keypos;	/* where the key itself starts */
	FLAG cfgi_avail;	/* if section is available within this file */
						/* must be FALSE if no file is opened */
};
struct CFGI_INIFILES_ {
	struct CFGI_INIFILE_
	  cfgi_user				/* information specific to user INI file */
	 ,cfgi_system;			/* information specific to system INI file */
	char *cfgi_key;			/* currently selected key (section):
								NULL == values preceeding the very first
									section
								dynamically allocated */

	char *cfgi_buf;			/* buffer with the currently parsed value;
								if == NULL => no value selected
								dynamically allocated */

	char *cfgi_value;		/* name of value; points into buf */
		/* the following members describe the currently parsed value
			and are valid only, if value != NULL */
	int cfgi_valLocation;	/* what INI file the value is from
									0 == system INI file
									else: user INI file
							*/
	char *cfgi_data;		/* data string of value; points into buf;
								valid if value != NULL or after one of
								the Has*Section() function was called. */
	char *cfgi_comment;		/* comment string of value; points into buf;
								if no comment, points to "". */
	int cfgi_trustlevel;	/* trustlevel of value <of no interest cur.) */
	int cfgi_type;			/* type of data */
	FLAG cfgi_decomposed;	/* if the data is already preprocessed:
								+ stripped leading & trailing whitepsaces
								+ cut comment */
	FLAG cfgi_writeOpen;	/* opened user INI file for writing */
	fpos_t cfgi_writePos;	/* position after which beginning with the
								next section all the user INI file will
								be copied into the output file in
								cfgCloseWrite(), if writeOpen == CFG_WSECTION */
	char *cfgi_wrSection;	/* section to be opened when the write was
								successful */
	int cfgi_modifications;	/* how often the user INI file was modified
								so far. Used to detect changes of the
								INI file within Enum() functions */
	fpos_t	cfgi_curpos;	/* start of the most recently read line */
};

#ifdef _MICROC_
#define inistru struct CFGI_INIFILES_
#define inifstru struct CFGI_INIFILE_
#else
typedef struct CFGI_INIFILES_ inistru;
typedef struct CFGI_INIFILE_ inifstru;
#endif

extern inistru cfgi_inis;		/* INI files for reading */
extern FILE *cfgi_ini;			/* INI file for writing */
extern char *cfgi_fnam;			/* proposed filename on Close() */
extern char *cfgi_appname;		/* name of current application and basename of
									the associated INI file */
extern char *cfgi_apppath;		/* path where to look for system INI file */
extern FLAG cfgi_nlwritten;		/* '\n' dumped into opened ini file */
extern char *cfgi_string;		/* last result of cfgi_quoteString() */
extern char *cfgi_data;			/* last result of cfgi_packData() */

#ifndef _MICROC_
#define I(m)	cfgi_inis.cfgi_##m 
#else
#define I(m)	cfgi_inis.cfgi_/**/m 
#endif
#define sysini	I(system)
#define usrini	I(user)

	/* has a physical file attached which was successfully opened */
#define hasFile(fstru)	fstru.cfgi_fp
#define hasFilep(fstru)	fstru->cfgi_fp
	/* has a cached value */
#define hasValue()	I(value)
	/* has an opened key */
#define hasKey(fstru)	fstru.cfgi_avail

/*
 *	Make sure the previous line is trailed by a newline
 */
#define CFGI_NXTLINE								\
	if(!cfgi_nlwritten)								\
		cfgi_nlwritten = 1, putc('\n', cfgi_ini);

/*
 *	Reformat the name to have the specified type and
 *	dump it into the opened INI file via cfgCreateValue()
 *
 *	If name == NULL, CFG_ERR_ARG is returned.
 *	If data == NULL, "" is assumed <-> empty value
 *
 *	If name already contains a type specification and this spec is
 *	immediately at the end of the string (aka there is no trailing
 *	whitespace), this type is removed, except for any trust level
 *	specification, which, in turn, is preserved.
 */
int cfgi_putValue(const char * const name, int type
	, const char * const data);

/*
 *	Check if the name ends in a type specification
 *
 *	A type spec looks like:	([\$\%\?])?(\!)?(\<\d+\>)?
 *	$1 and $2 are merged into *type; if neither one is specified,
 *	*type defaults to CFG_TEMPTY.
 *	$3 gets into *trustlevel; if absent, defaults to 0.
 *
 *	Return: pointer to leftmost character of type or t '\0' byte.
 */
char *cfgi_getType(const char * const name, int * const trustlevel
	, int * const type);

/*
 *	Return a dynamically allocated string to:
 *	+ absolute path of the user INI file
 *	+ absolute filename of the user INI file
 *	+ absolute filename of the system INI file
 *	+ INI filename without extension and path
 *
 *	All values are calculated from the values cfgi_appname and
 *	cfgi_apppath or, if not set, from the appName subsystem.
 *
 *	userINIpath() will cache the returne value within the heap to
 *	ensure it does not change during the lifetime of the program.
 *	The returned path does not have the "ETC" portion.
 *
 *	Return:
 *		NULL: out-of-memory (path is free()'ed)
 *		else: pointer to string
 */
char *cfgi_userINIpath(void);
char *cfgi_userINIfile(void);
char *cfgi_sysINIfile(void);
char *cfgi_appName(void);

/*
 *	Return a cfg error string for a supplied error number
 *
 *	Return:
 *		NULL: number too large
 *		else: pointer to static error string
 */
char *cfgi_getErrorString(int errnr);

/*
 *	Create the specific user INI file and open it for writing
 *
 *	If name == NULL --> return out-of-memory error.
 *	name MUST be a dynamically allocated string.
 *
 *	Return:
 *		0: on success
 *		else: cfg error code
 */
int cfgi_createFile(const char * const name);

/*
 *	Closes a probably opened inifstru
 */
void cfgi_clFileStru(inifstru *f);

/*
 *	Newly open an inifstru
 *
 *	The fnam member is always modified.
 *	"fnam" is never modified directly, but f->cfgi_fnam is free()'ed,
 *	in order to place fnam there.
 *
 *	Return:
 *		0: file could not been opened (fnam modified!)
 *		1: file opened
 */
int cfgi_opFileStru(inifstru * const f, char * const fnam);

/*
 *	Open a section which name is already duplicated into the heap
 *	Else it works like cfgOpenSection(), except that the positioning
 *	is undefined on failure and that only those INI files are searched
 *	in which have avail == 0.
 *	If updName != 0, the found name of the section overrides the passed
 *	name.
 */
int cfgi_opSec(char * const name, int updName);

/*
 *	Search a file for a specfic section
 *
 *	If fp == NULL --> fails always.
 *	Return:	cfg error code
 */
int cfgi_findSec(FILE *fp, const char * const name);

/*
 *	Probe the passed line, if it contains a section definition
 *	/^\s*\[\s*([^\]]*)\s*\]/
 *
 *	The function returns a pointer to $1 and puts an '\0' behind
 *	$1, on success.
 *	Return:
 *		NULL: on failure
 */
char *cfgi_isSection(char * const line);

/*
 *	Skip to next section
 *	Return:	CFG_ERR_END at end of file
 */
int cfgi_nxtSec(FILE * const f);

/*
 *	Seek to the first value of the given name within the section
 *	within the given INI file.
 *
 *	If name == NULL, the current name of the value is searched
 */
int cfgi_findVal(inifstru * const f, const char * const name);

/*
 *	Seek to next value and check if it the end of a section
 *	Return: CFG_ERR_END on end of section
 */
//int cfgi_nxtValLine(FILE * const f);

/*
 *	Seek to next value and parse it
 *	Return: CFG_ERR_END on end of section
 */
int cfgi_nxtVal(FILE * const f);

/*
 *	Read in the next line and try to interprete it as value
 *	Return: cfg error code
 *		NONE if a value was found,
 *		END if the end of the section was reached
 *		LINE and COMMENT if ill-formed line or comment line (ignore both)
 */
int cfgi_brkNxtVal(FILE * const f);

/*
 *	Parse value fetched into buf and break it into value/data/type/lvl
 *	Return: CFG_ERR_NONE if a value was found
 */
int cfgi_brkVal(void);

/*
 *	Fetch, parse and decompose a value
 */
int cfgi_parseVal(const char * const name);

/*
 *	Enter write state
 */
int cfgi_wrOpen(void);

/*
 *	Leave write state
 *	If rv == CFG_ERR_NONE, the user INI file is replaced by the constructed,
 *	otherwise the temporary file is discarded.
 *	If rv == CFG_ERR_ALREADY, the call is ignored completely.
 *	Note: cfgReset() is not called, it is recommended to re-open the
 *	user INI file.
 */
int cfgi_wrClose(int rv);

/*
 *	Enter write state and prepare a section-only update
 *	Every byte upto the passed position is copied into the output file.
 *	Then the next section is located and preserved, in order to copy
 *	all bytes from there when closed successfully.
 *	If the passed position points to the section line of the currrent
 *	section (and, thus, must be skipped when locating the next section),
 *	is specified in isSecLine. In fact, one section is skipped and not
 *	only one line.
 */
int cfgi_wrSecOpen(const fpos_t * const pos, int isSecLine);

/*
 *	Copy the a part of the user INI file into the temp output INI
 *	If no user INI file is opened, these functions succeed.
 *	copy(): copies all the remaining part of the user INI file
 *	copyall(): copies the complete user INI file
 *	copyto(): copies all upto, but excluding the specified position
 *	copyfromto(): copies from the beginning to the specified position
 *
 *	Return cfg error code
 */
int cfgi_copy(void);
int cfgi_copyall(void);
int cfgi_copyallfrom(const fpos_t * pos);
int cfgi_copyto(const fpos_t * const pos);
int cfgi_copyallto(const fpos_t * const pos);

/* 
 *	Modify the name of the section to be opened, when the output INI
 *	file is closed with success.
 *	"name" is duplicated into dynamic memory.
 *	SetCurKey() sets the currently opened section.
 */
int cfgi_wrSetKey(const char * const name);
int cfgi_wrSetCurKey(void);

/* Replace the data of a value

	If name == NULL:
		If data == NULL: the currently selected value is deleted;
		else: The data of the currently selected value is replaced by 'data';
	else
		all occurances of value(s) 'name' are removed and
		if data != NULL: a new value 'name' is appened to the section
			with the specified data

	If name == NULL, type may be CFG_TDEFAULT and trustlevel may be
	CFG_DEFAULT_TRUSTLEVEL, in which case the respective values of
	the currently selected values are used.

	Return: cfg error code
*/
int cfgi_replaceValue(const char * const name, const char * const data
	, int type, int trustlevel);

/* Pack the data and the comment portion together 

	Both are delimited by " ;" if the comment is present.

	If needData is true and data == NULL, the return value is NULL.
	In all other cases data == NULL <=> data == "".

	If comment is present, the constructed string is duplicated into
	the heap and is automatically free()'ed when this function is
	called next.

	If malloc() failes, the program is terminated with an error
	message.
*/
char *cfgi_packData(const char * const data, const char * const comment
 , int needData);

/* Quote a string and all quotes within

	string must be != NULL.

	Return:
		NULL: out of memory
		else: pointer to dynamically allocated memory
*/
char *cfgi_quoteString(const char * const string);


#endif /*#	!defined(__HASHFILE__H_) */
