/*****
 NAME
 	cguiparser.m - source code for CGUIParser class
 VERSION
 	$Id$
 CHANGELOG
 	$Log$
 */

#include <coconut/gtk/cguiparser.h>
#include <coconut/chash.h>
#include <coconut/cstring.h>
#include <coconut/cconststr.h>
#include <coconut/cint.h>
#include <coconut/csystem.h>
#include <coconut/cerror.h>
#include <coconut/cmessage.h>
#include <coconut/fobject.h>

/* include widgets */
#include <coconut/gtk/carrow.h>
#include <coconut/gtk/cbutton.h>
#include <coconut/gtk/ccalendar.h>
#include <coconut/gtk/ccontainer.h>
#include <coconut/gtk/cframe.h>
#include <coconut/gtk/clabel.h>
#include <coconut/gtk/cseparator.h>
#include <coconut/gtk/cwindow.h>

/* include event handlers */
#include <coconut/gtk/ceventtbl.h>
#include <coconut/ccmdparser.h>
#include <coconut/ctree.h>

/* make enum list */
typedef enum {
	unknown_element_id	= 0,
#	define TAG_NAME(IDENT,VALUE)	IDENT ## _id,
#	include <coconut/gtk/dgtkxml.h>
	last_element_id
} element_name_t ;

typedef enum {
	unknown_attr_id		= 0,
#	define ATTR_NAME(IDENT,VALUE)	IDENT ## _id,
#	include <coconut/gtk/dgtkxml.h>
	last_attr_id
} attr_name_t ;

typedef enum {
	unknown_attr_value_id	= 0,
#	define ATTR_VALUE(IDENT,VALUE)	IDENT ## _id,
#	include <coconut/gtk/dgtkxml.h>
	last_attr_value_id
} attr_value_t ;

/* this file must be installed before nexts */
#include <coconut/dconststr.h>

/* make const string */
#define	TAG_NAME(IDENT,VALUE) \
  static const utf8_char * IDENT ## _str = VALUE ;
#define	ATTR_NAME(IDENT,VALUE) \
  static const utf8_char * IDENT ## _str = VALUE ;
#define	ATTR_VALUE(IDENT,VALUE) \
  static const utf8_char * IDENT ## _str = VALUE ;
#include <coconut/gtk/dgtkxml.h>

@implementation CGUIParser

- init
{
	id	result ;

	result = [super init] ;
	if(result){
		[self setupNameTable] ;
	}
	return result ;
} 

- (void) dealloc
{
	[super dealloc] ;
}

/* this method called at the root node only */
- (id <PWindow>) parse: (id <PXMLTree>) xmltree
{
	return [self parse: [xmltree rootNode] name: [xmltree inputFileName]] ;
}

- (id <PWindow>) parse: (id <PXMLNode>) root name: (id <PBasicStr>) name
{
	id <PWindow> 	window ;

	/* copy the input file name */
	[super setInputName: name] ;

	window = (id <PWindow>) [self parseNode: root] ;

	return window ;
}

- parseNode: (id <PXMLNode>) node
{
	id <PWindow> 	window ;
	element_name_t	wordid ;
	screen_align_t	align ;

	/* check node name */
	wordid = [super searchTagName: [node tagName]] ;

	if(wordid != window_element_id){
		/* the node name is not "window" */
		[super error: node format:
		  "The root node name must be \"%s\".  But \"%s\" is found.",
		    window_element_str, [node tagName]] ;
		return nil ;
	}

	align = [self getScreenAlign: node] ;

	window = [[CWindow alloc] initWindow: window_toplevel align: align] ;
	[CSystem checkPtr: window] ;

	/* add attributes to the window */
	[self parseAttributes: window node: node] ;
	[self parseEvents: window node: node] ;
	[self parseContents: window node: node] ;
	return window ;
}

- parseAttributes: (id <PWidget>) widget node: (id <PXMLNode>) node
{
	id <PConstStr>		value ;
	id <PError>		err ;
	attr_name_t		nameid ;
	attr_value_t		valueid ;

	/* check properties */
	for([node setFirstAttr] ; [node attrName]  ; [node moveNextAttr]){
		nameid = [super searchAttrName: [node attrName]] ;
		value = [node getAttrValue] ;
		valueid = [super searchAttrValue: [value ptr]] ;
		/* printf("aA: id %d, value \"%s\"\n", nameid, [value ptr]) ; */
		switch(nameid){
		  case align_attr_id:
			err = [self setAlign: widget node: node valueId: 
			  valueid];
		  break ;
		  case direction_attr_id:
		  	err = [self setDirection: widget node: node valueId: 
			  valueid];
		  break ;
		  case homogeneous_attr_id:
			err = [self setHomogeneous: widget node: node valueId: 
			  valueid] ;
		  break ;
		  case screen_align_attr_id:
		  	/* this attribute used for Window */
			err = nil ;
		  break ;
		  case shadow_attr_id:
			err = [self setShadow: widget node: node valueId: 
			  valueid] ;
		  break ;
		  case spacing_attr_id:
			err = [self setSpacing: widget node: node value: value];
		  break ;
		  case title_attr_id:
			err = [self setTitle: widget node: node value: value] ;
		  break ;
		  case layout_attr_id:
		  	/* thie attribute used for Container. */
			/* do nothing here */
			err = nil ;
		  break ;
		  default:
			err = [CError not_supported] ;
		  break ;
		}
		if([err code] == not_supported_err){
			/* unsupported attr name */
			[self error: node format: "the attribute \"%s\" is"
			  "not supported for node \"%s\"",
			  [node attrName], [node tagName]] ;
		} else if([err code] == not_matched_err){
			/* unsupported attr value */
			[self error: node format: "the attribute value \"%s\""
			  " is not supported by attribute \"%s\"",
			  [value ptr], [node attrName]] ;
		}
		[value release] ;
	}
	return nil ;
}

- parseEvents: (id <PWidget>) widget node: (id <PXMLNode>) node
{
	id 			result ;
	id <PXMLNode>		eventnode ;
	id <PXMLNode>		child ;
	id <PEventTable>	eventtable ;
	id <PCmdParser>		cmdparser ;
	id <PTree>		cmdtree ;
	element_name_t		nameid ;

	/* search the tag named "events" */
	eventnode = [node searchChildByTagName: events_element_str level: 1] ;
	if(eventnode == nil)
		return nil ;

	/* get event table already defined */
	if((eventtable = [widget getUserObject]) == nil){
		eventtable = [[CEventTbl alloc] init] ;
		[widget setUserObject: eventtable] ;
		[eventtable release] ;
	}

	[eventnode setSelection: selection_rejected] ;
	for(child = [eventnode child] ; child ; child = [child next]){
		if(![child isElementNode] || IS_REJECTED([child selection])){
			continue ;
		}
		nameid = [super searchTagName: [child tagName]] ;

		cmdtree = [[CTree alloc] init] ;
		cmdparser = [[CCmdParser alloc] init] ;

		switch(nameid){
		  case clicked_element_id:
		  	result = [cmdparser parseNode: cmdtree node: [child 
			  child]] ;
			if(result == nil){
				[eventtable connectClickedEvent: widget and: 
				  cmdtree with: nil] ;
			} else {
				[self error: node format: "command description"
				  " error"] ;
			}
		  break ;
		  default:
			[self error: node format: "unknown event \"%s\"",
			  [child tagName]] ;
		  break ;
		}

		[cmdtree release] ; [cmdparser release] ;
		[child setSelection: selection_rejected] ;
	}
	return nil ;
}

- parseContents: (id <PWidget>) widget node: (id <PXMLNode>) node
{
	id <PXMLNode>	child ;
	element_name_t	nameid ;

	/* check children nodes */
	for(child = [node child] ; child ; child = [child next]){
		if(![child isElementNode] || IS_REJECTED([child selection])){
			continue ;
		}
		nameid = [super searchTagName: [child tagName]] ;
		/*printf("aC: id %d, name \"%s\"\n", nameid, [child tagName]) ;
		 */
		switch(nameid){
		  case arrow_element_id:
		  	[self addWidget: widget node: child
			  widget: [[CArrow alloc] init]] ;
			[child setSelection: selection_rejected] ;
		  break ;
		  case button_element_id:
			[self addWidget: widget node: child
			  widget: [[CButton alloc] init]] ;
			[child setSelection: selection_rejected] ;
		  break ;
		  case container_element_id:
			[self addContainer: widget node: child] ;
			[child setSelection: selection_rejected] ;
		  break ;
		  case frame_element_id:
			[self addWidget: widget node: child
			  widget: [[CFrame alloc] init]] ;
			[child setSelection: selection_rejected] ;
		  break ;
		  case label_element_id:
			[self addWidget: widget node: child
			  widget: [[CLabel alloc] init]] ;
			[child setSelection: selection_rejected] ;
		  break ;
		  case separator_element_id:
		  	[self addWidget: widget node: child
			  widget: [[CSeparator alloc] initSeparator: 
			  [self getLayout: child]]] ;
			[child setSelection: selection_rejected] ;
		  break ;
		  default:
			[self error: node format: "unknown tag \"%s\"",
			  [child tagName]] ;
		  break ;
		}
	}
	return nil ;
}

#define	CONTAINER(ID)	((id <PContainer>) ((void *) ID))
- addWidget: (id <PWidget>) parent node: (id <PXMLNode>) node
    widget: (id <PWidget>) newchild
{
	[CSystem checkPtr: newchild] ;
	if(![self isContainer: parent node: node]){
		return nil ;
	}
	[CONTAINER(parent) appendWidget: newchild] ;
	[self parseAttributes: newchild node: node] ;
	[self parseEvents: newchild node: node] ;
	[self parseContents: newchild node: node] ;
	return nil ;
}

- addContainer: (id <PWidget>) parent node: (id <PXMLNode>) node
{
	id <PContainer>	container ;
	id <PConstStr>	vertattr ;
	direction_t	dir = dir_down ;

	if(![self isContainer: parent node: node]){
		[self error: node format: "the parent node is not container"] ;
		return nil ;
	}
	/* search "vert=..." attribute */
	vertattr = [node getAttrValueByName: layout_attr_str] ;
	if(vertattr){
		if([vertattr comparePtrWoCase: holiz_attr_value_str] == 0){
			dir = dir_right ;
		} else if([vertattr comparePtrWoCase: vert_attr_value_str]==0){
			dir = dir_down ;
		}
	}
	container = [[CContainer alloc] initContainer: dir homogeneous:
	  FALSE spacing: 2] ;
	[CSystem checkPtr: container] ;
	[CONTAINER(parent) appendWidget: container] ;
	[self parseAttributes: container node: node] ;
	[self parseContents: container node: node] ;
	return nil ;
}

- (screen_align_t) getScreenAlign: (id <PXMLNode>) node
{
	id <PConstStr>	value ;
	screen_align_t	align ;
	attr_value_t	valueid ;

	align = screen_align_free ;
	value = [node getAttrValueByName: screen_align_attr_str] ;
	if(value){
		valueid = [super searchAttrValue: [value ptr]] ;
		switch(valueid){
		  case free_attr_value_id:
			align = screen_align_free ;
		  break ;
		  case center_attr_value_id:
			align = screen_align_center ;
		  break ;
		  case mouse_attr_value_id:
			align = screen_align_mouse ;
		  break ;
		  default:
			[self error: node format: "unknown property value: "
			  "\"%s\"", [value ptr]] ;
		  break ;
		}
	}
	return align ;
}

- (direction_t) getLayout: (id <PXMLNode>) node
{
	id <PConstStr>	value ;
	attr_value_t	valueid ;
	direction_t	dir ;

	value = [node getAttrValueByName: layout_attr_str] ;
	if(value){
		valueid = [super searchAttrValue: [value ptr]] ;
		switch(valueid){
		  case holiz_attr_value_id:
		  	dir = dir_right ;
		  break ;
		  case vert_attr_value_id:
		  	dir = dir_down ;
		  break ;
		  default:
			[self error: node format: "unknown property value: "
			  "\"%s\"", [value ptr]] ;
		  break ;
		}
	}
	return dir ;
}

#define	ALIGN(ID)	((id <PAlignPart>) ((void *) ID))
- (id <PError>) setAlign: (id <PWidget>) widget node: (id <PXMLNode>) node
    valueId: (int) valueid ;
{
	align_t		dir = align_left ;
	boolean		doholiz = TRUE ;

	if(![widget hasAlign])
		return [CError not_supported] ;
	switch((attr_value_t) valueid){
	  case left_attr_value_id:
		dir = align_left ; 
	  break ;
	  case center_attr_value_id:
	  	dir = align_center ; 
	  break ;
	  case right_attr_value_id:
	  	dir = align_right ; 
	  break ;
	  case top_attr_value_id:
	  	dir = align_top ; doholiz = FALSE ;
	  break ;
	  case middle_attr_value_id:
	  	dir = align_middle ; doholiz = FALSE ;
	  break ;
	  case bottom_attr_value_id:
	  	dir = align_bottom ; doholiz = FALSE ;
	  break ;
	  default:
		return [CError not_matched] ;
	  break ;
	}
	if(doholiz)
		[ALIGN(widget) setHolizAlign: dir] ;
	else
		[ALIGN(widget) setVertAlign: dir] ;
	return nil ;
}

#define	ARROW(ID)	((id <PArrow>) ((void *) ID))

- (id <PError>) setDirection: (id <PWidget>) widget node: (id <PXMLNode>) node
     valueId: (int) valueid
{
	direction_t		dir ;

	if(![widget hasDirection])
		return [CError not_supported] ;
	switch((attr_value_t) valueid){
	  case up_attr_value_id:	dir = dir_up ;		break ;
	  case down_attr_value_id:	dir = dir_down ;	break ;
	  case left_attr_value_id:	dir = dir_left ;	break ;
	  case right_attr_value_id:	dir = dir_right ;	break ;
	  default:
		return [CError not_matched] ;
	  break ;
	}
	[ARROW(widget) setDirection: dir] ;
	return nil ;
}

- (id <PError>) setHomogeneous: (id <PWidget>) widget node: (id <PXMLNode>) node
     valueId: (int) valueid
{
	boolean		value ;
	if(![self isContainer: widget node: node])
		return [CError not_supported] ;
	switch((attr_value_t) valueid){
	  case true_attr_value_id: value = TRUE ; break ;
	  case false_attr_value_id: value = FALSE ; break ;
	  default:
	  	return [CError not_matched] ;
	  break ;
	}
	[CONTAINER(widget) setHomogeneous: value] ;
	return nil ;
}

#define	SHADOW(ID)	((id <PShadowPart>) ((void *) ID))
- (id <PError>) setShadow: (id <PWidget>) widget node: (id <PXMLNode>) node
     valueId: (int) valueid
{
	shadow_t	shadow ;

	if(![widget hasShadow])
		return [CError not_supported] ;
	switch((attr_value_t) valueid){
	  case none_attr_value_id:
	  	shadow = shadow_none ;
	  break ;
	  case in_attr_value_id:
	  	shadow = shadow_in ;
	  break ;
	  case out_attr_value_id:
	  	shadow = shadow_out ;
	  break ;
	  case etched_in_attr_value_id:
	  	shadow = shadow_etched_in ;
	  break ;
	  case etched_out_attr_value_id:
	  	shadow = shadow_etched_out ;
	  break ;
	  default:
	  	return [CError not_matched] ;
	  break ;
	}
	[SHADOW(widget) setShadow: shadow] ;
	return nil ;
}

- (id <PError>) setSpacing: (id <PWidget>) widget node: (id <PXMLNode>) node
     value: (id <PConstStr>) value
{	
	unsigned long int	result ;

	if(![widget isContainer])
		return [CError not_supported] ;
	if([CInt strToULong: [value ptr] to: &result] == nil){
		[CONTAINER(widget) setSpacing: result] ;
	} else {
		[self error: node format: "the interger value required "
		  "but the attr value \"%s\" was given", [value ptr]] ;
	}
	return nil ;
}

#define	TITLE(ID)	((id <PTitlePart>) ((void *) ID))
- (id <PError>) setTitle: (id <PWidget>) widget node: (id <PXMLNode>) node
     value: (id <PConstStr>) value
{
	if([widget hasTitle]){
		[TITLE(widget) setTitle: [value ptr]] ;
		return nil ;
	} else
		return [CError not_supported] ;
}

- (boolean) isContainer: (id <PWidget>) widget node:(id <PXMLNode>) node
{
	boolean		result ;

	if(!(result = [widget isContainer])){
		[self error: node format: "the parent of \"%s\" is not "
		  "container.", [node tagName]] ;
	}
	return result ;
}

- setupNameTable
{
	/* setup tag/attr name/attr value table */
#	define TAG_NAME(NAME, STR) \
	  [super addTagName: STR value: NAME ## _id] ;
#	define ATTR_NAME(NAME, STR) \
	  [super addAttrName: STR value: NAME ## _id] ;
#	define ATTR_VALUE(NAME, STR) \
	  [super addAttrValue: STR value: NAME ## _id] ;
#	include <coconut/gtk/dgtkxml.h>
	return nil ;
}

@end

