/*****
 NAME
 	csystem.m - source code for CSystem class
 VERSION
 	$Id$
 CHANGELOG
 	$Log$
 */

#include <coconut/csystem.h>
#include <coconut/cmemalloc.h>
#include <coconut/cobstack.h>
#include <coconut/coptparser.h>
#include <coconut/cstring.h>
#include <coconut/cint.h>
#include <coconut/cerror.h>
#include <stdlib.h>
#include <stdio.h>

/* include constant string definition */
#define	EXTERN(NAME,STR)	const char NAME[] = STR 
#include <coconut/dconststr.h>

/* object pointer */
static id <PSystem>	s_system_object = nil ;

/* status counter */
static int		error_count = 0 ;
static int		warning_count = 0 ;
static int		note_count = 0 ;

/* long jump table */
#define	JMP_TABLE_SIZE	16
static int		jmp_stack_num ;
static jmp_buf		jmp_stack_entry[JMP_TABLE_SIZE] ;

@implementation CSystem

+ pushJump: (jmp_buf *) buf
{
	if(jmp_stack_num == JMP_TABLE_SIZE){
		fprintf(stderr,"fsystem.c: shortage of jmp_stack_entry.\n") ;
		exit((int) error_exit) ;
	}
	memmove(&jmp_stack_entry[jmp_stack_num++], buf, sizeof(jmp_buf)) ;
	return nil ;
}

+ popJump
{
	if(jmp_stack_num > 0){
		jmp_stack_num-- ;
	} else {
		fprintf(stderr,"fsystem.c: jmp_stack_entry is empty.\n") ;
		exit((int) error_exit) ;
	}
	return nil ;
}

+ getJump: (jmp_buf *) env
{
	if(jmp_stack_num > 0){
		memmove(env, &jmp_stack_entry[jmp_stack_num-1], 
		  sizeof(jmp_buf));
	} else {
		fprintf(stderr,"fsystem.c: jmp_stack_entry is empty.\n") ;
		exit((int) error_exit) ;
	}
	return nil ;
}

+ (void *) checkPtr: (void *) p
{
	jmp_buf	env ;
	if(p == NULL){
		[CSystem getJump: &env] ;
		longjmp(env, (int) no_mem_space_err) ;
	}
	return p ;
}

+ (const char *) applicationName
{
	gchar * appname ;
	return (appname = g_get_prgname()) != NULL ? appname : EMPTY_STR ;
}

+ setApplicationName: (const char *) name
{
	g_set_prgname(name) ;
	return nil ;
}

+ exit: (id <PError>) err
{
	exit(error_count == 0 ? [err exitCode] : error_count) ;
	return nil ; /* PROCESS CAN NOT REACH HERE */
}

+ incErrorCount
{
	error_count++ ;
	return nil ;
}

+ (int) errorCount
{
	return error_count ;
}

+ incWarningCount
{
	warning_count++ ;
	return nil ;
}

+ (int) warningCount
{
	return warning_count ;
}

+ incNoteCount
{
	note_count++ ;
	return nil ;
}

+ (int) noteCount
{
	return note_count ;
}

+ (char *) getEnv: (const char *) name
{
	return getenv(name) ;
}

+ setEnv: (const char *) name value: (const char *) value
{
	int	result ;
	result = setenv(name, value, 1) ;
	return result == 0 ? nil : [CError illegal_arg] ;
}

+ setEnvIfNotExist: (const char *) name value: (const char *) value
{
	int	result ;
	result = setenv(name, value, 0) ;
	return result == 0 ? nil : [CError illegal_arg] ;
}

+ unsetEnv: (const char *) name
{
	if(name)
		unsetenv(name);
	return nil ;
}

+ retainMainLoop
{
	return [s_system_object retainMainLoop] ;
}

+ releaseMainLoop: (id <PError>) err 
{
	return [s_system_object releaseMainLoop: err] ;
}

+ (u_int) getMainLoopNest
{
	return [s_system_object getMainLoopNest] ;
}

- init
{
	[CInt initRandomSeed] ;
	system_obstack = [[CObstack alloc] init] ;
	system_opt_parser = [[COptParser alloc] init] ;
	s_system_object = [super init] ;
	main_loop_nest = 0 ;
	main_result = nil ;
	return s_system_object ;
}

- (void) dealloc
{
	[system_obstack release] ;
	[system_opt_parser release] ;
	s_system_object = nil ;
	[super dealloc] ;
}

- (id <PError>) prologue: (int) argc argv: (const char **) argv
{
	id <PError>	err ;

	/* copy application name */
	[CSystem setApplicationName: argv[0]] ;

	/* setup command line parser */
	if((err = [self setupOption: system_opt_parser]) == nil){
		[system_opt_parser startAnalysis: argv[0] 
		  argc: argc argv: argv] ;
		/* set the help message */
		[system_opt_parser setHelpString: [self helpString]] ;
		if((err = [self decodeOption: system_opt_parser]) == nil){
			[self analyzeOption: system_opt_parser] ;
		}
	}
	return err ;
}

- (id <PError>) main
{
	return main_result ;
}

- (id <PError>) epilogue: (id <PError>) preerror
{
	return preerror ;
}

- exitMainLoop: (id <PError>) err
{
	main_result = err ;
	return nil ;
}

- retainMainLoop
{
	main_loop_nest++ ;
	return nil ;
}

- releaseMainLoop: (id <PError>) err
{
	if((--main_loop_nest) <= 0)
		[self exitMainLoop: err] ;
	return nil ;
}

- (u_int) getMainLoopNest
{
	return main_loop_nest ;
}

- (id <PError>) setupOption: (id <POptParser>) parser
{
	/* this method must be overrided */
	return nil ;
}

- (id <PError>) decodeOption: (id <POptParser>) parser
{
	/* this method must be overrided */
	return nil ;
}

- (id <PError>) analyzeOption: (id <POptParser>) parser 
{
	/* this method must be overrided */
	return nil ;
}

- (id <PObstack>) systemObstack
{
	return system_obstack ;
}

- (id <PError>) recoverPrologue: (id <PError>) preerror
{
	/* sorry, I can not do nothing */
	return nil ;
}

- (id <PError>) recoverMain: (id <PError>) preerror
{
	/* sorry, I can not do nothing */
	return preerror ;
}

- (id <PError>) recoverEpilogue: (id <PError>) preerror
{
	/* sorry, I can not do nothing */
	return preerror ;
}

- (const utf8_char *) helpString
{
	static const utf8_char help[] = "[options]" ;
	return help ;
}

- printUsage 
{
	return [system_opt_parser printUsage] ;
}

- printVersion
{
	fprintf(stderr, "coconut library version %s\n", COCONUT_VERSION) ;
	return nil ;
}

@end

int start_program(id <PSystem> system, int argc, const char **argv)
{
	jmp_buf		env ;
	id <PError>	pstate ;
	id <PError>	mstate ;
	id <PError>	estate ;
	error_code_t	jstate ;
	exit_code_t	result ;

	/* prologue */
	jstate = (error_code_t) setjmp(env) ;
	if(jstate == no_error){
		[CSystem pushJump: &env] ;
		  pstate = [system prologue: argc argv: argv] ;
		[CSystem popJump] ;
	} else { 
		pstate = [CError can_not_continue] ;
	}
	if(pstate != nil){
		pstate = [system recoverPrologue: pstate] ;
	}

	/* main */
	jstate = (error_code_t) setjmp(env) ;
	if(jstate == no_error){
		[CSystem pushJump: &env] ;
		  mstate = [system main] ;
		[CSystem popJump] ;
	} else {
		mstate = [CError can_not_continue] ;
	}
	if(mstate != nil){
		mstate = [system recoverMain: mstate] ;
	}

	/* epilogue */
	jstate = (error_code_t) setjmp(env) ;
	if(jstate == no_error){
		[CSystem pushJump: &env] ;
		  estate = [system epilogue: mstate] ;
		[CSystem popJump] ;
	} else {
		estate = [CError can_not_continue] ;
	}

	if(estate != nil){
		estate = [system recoverEpilogue: estate] ;
	}

	if(error_count > 0)
		result = error_counter_exit ;
	else if(estate)
		result = [estate exitCode] ;
	else
		result = normal_exit ;
	return (int) result ;
}

