/**
 * base package for fastrack applications
 */
com_starsensortech_fastrack_process = new es_lang.Package(true,
	/*Title*/   'JavaScrit ATMS Model Package',
	/*Docs */   '-- none --',
	/*Package*/ function() {
		// Imports
		var LANG = es_lang;
		var NET = es_net;
		var LOG = es_util_logging;
		var XSER = com_pagasg_xxapp_serialize;
		var WU = com_pagasg_www_util;
		var FM = com_starsensortech_fastrack_model;

		var Class = es_lang.Class;
		var Enum = es_lang.Enum;
		var Thread = es_lang.Thread;
		var StringBuilder = LANG.StringBuilder;
		var URLConnection = NET.URLConnection;
		var Container = WU.Container;
		var Objects = WU.Objects;
		var EventManager = WU.EventManager
		var Event = WU.Event;
		var Strings = WU.Strings;
		var QName = XSER.QName;

		var extend = Class.extend;
		var implement = Class.implement;
		var enumerate = Enum.enumerate;
		var argsOrArray2Array = Objects.argsOrArray2Array;
		var display = Objects.display;
		var testEmpty = Strings.testEmpty;
		var testDefault = Strings.testDefault;

		var NS = {
			xsi: "http://www.w3.org/2001/XMLSchema-instance",
			pr: "http://xcenetic.org/2009/prototype",
			am: 'http://atms.starsensortech.com/2009/model',
			ar: 'http://atms.starsensortech.com/2009/report',
			ac: 'http://atms.starsensortech.com/2009/control',
			fm: 'http://fastrack.starsensortech.com/2009/model',
			fs: 'http://fastrack.starsensortech.com/2009/store',
			fr: 'http://fastrack.starsensortech.com/2009/report',
			fp: 'http://fastrack.starsensortech.com/2009/process'
		};

		/**
		 * Standard request
		 */
		var Request = function(transaction, view) {
			var _name = '{http://atms.starsensortech.com/2009/control}request';
			var _start = '<ctl:request xmlns:fp="http://fastrack.starsensortech.com/2009/process" xmlns:rpt="http://fastrack.starsensortech.com/2009/report" xmlns:ctl="http://atms.starsensortech.com/2009/control" xmlns:atms="http://atms.starsensortech.com/2009/model" xmlns:pr="http://xcenetic.org/2009/prototype">';
			var _end = '</ctl:request>';
			var _logger = LOG.Logger.getLogger('com.starsensortech.fastrack.process.Request');

			// Private
			function generateXml() {
				var xml = new StringBuilder();

				xml.append(_start);
				if (transaction) {
					xml.append('\r\n');
					xml.append(transaction.generateXml());
				}
				if (view) {
					xml.append('\r\n');
					xml.append(view.generateXml());
				}

				xml.append('\r\n');
				xml.append(_end);

				return xml.toString();
			}

			// Public
			this.name = _name;
			this.generateXml = generateXml;
			this.transaction = transaction;
			this.view = view;
			this.makePost = function() {
				post = {};
				post[_name] = generateXml();

				return post;
			};
		};
		/**
		 * Response 
		 */
		var Response = function(builder) {
			var _xml;
			var _serializer = new XSER.Serializer();
			var _logger = LOG.Logger.getLogger('com.starsensortech.fastrack.process.Response');

			if (builder) {
				_xml = builder(_serializer.deserialize);
				// TODO: would like to get rid of this at some point ...
				if (_xml.getChild('response', NS.ac) != null) {
					_xml = _xml.getChild('response', NS.ac);
				}
			}

			this.xml = _xml;
		};
		/**
		 * Generic transaction handler for ATMS applications
		 */
		var Transaction = function(builder) {
			var _stack = [];
			var _serializer = new XSER.Serializer();
			var _externalRefs = new Container();
			var _modifications = new Container();
			var _commits = new Container();
			var _updates = new Container();
			var _xml = builder(_serializer.deserialize);
			var _logger = LOG.Logger.getLogger('com.starsensortech.fastrack.process.Transaction');

			function getModificationType(path) {
				var prefix = (path) ? path + '/' : '';
				var fullPath = prefix + '@modification-type'

				return _modifications.get(fullPath);
			}
			function makePath(trace) {
				return trace.join('/');
			}
			function getIndent(depth) {
				str = '';
				for (var i = 0; i < depth; i++) {
					str += '  ';
				}
				return str;
			}
			function getTraceName(node, isAttribute) {
				if (node.ns && node.ns != '') {
					return ((isAttribute)?'@':'') + '{' + node.ns + '}' + node.localName;
				} else {
					return ((isAttribute)?'@':'') + node.localName;
				}
			}
			function hasUpdates(path) {
				var exists = false;

				_updates.locate(function(key, value) {
					if (key.indexOf(path) == 0 || Strings.isEmpty(path)) {
						exists = true;
						return true;
					}
				})
				return exists;
			}
			function hasModifications(path) {
				var exists = false;
				_modifications.locate(function(key, value) {
					if (key.indexOf(path) == 0) {
						exists = true;
						return true;
					}
				})
				return exists;
			}
			function hasExternalRefs(path) {
				var exists = false;
				_externalRefs.locate(function(key, value) {
					if (key.indexOf(path) == 0) {
						exists = true;
						return true;
					}
				})
				return exists;
			}

			function serialize(bldr, nsMap, trace, elm) {
				var child;
				var indent;
				var attr;
				var value;
				var name;
				var path;
				var children;
				var attributes = elm.getAttributes();

				for (var i = 0; i < attributes.length; i++) {
					attr = attributes[i];
					name = getTraceName(attr, true)
					trace.push(name);
					if (_modifications.contains(makePath(trace))) {
						value = Strings.stringValue(_modifications.get(makePath(trace)));
					} else if (_commits.contains(makePath(trace))) {
						value = Strings.stringValue(_commits.get(makePath(trace)));
					} else if (_externalRefs.contains(makePath(trace))) {
						value = Strings.stringValue(_externalRefs.get(makePath(trace)));
					} else {
						value = attr.value;
					}
					if (value != null && value != undefined && value != '') {
						name = getName(nsMap, attr.ns, attr.localName);
						bldr.append(' ' + name).append('="');
						bldr.append(value.replace(/\"/g, '&quot;'));
						bldr.append('"');
					}
					trace.pop();
				}
				
				if (!elm.hasChildren) {
					value = _updates.get(makePath(trace));
					if (value != null && value != undefined && value != '') {
						bldr.append('>' + value + '</' + getName(nsMap, elm.ns, elm.localName) + '>');
					} else if (value == null) {
						bldr.append(' ' + getName(nsMap, NS.xsi, 'nil', 'xsi') + '="true"');
						bldr.append('/>');
					} else {
						bldr.append('/>');
					}
				} else {
					children = elm.getChildren();
					indent = getIndent(trace.length + 1);

					if (children.length > 0) {
						bldr.append('>');
						for (var i = 0; i < children.length; i++) {
							child = children[i];

							trace.push(getTraceName(child));
							path = makePath(trace);
							if (hasUpdates(path) || hasModifications(path) || hasExternalRefs(path)) {
								bldr.append('\r\n' + indent + '<' + getName(nsMap, child.ns, child.localName));
								if (child.getChildren && child.getAttributes) {
									serialize(bldr, nsMap, trace, child);
								}
							} else {
								//_logger.info('Path: ' + path + ' not found');
							}
							trace.pop();
						}
					} else {
						bldr.append('/>');
					}

					bldr.append('\r\n' + getIndent(trace.length) + '</' + getName(nsMap, elm.ns, elm.localName) + '>');
				}
			}
			function getName(nsMap, ns, localName, preferredPrefix) {
				var name;
				var count;
				var prefix;
				
				if (ns != '' && ns != null) {
					if (ns == 'http://www.w3.org/XML/1998/namespace') {
						prefix = 'xml';
					} else {
						prefix = nsMap[ns];
						if (!prefix) {
							if (!preferredPrefix) {
								count = nsMap[''];
								prefix = 'ns' + count;
								nsMap[''] = count + 1;
							} else {
								prefix = preferredPrefix;
							}
							nsMap[ns] = prefix;
						}
					}
					name = prefix + ':' + localName;
				} else {
					name = localName;
				}

				return name;
			}

			function copyContainer(container) {
				var copy = new Container();
				container.getValues(function(key, value) {
					copy.put(key, value);
				});
				return copy;
			}

			// Public
			this.xml = _xml;
			this.isInitialized = false;
			this.isPrepared = false;
			this.push = function() {
				var state = { refs: _externalRefs, mods: _modifications, commits: _commits, updates: _updates };
				
				_externalRefs = copyContainer(_externalRefs);
				_modifications = copyContainer(_modifications);
				_commits = copyContainer(_commits);
				_updates = copyContainer(_updates);
				
				_stack.push(state);
				return state;
			}
			this.pop = function() {
				var state = _stack.pop(state);

				if (state) {
					_externalRefs = state.refs
					_modifications = state.mods;
					_commits = state.commits;
					_updates = state.updates;
					
					return state;
				} else {
					throw new Error('Pop beyond the top of the stack.');
				}
			}
			this.setModificationType = function(type, path) {
				var prefix = (path) ? path + '/' : '';
				var fullPath = prefix + '@modification-type'

				if (type != null) {
					_modifications.put(fullPath, type);
				} else {
					_modifications.remove(fullPath);
				}
			}
			this.getModificationType = getModificationType;
			this.setExternalRef = function(externalId, path) {
				var prefix = (path) ? path + '/' : '';
				var fullPath = prefix + '@{' + NS.am + '}external-ref'

				if (externalId != null) {
					_externalRefs.put(fullPath, externalId);
				} else {
					_externalRefs.remove(fullPath);
				}
			}
			this.getExternalRef = function(path) {
				var prefix = (path) ? path + '/' : '';
				var fullPath = prefix + '@{' + NS.am + '}external-ref'

				return _externalRefs.get(fullPath);
			}
			this.setCommit = function(commit, path) {
				var prefix = (path) ? path + '/' : '';
				var fullPath = prefix + '@commit'

				if (commit != null) {
					_commits.put(fullPath, (commit) ? true : false);
				} else {
					_commits.remove(fullPath);
				}
			}
			this.getCommit = function(path) {
				var prefix = (path) ? path + '/' : '';
				var fullPath = prefix + '@commit'

				return _commits.get(fullPath);
			}
			
			this.generateXml = function(debug) {
				var str;
				var xmlns;
				var nsMap = {};
				var bldr = new StringBuilder();
				var trace = [ ];

				nsMap[''] = 0;

				str = '<' + getName(nsMap, _xml.ns, _xml.localName);

				try {
					serialize(bldr, nsMap, trace, _xml);
				} catch (e) {
					_logger.warning(display(e, 2));
				}

				xmlns = '';
				Objects.getValues(nsMap, function(ns, prefix) {
					if (ns != '') {
						xmlns += ' xmlns:' + prefix + '="' + ns + '"';
					}
				});

				return str + xmlns + bldr.toString();
			};
			this.setUpdate = function(path, value) {
				if (value && value.constructor == String) {
					value = value.replace(/\&/g, '&amp;amp;');
					_logger.info('Setting: ' + path + ' to: ' + value);
				}
				return _updates.put(path, value);
			};
			this.getUpdate = function(path) {
				return _updates.get(path);
			};
			this.clearUpdate = function(path) {
				return _updates.remove(path);
			};
			this.clearUpdates = function(path) {
				var removals = _updates.getKeys(function(key, value) {
					return (key.indexOf(path) == 0);
				});
				for (var i = 0; i < removals.length; i++) {
					_updates.remove(removals[i]);
				}
			}
			this.hasUpdates = hasUpdates;
			this.getUpdates = function() {
				return _updates.getKeys();
			}

			this.clear = function(updates, mods, refs, commits) {
				var all = (arguments.length == 0);

				if (updates || all) {
					_updates.clear();
				}
				if (mods || all) {
					_modifications.clear();
				}
				if (refs || all) {
					_externalRefs.clear();
				}
				if (commits || all) {
					_commits.clear();
				}

				this.isPrepared = false;
				this.isInitialized = false;
			};

			/* [deprecated], use *Update instead */
			this.getPaths = function() {
				return _updates.getKeys();
			}
			this.setPath = function(path, value) {
				return _updates.put(path, value);
			};
			this.clearPath = function(path) {
				return _updates.remove(path);
			};
			this.initRoot = function(rootXml, path, require) {
				var externalId;

				if (rootXml) {
					externalId = WU.Uuid.testEmpty(rootXml.getAttribute('external-id', NS.am));
				} else {
					externalId = null;
				}

				if (require) {
					if (externalId) {
						this.setExternalRef(externalId, path);
						this.setModificationType(null, path);
					} else {
						this.setExternalRef(externalId, path);
						this.setModificationType('new', path);
					}
				} else if (externalId) {
					this.setModificationType(null, path);
				} else {
					this.setModificationType('new', path);
				}
			};
		};
		/**
		 * View / report processor
		 */
		var View = function(builder) {
			var _serializer = new XSER.Serializer();
			var _filters = new Container();
			var _queries = new Container();
			var _options = new Container();
			var _xml = builder(_serializer.deserialize);
			var _logger = LOG.Logger.getLogger('com.starsensortech.fastrack.process.View');

			function getPath(trace) {
				return trace.join('/');
			}
			function getIndent(depth) {
				str = '';
				for (var i = 0; i < depth; i++) {
					str += '  ';
				}
				return str;
			}
			function getTraceName(nsMap, node) {
				var localName;
				var ns;
				var start, end;
				var name;
				var attr = node.getAttribute('name', NS.pr);

				if (attr) {
					name = attr.value;
					start = name.indexOf('{');
					end = name.indexOf('}');
					if (start == 0 && end > start + 1 && end < name.length - 1) {
						ns = name.substring(1, name.indexOf('}'));
						localName = name.substring(end + 1);
						getPrefixedName(nsMap, ns, localName);			// Put it in the prefix map
					}
				}

				return name;
			}
			function getPrefixedName(nsMap, ns, localName, preferredPrefix) {
				var name;
				var count;
				var prefix;
				
				if (ns != '' && ns != null) {
					if (ns == 'http://www.w3.org/XML/1998/namespace') {
						prefix = 'xml';
					} else {
						prefix = nsMap[ns];
						if (!prefix) {
							if (!preferredPrefix) {
								count = nsMap[''];
								prefix = 'ns' + count;
								nsMap[''] = count + 1;
							} else {
								prefix = preferredPrefix;
							}
							nsMap[ns] = prefix;
						}
					}
					name = prefix + ':' + localName;
				} else {
					name = localName;
				}

				return name;
			}
			function hasFilters(path) {
				var exists = false;
				_filters.locate(function(key, value) {
					if (key.indexOf(path) == 0) {
						exists = true;
						return true;
					}
				})
				return exists;
			}
			function hasQuery(path) {
				return _queries.contains(path);
			}
			function serialize(bldr, nsMap, trace, elm, debug) {
				var child;
				var indent;
				var attr;
				var value;
				var values;
				var name;
				var children;
				var valueName;
				var path = getPath(trace);
				var options = _options.get(path);
				var attributes = elm.getAttributes();

				for (var i = 0; i < attributes.length; i++) {
					attr = attributes[i];
					value = attr.value;
					if (attr.localName == 'name' && attr.ns == NS.pr) {
						name = XSER.QName.parse(value);
						if (name) {
							value = getPrefixedName(nsMap, name.namespaceUri, name.localName);
						}
					} else if (attr.ns == NS.ac && options) {
						if (attr.localName == 'keywords' && options.keywords != undefined) {
							value = Strings.testEmpty(options.keywords);
						} else if (attr.localName == 'keywords-require' && options.require != undefined) {
							value = options.require;
						} else if (attr.localName == 'limit') {
							if (options.limit != undefined) {
								value = parseInt(options.limit);
							} else {
								value = parseInt(attr.value);
							}
							value = (isNaN(value)) ? null : value;
						} else if (attr.localName == 'order-by') {
							value = Strings.testEmpty(options.orderBy);
						}
					}
					
					if (value != null && value != undefined) {
						name = getPrefixedName(nsMap, attr.ns, attr.localName);
						bldr.append(' ' + name).append('="');
						bldr.append(testDefault(value).replace(/\"/g, '&quot;'));
						bldr.append('"');
					}
				}
				
				if (!elm.hasChildren) {
					values = _filters.get(getPath(trace));
					if (values != null && values != undefined && values.length > 0) {
						bldr.append('>')
						valueName = getPrefixedName(nsMap, NS.pr, 'value');
						for (var i = 0; i < values.length; i++) {
							bldr.append('\r\n').append(getIndent(trace.length + 1));
							bldr.append('<' + valueName + '>').append(values[i]).append('</' + valueName + '>');
						}
						bldr.append('\r\n').append(getIndent(trace.length));
						bldr.append('</' + getPrefixedName(nsMap, elm.ns, elm.localName) + '>');
					} else {
						bldr.append('/>');
					}
				} else {
					children = elm.getChildren();
					indent = getIndent(trace.length + 1);

					if (children.length > 0) {
						bldr.append('>');
						for (var i = 0; i < children.length; i++) {
							child = children[i];

							trace.push(getTraceName(nsMap, child));
							path = getPath(trace);
							if (debug) {
								alert(debug + ", " + path);
							}
							if (child.ns != NS.pr || hasFilters(path) || hasQuery(path)) {
								bldr.append('\r\n' + indent + '<' + getPrefixedName(nsMap, child.ns, child.localName));
								if (child.getChildren && child.getAttributes) {
									serialize(bldr, nsMap, trace, child, debug);
								}
							} else {
								//_logger.info('Path: ' + path + ' not found');
							}
							trace.pop();
						}
					} else {
						bldr.append('/>');
					}

					bldr.append('\r\n' + getIndent(trace.length) + '</' + getPrefixedName(nsMap, elm.ns, elm.localName) + '>');
				}
			}

			// Public
			this.hasFilters = hasFilters;
			this.getFilters = function() {
				return _filters.getKeys();
			}
			
			this.generateXml = function(debug) {
				var str;
				var xmlns;
				var nsMap = {};
				var bldr = new StringBuilder();
				var trace = [ ];

				nsMap[''] = 0;

				str = '<' + getPrefixedName(nsMap, _xml.ns, _xml.localName);

				try {
					serialize(bldr, nsMap, trace, _xml, debug);
				} catch (e) {
					_logger.warning(display(e, 2));
				}

				xmlns = '';
				Objects.getValues(nsMap, function(ns, prefix) {
					if (ns != '') {
						xmlns += ' xmlns:' + prefix + '="' + ns + '"';
					}
				});

				return str + xmlns + bldr.toString();
			};
			this.getFilter = function(path) {
				return _filters.get(path);
			};
			this.setFilter = function(path, value) {
				var values;

				if (value != null) {
					values = Objects.argsOrArray2Array(arguments, 1);
				} else {
					values = null;
				}
				return _filters.put(path, values);
			};
			/* TODO: deprecate this */
			this.setOptions = function(path, keywords, keywordsRequire, limit, orderBy) {
				var options = _options.get(path);

				if (!options) {
					options = { keywords: null, require: null, limit: null, orderBy: null };
				}

				if (keywords != undefined) {
					options.keywords = keywords;
				}
				if (keywordsRequire != undefined) {
					options.require = keywordsRequire;
				}
				if (limit != undefined) {
					options.limit = limit;
				}
				if (orderBy != undefined) {
					options.orderBy = orderBy;
				}

				_options.put(path, options);
			};
			this.getKeywords = function(path) {
				return _options.get(path);
			};
			this.clearKeywords = function(path) {
				return _options.remove(path);
			};
			this.clearFilter = function(path) {
				return _filters.remove(path);
			};
			this.hasQuery = hasQuery;
			this.isSetQuery = function(path) {
				return _queries.get(path);
			};
			this.setQuery = function(path) {
				return _queries.put(path, true);
			};
			this.clearQuery = function(path) {
				return _queries.remove(path);
			};
			this.clear = function(queries, filters, options) {
				var all = (arguments.length == 0);
				if (queries || all) {
					_queries.clear();
				}
				if (filters || all) {
					_filters.clear();
				}
				if (options || all) {
					_options.clear();
				}
			};
		};

		// ****** Error ******
		var Error = function(builder) {
			var _serializer = new XSER.Serializer();

			this.xml = (builder) ? builder(_serializer.deserialize) : undefined;
			this.setXml = function(xml) {
				this.xml = xml;
			}
		};

		// === PACKAGE PUBLIC ===
		this.NS = NS;
		this.Request = Request;
		this.Response = Response;
		this.Transaction = Transaction;
		this.View = View;
		this.Error = Error;
		// === PACKAGE PUBLIC ===
	}
);

