var JSControl = Class.create({
	initialize: function(element){
		this.element = $(element);
		if (!this.element) return;
		this._id_ = Element.readAttribute(this.element, "id");
		this.element.JSControl = this;
	},

	ID: function(){
		return this._id_;
	},

	Value: function(){
		return Element.getTextFast(this.element);
	},

	SetValue: function(){
	},

	SetAttribute: function(attrName, attrValue){
		switch (attrName){
			case "style": Element.setStyle(this.element, JSControl.parseStyle(attrValue)); break;
			case "htmlid":
			case "title":
			default: break;
		}
	},

	SetTabOrder: function(nTabBase){
		this.element.tabIndex = nTabBase++;
		return nTabBase;
	},

	onchange: function(args){ new AEvent("CHANGE", typeof args == "undefined" ? {} : {"args": args}, this); },

	isChildControl: function(jscontrol){ return jscontrol.ID().startsWith(this.ID() + "."); }
});

JSControl.parseStyle = function(str){
	var result = {};
	String(str).replace(/\/\*[\S\s]*?\*\//g, "").split(";").each(function(record){
		var matches = record.match(/^\s*([a-zA-Z-]+)\s*\:\s*([\S\s]+)\s*$/);
		if (!matches || matches.collect(Object.isUndefined).any()) return;
		result[String(matches[1]).camelize()] = matches[2];
	});
	return result;
}

Event.fire = function(element, eventType){
	if (document.createEventObject){
			var event = document.createEventObject();
			return element.fireEvent('on' + eventType, event);
	}else{
		var event = document.createEvent("HTMLEvents");
		event.initEvent(eventType, true, true ); // eventType, bubbling, cancelable
		return !element.dispatchEvent(event);
	}
}

JSControl.struct2attrs = function(struct){
	return Object.keys(struct).collect(function(key){
		return key + "='" + AXML.EscapeEntities(struct[key]) + "'";
	}).join(" ");
}

JSControl.TransformDOM = function(xmldom, xsldom, to_fragment){
	if (typeof XSLTProcessor != "undefined"){
		var processor = new XSLTProcessor();
		processor.importStylesheet(xsldom);
		return to_fragment ? processor.transformToFragment(xmldom, document).xml : processor.transformToDocument(xmldom).xml;
	}
	if (!Object.isUndefined(ActiveXObject)) return xmldom.transformNode(xsldom);
	throw new Error("JSControl.TransformDOM: No XSLT engine found.");
}

JSControl.ParseXML = function(str){
	if (window.DOMParser) return (new DOMParser()).parseFromString(str, "text/xml");
	if (!Object.isUndefined(ActiveXObject)){
		var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
		xmldoc.async = "false";
		xmldoc.loadXML(str);
		return xmldoc;
	}else{
		throw new Error("JSControl.ParseXML: Unable to parse XML document from string.");
	}
}

JSControl.TransformToDocument = function(xmldom, xsldom){
	if (typeof XSLTProcessor != "undefined"){
		var processor = new XSLTProcessor();
		processor.importStylesheet(xsldom);
		return processor.transformToDocument(xmldom);
	}
	if (!Object.isUndefined(ActiveXObject)) return JSControl.ParseXML(xmldom.transformNode(xsldom));
	throw new Error("JSControl.TransformToDocument: No XSLT engine found.");
}

JSControl.auxWindowClass = "AuxElementsOnly";

JSControl.TransformElement = function(attrs, types){
	return JSControl.TransformElements([attrs], types);
}

JSControl.Unwrap = function(html){
	return String(html).replace(/^\s*<[^>]+?>([\s\S]*?)<\/[^>]+>\s*$/, "$1");
}

JSControl.TransformElementAndUnwrap = function(attrs, types){
	return JSControl.TransformElementsAndUnwrap([attrs], types);
}

JSControl.createXML = function(desc){
	if (!desc["id"]) return "";
	if (typeof desc["htmlid"] == "undefined") desc["htmlid"] = desc["id"];
	var nodeName = "ele";
	var childNodes = [];
	var attrs = {};
	for (var key in desc){
		if (key == "nodeName") nodeName = desc[key];
		else if (key == "childNodes") childNodes = desc[key];
		else attrs[key] = desc[key];
	}
	return "<" + nodeName + " " + JSControl.struct2attrs(attrs) + //
		(childNodes && childNodes.length > 0 ? ">" + $A(childNodes).collect(arguments.callee).join("") + "</" + nodeName + ">" : "/>");
}

JSControl.TransformElements = function(descrs, types){
	var wndattrs = { "xmlns": AIURI, "css-class": JSControl.auxWindowClass, "xmlns:st": STURI };
	if (!Object.isArray(types)) types = new Array();
	descrs = $A(descrs);
	var xml = "<wnd " + JSControl.struct2attrs(wndattrs) + ">" +
			types.collect(function(from){ return "<types from='" + from + "'/>"; }).join("") +
			descrs.collect(JSControl.createXML).join("") +
			"<lay>" +
				descrs.collect(function(attrs){
					if (attrs["class"] == "DynamicAutosuggest") return "";
					return attrs["id"] ? "<ele id='" + attrs["id"] + "'/>" : "";
				}).join("") +
			"</lay>" +
		"</wnd>";
	var result = GWindowManager.Transform(JSControl.ParseXML(xml));
	return result.replace(/<\/?\s*(html|head|body)\s*\/?>/gi, "");
}

JSControl.TransformElementsAndUnwrap = function(descrs, types){
	return JSControl.Unwrap(JSControl.TransformElements(descrs, types));
}

JSControl.SelectNodes = function(node, xpathExpr, namespaces) {
	namespaces = namespaces || {};
	if (typeof XPathEvaluator != "undefined") {
		var resolver = function(prefix){ return namespaces[prefix]; };
		var evaluator = new XPathEvaluator();
		var evalResult = evaluator.evaluate(xpathExpr, node, resolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
		var result = new Array;
		if (evalResult){
			for (var element = evalResult.iterateNext(); element; element = evalResult.iterateNext())
				result.push(element);
		}
		return result;
	} else if (typeof ActiveXObject != "undefined") {
		AXML.registerNS(node, namespaces);
		node.ownerDocument.setProperty("SelectionLanguage", "XPath")
		return node.selectNodes(xpathExpr);
	} else {
		throw new Error("No XPath engine found. ");
	}
};

JSControl.windowChangeEvents = ["WM_WINDOWCHANGE", "WM_CLEAR", "WM_PUSH", "WM_BACK", "WM_TABBAR", "WM_REDIRECT", "WM_LINKOPENER"];

JSControl.AddWindowChangeListener = function(object, handler) {
	JSControl.windowChangeEvents.each(function(eventType){
		SubscribeOnEvent(eventType, "document", handler, object);
	});
}

JSControl.RemoveWindowChangeListener = function(object, handler) {
	JSControl.windowChangeEvents.each(function(eventType){
		UnsubscribeFromEvent(eventType, "document", handler, object);
	});
}

Node.localName = function(node){
	if (!node) return "";
	if (typeof node.localName != "undefined") return node.localName;
	var nodeName = String(node.nodeName);
	var lastColonIndex = nodeName.lastIndexOf(":");
	return nodeName.substr(lastColonIndex + 1);
}

JSControl.copy_node_bruteforce = function(doc, node, deep){
	var node_copy = null;
	if (node.namespaceURI && doc.createElementNS) node_copy = doc.createElementNS(node.namespaceURI, node.localName);
	else node_copy = doc.createElement(Node.localName(node));
	if (!node_copy) return null;
	var attrs = node.attributes;
	for (var iii = 0; iii < attrs.length; ++iii){
		node_copy.setAttribute(attrs[iii].name, attrs[iii].value);
	}
	if (deep){
		for (var iii = 0; iii < node.childNodes.length; ++iii){
			node_copy.appendChild(JSControl.copy_node_bruteforce(doc, node.childNodes[iii], deep));
		}
	}
	return node_copy;
}

JSControl.ImportNode = function(doc, node, deep){
	return (Object.isFunction(doc.importNode)) ? doc.importNode(node, deep) : JSControl.copy_node_bruteforce(doc, node, deep);
}

JSControl.PrintNode = function(node, params){
	var params = params || {};
	var features = {
		"location": "no",
		"status": "no"
	};
	["width", "height"].each(function(key){
		if (Object.isNumber(params[key])) features[key] = String(params[key]) + "px";
		else if (Object.isString(params[key])) features[key] = params[key];
	});
	var newwnd = window.open(null, null, $H(features).collect(function(pair){ return pair.key + "=" + pair.value; }).join(","));
	newwnd.document.open();
	newwnd.document.write("<html><head><title>" + (params["title"] || "") + "</title></head><body></body></html>");
	newwnd.document.close();
	Element.setStyle(newwnd.document.body, {"margin": "0px"});
	newwnd.document.body.appendChild(JSControl.ImportNode(newwnd.document, node, true));
	newwnd.print();
}

JSControl.GetChildControls = function(container){
	var result = new Array();
	if (!container || container.nodeType != Node.ELEMENT_NODE) return result;
	for (var child = container.firstChild; child; child = child.nextSibling){
		if (child.nodeType != Node.ELEMENT_NODE) continue;
		if (child.JSControl) result.push(child.JSControl);
		else result.push.apply(result, arguments.callee(child));
	}
	return result;
}

JSControl.FindFirstFocusable = function(container){
	if (!container || container.nodeType != Node.ELEMENT_NODE) return null;
	for (var child = container.firstChild; child; child = child.nextSibling){
		if (child.nodeType != Node.ELEMENT_NODE) continue;
		if (child.getAttributeNode("tabIndex") && typeof child.tabIndex != "undefined" && child.tabIndex >= 0) return child;
		var focusable = arguments.callee(child); if (focusable) return focusable;
	}
}

var HandleStorage = Class.create({
	initialize: function($super, element){
		this.element = $(element);
		this.handles = {};
		Element.descendants(this.element).each(function(child){
			var hook = child.getAttribute("hook"); if (!hook) return;
			this.handles[hook] = child;
		}, this);
	},
	GetHandle: function(hook){ return this.handles[hook]; },
	GetHandles: function(){ return Object.values(this.handles); }
});

