#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "ezxml.h"
#include "read_xml_util.h"

/* Finds child element with given name and returns it. Errors out if
 * more than one instance exists. */
ezxml_t
FindElement(INP ezxml_t Parent, INP const char *Name, INP boolean Required)
{
    ezxml_t Cur;

        /* Find the first node of correct name */
        Cur = ezxml_child(Parent, Name);

        /* Error out if node isn't found but they required it */
        if(Required)
        {
            if(NULL == Cur)
                {
                    printf(ERRTAG
                            "[LINE %d] Element '%s' not found within element '%s'.\n", Parent->line,
							Name, Parent->name);
                    exit(1);
                }
        }

        /* Look at next tag with same name and error out if exists */
        if(Cur != NULL && Cur->next)
        {
            printf(ERRTAG "[LINE %d] Element '%s' found twice within element '%s'.\n", Parent->line,
                    Name, Parent->name);
            exit(1);
        }
    return Cur;
}
/* Finds child element with given name and returns it. */
ezxml_t
FindFirstElement(INP ezxml_t Parent, INP const char *Name, INP boolean Required)
{
    ezxml_t Cur;

        /* Find the first node of correct name */
        Cur = ezxml_child(Parent, Name);

        /* Error out if node isn't found but they required it */
        if(Required)
        {
            if(NULL == Cur)
                {
                    printf(ERRTAG
						"[LINE %d] Element '%s' not found within element '%s'.\n", Parent->line,
                            Name, Parent->name);
                    exit(1);
                }
        }

    return Cur;
}


/* Checks the node is an element with name equal to one given */
void
CheckElement(INP ezxml_t Node, INP const char *Name)
{
	assert(Node != NULL && Name != NULL);
    if(0 != strcmp(Node->name, Name))
        {
            printf(ERRTAG
				"[LINE %d] Element '%s' within element '%s' does match expected "
				"element type of '%s'\n", Node->line, Node->name, Node->parent->name,
                    Name);
            exit(1);
        }
}

/* Checks that the node has no remaining child nodes or property nodes,
 * then unlinks the node from the tree which updates pointers and
 * frees the memory */
void
FreeNode(INOUTP ezxml_t Node)
{
    ezxml_t Cur;
    char *Txt;


        /* Shouldn't have unprocessed properties */
        if(Node->attr[0])
        {
			printf(ERRTAG "[LINE %d] Node '%s' has invalid property %s=\"%s\".\n", Node->line,
                    Node->name, Node->attr[0], Node->attr[1]);
            exit(1);
        }

        /* Shouldn't have non-whitespace text */
        Txt = Node->txt;
    while(*Txt)
        {
            if(!IsWhitespace(*Txt))
                {
                    printf(ERRTAG
						"[LINE %d] Node '%s' has unexpected text '%s' within it.\n", Node->line,
                            Node->name, Node->txt);
                    exit(1);
                }
            ++Txt;
        }

        /* We shouldn't have child items left */
        Cur = Node->child;
    if(Cur)
        {
			printf(ERRTAG "[LINE %d] Node '%s' has invalid child node '%s'.\n", Node->line,
                    Node->name, Cur->name);
            exit(1);
        }

        /* Now actually unlink and free the node */
        ezxml_remove(Node);
}

/* Returns TRUE if character is whatspace between tokens */
boolean
IsWhitespace(char c)
{
    switch (c)
        {
        case ' ':
        case '\t':
        case '\r':
        case '\n':
            return TRUE;
        default:
            return FALSE;
        }
}

const char *
FindProperty(INP ezxml_t Parent, INP const char *Name, INP boolean Required)
{
    const char *Res;

    Res = ezxml_attr(Parent, Name);
    if(Required)
        {
            if(NULL == Res)
                {
                    printf(ERRTAG
						"[Line %d] Required property '%s' not found for element '%s'.\n", Parent->line,
                            Name, Parent->name);
                    exit(1);
                }
        }
    return Res;
}



/* Count tokens and length in all the Text children nodes of current node. */ 
extern void
CountTokensInString(INP const char *Str, OUTP int *Num, OUTP int *Len)
{
    boolean InToken;
    *Num = 0;
    *Len = 0;
    InToken = FALSE;
    while(*Str)
	{
	    if(IsWhitespace(*Str))
		{
		    InToken = FALSE;
		}
	    
	    else
		
		{
		    if(!InToken)
			{
			    ++(*Num);
			}
		    ++(*Len);
		    InToken = TRUE;
		}
	    ++Str;		/* Advance pointer */
	}
}

/* Returns a token list of the text nodes of a given node. This
 * unlinks all the text nodes from the document */ 
extern char **
GetNodeTokens(INP ezxml_t Node)
{
	int Count, Len;
	char *Cur, *Dst;
	boolean InToken;
	char **Tokens;

    
	/* Count the tokens and find length of token data */ 
	CountTokensInString(Node->txt, &Count, &Len);
    
	/* Error out if no tokens found */ 
	if(Count < 1)
	{
	    return NULL;
	}
    Len = (sizeof(char) * Len) + /* Length of actual data */ 
	(sizeof(char) * Count);	/* Null terminators */
    
	/* Alloc the pointer list and data list. Count the final 
	 * empty string we will use as list terminator */ 
	Tokens = (char **)my_malloc(sizeof(char *) * (Count + 1));
    Dst = (char *)my_malloc(sizeof(char) * Len);
    Count = 0;
    
	/* Copy data to tokens */ 
	Cur = Node->txt;
    InToken = FALSE;
    while(*Cur)
	{
	    if(IsWhitespace(*Cur))
		{
		    if(InToken)
			{
			    *Dst = '\0';
			    ++Dst;
			}
		    InToken = FALSE;
		}
	    
	    else
		{
		    if(!InToken)
			{
			    Tokens[Count] = Dst;
			    ++Count;
			}
		    *Dst = *Cur;
		    ++Dst;
		    InToken = TRUE;
		}
	    ++Cur;
	}
    if(InToken)
	{			/* Null term final token */
	    *Dst = '\0';
	    ++Dst;
	}
    ezxml_set_txt(Node, "");
    Tokens[Count] = NULL;	/* End of tokens marker is a NULL */
    
	/* Return the list */ 
	return Tokens;
}


/* Returns a token list of the text nodes of a given node. This
 * does not unlink the text nodes from the document */ 
extern char **
LookaheadNodeTokens(INP ezxml_t Node)
{
	int Count, Len;
	char *Cur, *Dst;
	boolean InToken;
	char **Tokens;

    
	/* Count the tokens and find length of token data */ 
	CountTokensInString(Node->txt, &Count, &Len);
    
	/* Error out if no tokens found */ 
	if(Count < 1)
	{
	    return NULL;
	}
    Len = (sizeof(char) * Len) + /* Length of actual data */ 
	(sizeof(char) * Count);	/* Null terminators */
    
	/* Alloc the pointer list and data list. Count the final 
	 * empty string we will use as list terminator */ 
	Tokens = (char **)my_malloc(sizeof(char *) * (Count + 1));
    Dst = (char *)my_malloc(sizeof(char) * Len);
    Count = 0;
    
	/* Copy data to tokens */ 
	Cur = Node->txt;
    InToken = FALSE;
    while(*Cur)
	{
	    if(IsWhitespace(*Cur))
		{
		    if(InToken)
			{
			    *Dst = '\0';
			    ++Dst;
			}
		    InToken = FALSE;
		}
	    
	    else
		{
		    if(!InToken)
			{
			    Tokens[Count] = Dst;
			    ++Count;
			}
		    *Dst = *Cur;
		    ++Dst;
		    InToken = TRUE;
		}
	    ++Cur;
	}
    if(InToken)
	{			/* Null term final token */
	    *Dst = '\0';
	    ++Dst;
	}
    Tokens[Count] = NULL;	/* End of tokens marker is a NULL */
    
	/* Return the list */ 
	return Tokens;
}

/* Find integer attribute matching Name in XML tag Parent and return it if exists.  
Removes attribute from Parent */
extern int GetIntProperty(INP ezxml_t Parent,
				 INP const char *Name,
				 INP boolean Required,
				 INP int default_value)
{
	const char * Prop;
	int property_value;

	property_value = default_value;
	Prop = FindProperty(Parent, Name, Required);
    if(Prop)
	{
	    property_value = my_atoi(Prop);
	    ezxml_set_attr(Parent, Name, NULL);
	}
	return property_value;
}

/* Find floating-point attribute matching Name in XML tag Parent and return it if exists.  
Removes attribute from Parent */
extern float GetFloatProperty(INP ezxml_t Parent,
				 INP const char *Name,
				 INP boolean Required,
				 INP float default_value)
{
	
	const char * Prop;
	float property_value;

	property_value = default_value;
	Prop = FindProperty(Parent, Name, Required);
    if(Prop)
	{
	    property_value = atof(Prop);
	    ezxml_set_attr(Parent, Name, NULL);
	}
	return property_value;
}

/* Find boolean attribute matching Name in XML tag Parent and return it if exists.  
Removes attribute from Parent */
extern boolean GetBooleanProperty(INP ezxml_t Parent,
				 INP const char *Name,
				 INP boolean Required,
				 INP boolean default_value)
{
	
	const char * Prop;
	boolean property_value;

	property_value = default_value;
	Prop = FindProperty(Parent, Name, Required);
    if(Prop)
	{
	    if((strcmp(Prop, "false") == 0) ||
			(strcmp(Prop, "FALSE") == 0) ||
			(strcmp(Prop, "False") == 0)) {
			property_value = FALSE;
		} else if ((strcmp(Prop, "true") == 0) ||
			(strcmp(Prop, "TRUE") == 0) ||
			(strcmp(Prop, "True") == 0)) {
			property_value = TRUE;
		} else {
			printf(ERRTAG "[LINE %d] Unknown value %s for boolean attribute %s in %s", Parent->line,
				Prop, Name, Parent->name);
			exit(1);
		}
	    ezxml_set_attr(Parent, Name, NULL);
	}
	return property_value;
}


/* Counts number of child elements in a container element. 
 * Name is the name of the child element. 
 * Errors if no occurances found. */ 
extern int
CountChildren(INP ezxml_t Node, INP const char *Name, INP int min_count)
{
    ezxml_t Cur, sibling;
    int Count;

    Count = 0;
    Cur = Node->child;
	sibling = NULL;

	if(Cur)
	{
		sibling = Cur->sibling;
	}
	    
    while(Cur)
	{
		if(strcmp(Cur->name, Name) == 0)
		{
		    ++Count;
		}
	    Cur = Cur->next;
	    if(Cur == NULL)
		{
		    Cur = sibling;
			if(Cur != NULL)
			{
				sibling = Cur->sibling;
			}
		}
	}   
	/* Error if no occurances found */ 
	if(Count < min_count)
	{
		printf(ERRTAG "[Line %d] Expected node '%s' to have %d "
		    "child elements, but none found.\n", Node->line, Node->name, min_count);
	    exit(1);
	}
    return Count;
}


