
/*-------------------------------------------------------------------------
 * pg_guc
 *
 * Displays available options under grand unified configuration scheme 
 * Refer to header file, pg_guc.h for more details.
 *
 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
 *
 * $Header: $ 
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"
#include <fcntl.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#include <errno.h>
#include <float.h>
#include <limits.h>
#include <unistd.h>

#include "utils/guc.h"
#include "utils/guc_vars.h"
#include "utils/pg_guc.h"


/*
 * This array contains the display names for each of the GucContexts available
 */
char *GucContext_names [] = { translatable("INTERNAL"),
							  translatable("POSTMASTER"), 
							  translatable("SIGHUP"),
                              translatable("BACKEND"), 
							  translatable("SUSET"),
							  translatable("USERSET")
                            };

/*
 * Reads in the the command line options and sets the state of the program
 * accordingly. Initializes the result list and sorts it.
 */
int GucInfoMain ( int argc, char *argv[] )  {

	extern char *optarg;
	char c;

	while ((c = getopt(argc, argv, "g:mMhGl")) != -1)
	{
		switch (c)
		{
			case 'g':
				groupString = optarg;
				break;
			case 'r':
				nameString = optarg;
				nameRegexBool = true;
				break;
			case 'm':
				outFormat = MACHINE;
				break;
			case 'M':
				outFormat = MACHINE;
				suppressAllHeaders = true;
				break;
			case 'G':
				groupResults = false;
				break;
			case 'l':
				listAllGroups();
				exit(0);
			case 'h':
				helpMessage();
				exit(0);
				break;
			default:
				fprintf(stderr, gettext("%s \n Try -h for further details\n"), usageErrMsg());
				exit(1);
		}
	}


	if ( nameString == NULL ) {
		nameRegexBool = false;
		if ( argc > optind ) {
			nameString = argv[optind];
		}
	}


	/* get this list of variables that match the user's specs. */
	mixedStruct ** varList = varsToDisplay ();

	/* sort them if desired */
	if ( groupResults ) {
		qsort ( varList, resultListSize,
			sizeof(mixedStruct*), compareMixedStructs
		);
	}

	/* output the results */
	int i=0;
	if ( ! suppressAllHeaders ) {
		printf ( gettext(COLUMN_HEADER [outFormat]) );
	}
	while ( varList [i] != NULL ) {
		printf ( gettext(ROW_SEPARATOR [outFormat]) );
		printMixedStruct ( *(varList[i++]) );
	}
	return 0;
}


/*
 * This function is used to compare two mixedStruct types. It compares based
 * on the value of the 'group' field, and then the name of the variable.
 * Each void* is expected to be a pointer to a pointer to a struct.
 * (This is because it is used by qsort to sort an array of struct pointers)
 *
 * Returns an integer less than, equal to, or greater than zero if the first
 * argument (struct1) is considered to be respectively less than, equal to,
 * or greater than the second (struct2). The comparison is made frist on the
 * value of struct{1,2}.generic.group and then struct{1,2}.generic.name. The
 * groups will display in the order they are defined in enum config_group
 */
static int compareMixedStructs ( const void* struct1, const void* struct2 ) {

	mixedStruct *structVar1 = *(mixedStruct **)struct1;
	mixedStruct *structVar2 = *(mixedStruct **)struct2;
	int lessThan = -1;
	int greaterThan = 1;

	if ( structVar1->generic.group > structVar2->generic.group ) {
		return greaterThan;
	} else if ( structVar1->generic.group < structVar2->generic.group ) {
		return lessThan;
	} else {
		return strcmp(structVar1->generic.name, structVar2->generic.name);
	}
}


/*
 * This function returs a complete list of all the variables to display,
 * according to what the user wants to see.
 *
 * Returns pointer to a global variable to emphasise that this variable
 * cannot be used until after this function is properly called.
 *
 * It iterates through all the different variable structs, creates a
 * mixedstruct type for each one, and then decides if this variable matches
 * the user's specifications, and if so it adds it to the return list
 */
static mixedStruct **varsToDisplay ( void ) {

	int arrayIndex = 0;

	int i=0;
	while ( ConfigureNamesBool[i].gen.name != NULL ) {
		mixedStruct *boolVar = buildMixedStruct ( PGC_BOOL, &ConfigureNamesBool[i] );
		if ( varMatches ( *boolVar ) ) {
			resultList [arrayIndex++] = boolVar;
		}
		i++;
	}

	i=0;
	while ( ConfigureNamesInt[i].gen.name != NULL ) {
		mixedStruct *intVar = buildMixedStruct ( PGC_INT, &ConfigureNamesInt[i] );
		if ( varMatches ( *intVar ) ) {
			resultList [arrayIndex++] = intVar;
		}
		i++;
	}

	i=0;
	while ( ConfigureNamesReal[i].gen.name != NULL ) {
		mixedStruct *realVar = buildMixedStruct ( PGC_REAL, &ConfigureNamesReal[i] );
		if ( varMatches ( *realVar ) ) {
			resultList [arrayIndex++] = realVar;
		}
		i++;
	}

	i=0;
	while ( ConfigureNamesString[i].gen.name != NULL ) {
		mixedStruct *stringVar = buildMixedStruct ( PGC_STRING, &ConfigureNamesString[i] );
		if ( varMatches ( *stringVar ) ) {
			resultList [arrayIndex++] = stringVar;
		}
		i++;
	}

	/* set an end marker */
	resultList [ arrayIndex ] = NULL;
	resultListSize = arrayIndex;
	return resultList;
}


/*
 * This function is used to convert on of a number of different types of
 * variable structs ( int, real, string, bool ) into a generic mixedstruct
 * type. This way, multiple functions can operate on them conveniently
 * without needing to know exactly what type they are.
 */
static mixedStruct *buildMixedStruct ( enum config_type configType, void * mixed ) {

	mixedStruct *structToReturn = malloc ( sizeof( mixedStruct) );
	switch ( configType ) {
		case PGC_INT :
			structToReturn->type = PGC_INT;
			structToReturn->generic = ((struct config_int *) mixed)->gen;
			structToReturn->var.configInt = *((struct config_int *) mixed);
			break;
		case PGC_STRING :
			structToReturn->type = PGC_STRING;
			structToReturn->generic = ((struct config_string *) mixed)->gen;
			structToReturn->var.configString = *((struct config_string *) mixed);
			break;
		case PGC_REAL :
			structToReturn->type = PGC_REAL;
			structToReturn->generic = ((struct config_real *) mixed)->gen;
			structToReturn->var.configReal = *((struct config_real *) mixed);
			break;
		case PGC_BOOL :
			structToReturn->type = PGC_BOOL;
			structToReturn->generic = ((struct config_bool *) mixed)->gen;
			structToReturn->var.configBool = *((struct config_bool *) mixed);
			break;
		default :
			printf ( gettext("Unidentified variable type!\n") );
			/* do some error catching here? */
			break;
	}

	return structToReturn;
}


/*
 * This function will return false (0) if the struct passed to it (via the
 * pointer) should not be displayed to the user.
 * The criteria to determine if the struct should not be displayed is:
 *  + It's flag bits are set to GUC_NO_SHOW_ALL
 *  + It's flag bits are set to NOT_IN_SAMPLE_CONF 
 *  + It's flag bits are set to DISALLOW_IN_CONF_FILE 
 *
 * Otherwise it will return true (1)
 */
static int displayStruct ( mixedStruct* structToDisplay ) {
    if  ( structToDisplay->generic.flags & ( GUC_NO_SHOW_ALL |
											 NOT_IN_SAMPLE_CONF |
											 DISALLOW_IN_CONF_FILE
										   )
		)
	{
        return 0;
    } else {
        return 1;
    }
}



/*
 * Used to determine if a variable matches the user's specifications (stored in
 * global variables). Returns true if this particular variable information should
 * be returned to the user.
 */
static bool varMatches ( mixedStruct structToTest ) {

	bool matches = false;
	bool specificSearch = false; 	/* This is true if the user searched for
									 * a variable in particular. */

	if ( nameString != NULL && ! nameRegexBool ) {
		if ( strstr ( structToTest.generic.name, nameString ) != NULL ) {
			matches = true;
			specificSearch = true;
		}
	}

	if ( nameString != NULL && nameRegexBool ) {
		/* We do not support this option yet */	
	}

	if ( groupString != NULL && ! groupRegexBool ) {

		if ( strstr ( config_group_names [structToTest.generic.group], groupString ) != NULL ) {
			if ( nameString != NULL ) {
				matches = (matches && true);
			} else {
				matches = true;
			}
		} else {
			matches = false;
		}
	}

	if ( groupString != NULL && groupRegexBool ) {
		/* We do not support this option yet */	
	}

	/* return all variables */
	if ( nameString == NULL && groupString == NULL ) {
		matches = true;
	}

	if ( specificSearch ) {
		return matches;
	} else {
		return matches && displayStruct ( &structToTest );
	}
}



/*
 * This function prints out the generic struct passed to it. It will print out
 * a different format, depending on what the user wants to see.
 */
static int printMixedStruct ( mixedStruct structToPrint ) {

	structVal var = structToPrint.var;

	switch ( structToPrint.type ) {

		case PGC_BOOL:
				printGenericHead ( var.configBool.gen );
				printf ( 
					gettext(BOOL_FORMAT [outFormat]), 
					(var.configBool.reset_val = 0 ) ? gettext("FALSE") : 
													  gettext("TRUE")
				);
				printGenericFoot ( var.configBool.gen );
				break;

		case PGC_INT:
				printGenericHead ( var.configInt.gen );
				printf ( 
					gettext(INT_FORMAT [outFormat]), 
					var.configInt.reset_val,
					var.configInt.min,
					var.configInt.max
				);
				printGenericFoot ( var.configInt.gen );
				break;

		case PGC_REAL:
				printGenericHead ( var.configReal.gen );
				printf (
					gettext(REAL_FORMAT[outFormat]),
					var.configReal.reset_val,
					var.configReal.min,
					var.configReal.max
				);
				printGenericFoot ( var.configReal.gen );
				break;

		case PGC_STRING:
				printGenericHead ( var.configString.gen );
				printf (
					gettext(STRING_FORMAT [outFormat]),
					var.configString.boot_val
				);
				printGenericFoot ( var.configString.gen );
				break;

		default:
				printf(gettext("Unrecognized variable type!\n"));
				break;
	}
	
	return 0;
}


static int printGenericHead ( struct config_generic structToPrint ) {

	printf (
		gettext(GENERIC_FORMAT [outFormat]), structToPrint.name,
		gettext(GucContext_names [ structToPrint.context ]),
		gettext(config_group_names [ structToPrint.group ])
	);
	return 0;
}


static int printGenericFoot ( struct config_generic sPrint ) {

	printf (
		gettext(GENERIC_DESC [outFormat]), 
		(sPrint.short_desc == NULL) ? "" : gettext(sPrint.short_desc),
		(sPrint.long_desc == NULL ) ? "" : gettext(sPrint.long_desc)
	);
	return 0;
}


static void listAllGroups ( void ) {

	printf ( gettext("All currently defined groups\n") );
	printf ( gettext("----------------------------\n") );
	int i =0;
	while ( config_group_names[i] != NULL ) {
		printf (gettext("%s\n"), gettext(config_group_names[i++]));
	}
}

static char * usageErrMsg ( void ) {
	return gettext(
		"Usage for --long-help option: [-g <group>] [-h] [-H] [-G] [-l] [string]\n");
}

static void helpMessage ( void ) {

	printf ( gettext ("Description:\n"
			 "--long-help displays all the runtime options available to postgresql.\n"
			 "It groups them by category and sorts them by name. If available, it will\n"
			 "present a short description, default, max and min values as well as other\n"
			 "information about each option.\n\n"
			 "With no options specified, it will output all available runtime options\n"
			 "in human friendly format, grouped by category and sorted by name.\n\n"

			 "Usage:\n"
			 "  %s\n\n"

			 "General Options:\n"
			 "  [string]	All options with names that match this string\n"
			 "  -g GROUP	All options in categories that match GROUP\n"
			 "  -G      	Do not sort output, by group or name\n\n"
			 "Output Options:\n"
			 "  -m      	Machine friendly format. All fields are tab separated.\n"
			 "  -M      	Same as m, except header with column names is suppressed\n"
			 "  -l      	Prints list of all currently defined groups \\ subgroups\n"	
			 "  -h      	Prints this help message\n"	
			 "\n"),
			 usageErrMsg()
		  );
}
