//
// A program to store XML data
//
//
//    Copyright (C) 2000-1 by Hugh Jack <jackh@gvsu.edu>
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//
//    This program 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 General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program; if not, write to the Free Software
//    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//
// Last Modified: April 8, 2001
//



class list {
	String		name;
	String		value;
	list		next;
	list		previous;
	node		parent;

	list(){
		next = null;
		previous = null;
		parent = null;
	}

	void 	set_name(String text){ name = new String(text);}
	// I feel like I should be deleting old name values here
	String 	get_name(){return name;}
	void 	set_value(String text){ value = new String(text);}
	String 	get_value(){ return value;}
	int	length(){
			int		i;
			list	temp;
			for(i = 0, temp = this; temp != null; temp = temp.next, i++){}
			return i;
		}
	list	position(int location){
			int		i;
			list	temp;
			for(i = 0, temp = this; (i < location) && (temp != null); i++, temp = temp.next){}
			return temp;
		}
}


class node {
	list		start;
	node		previous;
	node		next;
	node		parent;
	node		child;

	node(){
		start = new list();
		start.parent = this;
		previous = null;
		next = null;
		parent = null;
		child = null;
	}

	node	position(int location){
			int		i;
			node	temp;
			for(i = 0, temp = this; (i < location) && (temp != null); i++, temp = temp.next){}
			return temp;
	}

	int	length(){
		int		len;
		node	temp;
		for(len = 0, temp = this; temp != null; len++, temp = temp.next){}
		return	len;
	}
};


class tree {
	node	root;

	tree(){
		root = new node();
		root.start.set_name("root");
	}

	list append_list(list current){
		list	now;
		list	next;
		for(now = current; now.next != null; now = now.next){}
		next = new list();
		next.parent = current.parent;
		now.next = next;
		next.previous = now;
		return next;
	}

	list insert_list(list current){
		list	previous;

		previous = new list();
		previous.parent = current.parent;
		previous.previous = current.previous;
		previous.next = current;
		if(previous.previous != null){
			previous.previous.next = previous;
		} else {
			previous.parent.start = previous;
		}
		current.previous = previous;
		return previous;
	}

	void delete_list(list target){
		if(target.previous != null){
			target.previous.next = target.next;
		} else {
			target.parent.start = target.next;
		}
		target.next.previous = target.previous;
	}

	node append_child(node parent){
		node child = new node();
		if(parent.child == null){
			parent.child = child;
		} else {
			node next;
			for(next = parent.child; next.next != null; next = next.next){}
			next.next = child;
			child.previous = next;
		}
		child.parent = parent;
		return child;
	}

	node insert_child(node parent){
		node child = new node();
		parent.child.previous = child;
		child.next = parent.child;
		parent.child = child;
		child.parent = parent;
		return child;
	}

	node append_next(node current){
		node next_next;
		node next = new node();
		next.parent = current.parent;
		next_next = current.next;
		current.next = next;
		next.next = next_next;
		next.previous = current;
		if(next_next != null) next_next.previous = next;

		return next;
	}

	node insert_next(node current){
		node previous_previous;
		node previous = new node();
		previous_previous = current.previous;
		current.previous = previous;
		previous.next = current;
		previous.previous = previous_previous;
		previous.parent = current.parent;
		if(previous_previous == null){
			current.parent.child = previous;
		} else {
			previous_previous.next = previous;
		}
		return previous;
	}

	void delete_node(node target){
		if(target.next != null) target.next.previous = target.previous;
		if(target.previous == null){
			target.parent.child = target.next;
		} else {
			target.previous.next = target.next;
		}
	}

	node find_node(node parent, String name, String value){
		node	stack[] = new node[20];
		int		stack_pnt;
		int		match_flag;

		stack_pnt = 0;
		match_flag = 0;
		stack[stack_pnt] = parent;
		while((stack_pnt >= 0) && (match_flag == 0)){
			if(stack[stack_pnt] == null){
				stack_pnt--;
			} else if((value == null) && (stack[stack_pnt].start.name.equals(name))){
				match_flag = 1;
			} else if((value != null) && (find_list(stack[stack_pnt].start, name, value) != null)){
				match_flag = 1;
			} else {
				stack[stack_pnt+1] = stack[stack_pnt].child;
				stack[stack_pnt] = stack[stack_pnt].next;
				if(stack[stack_pnt+1] != null){
					stack_pnt++;
				}
			}
		}
		if(match_flag == 1){
			return stack[stack_pnt];
		} else {
			return null;
		}
	}

	list find_list(list start, String name, String value){
		list		current,
					result;
		boolean		flag1, flag2;

		flag1 = false;
		flag2 = false;
		result = null;
		for(current = start; (current != null) && (result == null); current = current.next){
			if(name != null){
				flag1 = name.equals(current.name);
			} else {
				flag1 = true;
			}
			if(value != null){
				flag2 = value.equals(current.value);
			} else {
				flag2 = true;
			}
			if((flag1 == true) && (flag2 == true)) result = current;
			// System.out.println("  Comparing ["+name+"]=("+current.name+")  ["+value+"]=("+current.value+")  "+flag1+"  "+flag2);
		}
		// System.out.println("RESULTS ["+name+"]  ["+value+"]   "+result);
		return result;
	}

	void	dump_init(){

	}

	node		dump_stack[] = new node[20];
	int		dump_stack_pnt = -1;
	int		dump_match_flag = 0;

	void	dump_init(node parent){
		dump_stack_pnt = 0;
		dump_stack[dump_stack_pnt] = parent;
		dump_match_flag = 0;
	}

	String	dump_line(){
		list	list_temp;
		String	out_line = new String("");
		int		i;

		while((dump_stack_pnt >= 0) && (dump_match_flag == 0)){
			if(dump_stack[dump_stack_pnt] == null){
				dump_stack_pnt--;
			} else {
				out_line = "";
				for(i = 0; i < dump_stack_pnt; i++) out_line += "\t";
				for(list_temp = dump_stack[dump_stack_pnt].start; list_temp != null; list_temp = list_temp.next){
					out_line += " "+list_temp.name;
					if(list_temp.value != null) out_line += "=\"" + list_temp.value+"\"";
				}
				// System.out.println(out_line);
				dump_stack[dump_stack_pnt+1] = dump_stack[dump_stack_pnt].child;
				dump_stack[dump_stack_pnt] = dump_stack[dump_stack_pnt].next;
				dump_stack_pnt++;
				return out_line;
			}
		}

		return null;
	}

	void	dump(node parent){
		String	temp;
		dump_init(parent);
		while((temp = dump_line()) != null){
			System.out.println(temp);
		}
	}

};


public class XmlStore {
	tree			data;
	node			stack[];
	int			stack_pnt;

	// Stuff for the parser
	boolean		attr_start;
	boolean		attr_end;
	boolean		arg_start;
	boolean		arg_end;
	boolean		tag_start;
	boolean		tag_end;
	boolean		label_start;
	boolean		label_end;
	String		buffer_attr;
	String		buffer_arg;
	boolean		string_flag;
	boolean		comment_flag;
	node		temp_node;
	list		temp_list;


	XmlStore(){
		data = new tree();
		stack = new node[20];
		//for(int i = 0; i < 20; i++){
		//	stack[i] = new node();
		//}
		stack_pnt = -1;
		buffer_attr = new String("");
		buffer_arg = new String("");
		temp_node = new node();
		temp_list = new list();
	}

	//
	// This routine is designed to merge memory and program files
	// It will update values in the memory store, and add values when needed
	// For program files it will define the new one, and then delete the old
	//
	void merge(XmlStore source){
		node	dest_memory_node;
		node	source_memory_node;
		node	dest_node;
		node	source_node;
		node	source_program_node;
		node	dest_program_node;
		node	dest_plc_node;
		node	source_ladder_node;
		node	dest_ladder_node;
		node	item_node;
		node	stack[] = new node[20];
		int		stack_pnt = 0;
		list	source_list;
		list	source_variable;
		list	dest_variable;
		list	source_program_list;

		// first, update any memory values
		source_memory_node = source.data.find_node(source.data.root, "memory", null);
		if(source_memory_node != null){
			// get the memory node, and create it if needed
			dest_memory_node = data.find_node(data.root, "memory", null);
			if(dest_memory_node == null){
				dest_plc_node = data.find_node(data.root, "plc", null);
				if(dest_plc_node == null){
					dest_plc_node = data.append_child(data.root);
					dest_plc_node.start.set_name("plc");
				}
				dest_memory_node = data.append_child(dest_plc_node);
				dest_memory_node.start.set_name("memory");
			}
			// go through the list of data that arrived
			stack[stack_pnt] = source_memory_node.child;
			stack_pnt++;
			while(stack_pnt > 0){
				if(stack[stack_pnt-1] != null){
					// copy stuff over
					source_list = stack[stack_pnt-1].start;
					source_variable = source.data.find_list(source_list, "name", null);
					if(source_variable != null){
						dest_node = data.find_node(dest_memory_node, "name", source_variable.value);
						if(dest_node == null){
							//
							// a quick and dirty node adder, there is no hierarchy right now
							// YOU COULD ADD IT
							//
							dest_node = data.append_child(dest_memory_node);
						}
						dest_node.start = source_variable;
					}

					if(stack[stack_pnt-1].child != null){
						stack[stack_pnt] = stack[stack_pnt-1].child;
						stack[stack_pnt-1] = stack[stack_pnt-1].next;
						stack_pnt++;
					} else if(stack[stack_pnt-1].next != null){
						stack[stack_pnt-1] = stack[stack_pnt-1].next;
					} else {
						stack_pnt--;
					}
				} else {
					stack_pnt--;
				}
			}
		}

		//
		//  Here the new programs will overwrite the previous programs
		//
		source_program_node = source.data.find_node(source.data.root, "program", null);
		if(source_program_node != null){
			dest_program_node = data.find_node(data.root, "program", null);
			if(dest_program_node == null){
				dest_plc_node = data.find_node(data.root, "plc", null);
				if(dest_plc_node == null){
					dest_plc_node = data.append_child(data.root);
					dest_plc_node.start.set_name("plc");
				}
				dest_program_node = data.append_child(dest_plc_node);
				dest_program_node.start.set_name("program");
			}
//System.out.println("Ready to cut up");
			for(source_ladder_node = source_program_node.child; source_ladder_node != null; source_ladder_node = source_ladder_node.next){
				source_program_list = data.find_list(source_ladder_node.start, "location", null);
				if(source_program_list != null){
//System.out.println("dealing with "+ source_ladder_node);
					// I should probably just copy the list here - YOU COULD DO IT
					dest_ladder_node = data.find_node(dest_program_node, "location", source_program_list.value);
					if(dest_ladder_node == null){
						dest_ladder_node = data.append_child(dest_program_node);
						dest_ladder_node.start.set_name("ladder");
						dest_variable = data.append_list(dest_ladder_node.start);
						dest_variable.set_name("location");
						dest_variable.set_value(source_program_list.value);
					}
					dest_ladder_node.child = source_ladder_node.child;	
				}		
			}
		}
//System.out.println("Done processing");
	}

	void parse_init(){
		stack_pnt = 0;
		stack[stack_pnt] = data.root;

		// set up parser stuff
		attr_start = false;
		attr_end = false;
		arg_start = false;
		arg_end = false;
		tag_start = false;
		tag_end = false;
		string_flag = false;
		label_start = false;
		label_end = false;
		buffer_attr = "";
		buffer_arg = "";
	}

	void parse_char(char in){
		try{
		if(in == '\"'){
			if(string_flag == true){
				string_flag = false;
				arg_end = true;
			} else {
				string_flag = true;
			}
		} else if(string_flag == true){
			if(arg_start == false) buffer_attr += in;
			if(arg_start == true) buffer_arg += in;
		} else if((in == '\n') || (in == '\r')){	// ignore some characters
			// do nothing
		} else if(in == '<'){	// Start of attribute
			if(tag_start == false){	// check for nested tags
				tag_start = true;
				attr_start = true;
				attr_end = false;
				arg_start = false;
				arg_end = false;
				label_start = true;
				label_end = false;
			} else {
				System.out.println("ERROR: misplaced '<' in file");
			}
		} else if(in == '>'){
			if(tag_start == true){
				attr_end = true;
				//tag_start = false;
				tag_end = true;
				arg_end = true;
			} else if(comment_flag == true){
				// say nothing
			} else {
				System.out.println("ERROR: misplaced '>' in file");
			}
		} else if(in == '='){
			attr_end = true;
			arg_start = true;
			// arg_end = false;
		} else if(in == '/'){
			arg_end = true;
			//tag_end = true;
			label_end = true;
		} else if((in == '?') || (in == '!')){
			if(comment_flag != true) buffer_attr += in;
			comment_flag = true;
		} else if((in == ' ') || (in == '\t')){
			arg_end = true;
		} else {
			if(arg_start == false) buffer_attr += in;
			if(arg_start == true) buffer_arg += in;
		}
//System.out.println("char ["+in+"] ");

		if(arg_end == true){
//System.out.println("  Name="+buffer_attr+"   Arg="+buffer_arg);
			if(((label_end == false) || (comment_flag == true)) && (buffer_attr.length() > 0)){
//System.out.println("  Name="+buffer_attr+"   Arg="+buffer_arg);
				if(label_start == true){
					stack[stack_pnt+1] = data.append_child(stack[stack_pnt]);
					temp_list = stack[stack_pnt+1].start;
					stack_pnt++;
					temp_node = stack[stack_pnt];
					label_start = false;
				} else {
					if(buffer_attr.length() > 0) temp_list = data.append_list(stack[stack_pnt].start);
				}
				temp_list.set_name(buffer_attr);
				if(buffer_arg.length() > 0) temp_list.set_value(buffer_arg);
			} else {
				//tag_start = false;
				//tag_end = false;
			}
			buffer_attr = "";
			buffer_arg = "";
			arg_start = false;
			arg_end = false;
			attr_start = false;
			attr_end = false;

			if(tag_end == true){
				if((label_end == true) || (comment_flag == true)){
					stack_pnt--;
					// label_start = false;
					label_end = false;
				}
				tag_end = false;
				tag_start = false;
				comment_flag = false;
			}
		}
		} catch(Exception e){
			System.out.println("Had a parsing problem - stack_pnt:"+stack_pnt);
			e.printStackTrace();
			// exit(0);
		}
	}

	void parse_string(String text){
		int		i;
		int		len;

		len = text.length();
//System.out.println("Line["+text+"]");
		for(i = 0; i < len; i++) parse_char(text.charAt(i));
	}


	node	output_stack[] = new node[20];
	node	output_label[] = new node[20];
	boolean	output_end_label[] = new boolean[20];
	int	output_stack_pnt = -1;
	int	output_match_flag = 0;

	void	output_xml_init(node parent){
		output_stack_pnt = 0;
		output_stack[output_stack_pnt] = parent;
		output_end_label[output_stack_pnt] = false;
	}

	void	output_xml(node parent){
		String temp;
		output_xml_init(parent);
		while((temp = output_xml_line()) != null){
			System.out.println(temp);
		}
	}

	String	output_xml_line(){
		list	temp_list = new list();
		int		i;
		String	out_line = new String();

		while(output_stack_pnt >= 0){
			if(output_stack[output_stack_pnt] == null){
				if((output_stack_pnt > 0) && (output_end_label[output_stack_pnt-1] == true)){
					out_line = "";
					for(i = 0; i < output_stack_pnt-1; i++) out_line += '\t';
//System.out.println("Thing "+output_stack_pnt + "    " + output_label[output_stack_pnt-1]);
					out_line += "</"+output_label[output_stack_pnt-1].start.name+">";
					//System.out.println(out_line);
					output_stack_pnt--;
					return out_line;
				} else {
					output_stack_pnt--;
				}
			} else {
				out_line = "";
				for(i = 0; i < output_stack_pnt; i++) out_line += '\t';
				out_line += "<";
				for(temp_list = output_stack[output_stack_pnt].start; temp_list != null; temp_list = temp_list.next){
					out_line += temp_list.name;
					if(temp_list.value != null) out_line += "=\""+temp_list.value+"\"";
					out_line += " ";
				}
				// System.out.println(out_line + "   sddsdf "+output_stack_pnt);
				if(output_stack[output_stack_pnt].child == null){
					output_stack[output_stack_pnt] = output_stack[output_stack_pnt].next;
					out_line += "/>";
				} else {
					out_line += ">";
					output_stack[output_stack_pnt+1] = output_stack[output_stack_pnt].child;
					output_label[output_stack_pnt] = output_stack[output_stack_pnt];
					output_stack[output_stack_pnt] = output_stack[output_stack_pnt].next;
					output_end_label[output_stack_pnt] = true;
					output_stack_pnt++;
					output_end_label[output_stack_pnt] = false;
				}
				//System.out.println(out_line);
				return out_line;
			}
		}
		return null;
	}





	void test(){
		parse_init();
		parse_string("<plc>");
		parse_string("  <memory>");
		parse_string("    <integer name=\"A\" value=\"5\" symbol=\"a test\" >");
		parse_string("    </integer>");
		parse_string("    <integer name=\"B\" value=\"8\" symbol=\"another test\"/>");
		parse_string("  </memory>");
		parse_string("</plc>");
		data.dump(data.root);
	}

	void test2(){
		node	temp1, temp2, temp3, temp4, temp5, temp6;
		list	lst1,	lst2;

		temp1 = data.append_child(data.root);
		temp1.start.set_name("plc");

		temp2 = data.append_child(temp1);
		temp2.start.set_name("memory");

		temp3 = data.append_child(temp2);
		temp3.start.set_name("integer");

		lst1 = temp3.start;
		// temp4 = data.append_list(temp3);
		lst2 = data.append_list(lst1);
		lst2.set_name("name");
		lst2.set_value("A");

		//temp4 = data.append_child(temp3);
		lst2 = data.append_list(lst1);
		lst2.set_name("value");
		lst2.set_value("1");

		temp3 = data.append_child(temp2);
		temp3.start.set_name("integer");

		lst1 = temp3.start;
		//temp4 = data.append_child(temp3);
		lst2 = data.append_list(lst1);
		lst2.set_name("name");
		lst2.set_value("B");

		// temp4 = data.append_child(temp3);
		lst2 = data.append_list(lst1);
		lst2.set_name("value");
		lst2.set_value("2");
	
		System.out.println("After Definition");
		data.dump(data.root);
	
		System.out.println("Dumping XML");
		output_xml(data.root.child);

		temp1 = data.find_node(data.root, "name", "B");
		if(temp1 != null){
			System.out.println("About to delete");
			data.dump(temp1);
			data.delete_node(temp1);
		} else {
			System.out.println("not found");
		}

		System.out.println("After Deletion");
		data.dump(data.root);
	}
};
