/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/*global window ArrayBuffer addEventListener removeEventListener self XMLHttpRequest define*/
(function() {
	var global = this;
	if (!global.define) {
		global.define = function(f) {
			global.orion = global.orion || {};
			global.orion.PluginProvider = f();
			global.eclipse = global.orion; // (deprecated) backward compatibility 
			delete global.define;
		};
	}
}());

define(function() {
	function PluginProvider(headers) {
		var _headers = headers;
		var _services = [];
		var _connected = false;
		var _activePromises = {};
		var _target = null;
	
		function _publish(message) {
			if (_target) {
				if (typeof(ArrayBuffer) === "undefined") { //$NON-NLS-0$
					message = JSON.stringify(message);
				}
				if (_target === self) {
					_target.postMessage(message);
				} else {
					_target.postMessage(message, "*"); //$NON-NLS-0$
				}
			}
		}
		
		function _getPluginData() {
			var services = [];
			// we filter out the service implementation from the data
			for (var i = 0; i < _services.length; i++) {
				services.push({serviceId: i, names: _services[i].names, methods: _services[i].methods, properties: _services[i].properties });
			}
			return {headers: _headers || {}, services: services};		
		}
		
		function _jsonXMLHttpRequestReplacer(name, value) {
			if (value && value instanceof XMLHttpRequest) {
				return {
					status: value.status, 
					statusText: value.statusText
				};
			}
			return value;
		}
		
		function _createListener(serviceId) {
			return function(event) {
				if (_connected) {
					var message = {
						serviceId: serviceId,
						method: "dispatchEvent", //$NON-NLS-0$
						params: [event]
					};
					_publish(message);
				}
            };
        }
		
		function _handleRequest(event) {
			if (event.source !== _target ) {
				return;
			}
			var message = (typeof event.data !== "string" ? event.data : JSON.parse(event.data)); //$NON-NLS-0$
			if (typeof message.cancel === "string") {
				var promise = _activePromises[message.id];
				if (promise) {
					delete _activePromises[message.id];
					if(promise.cancel) {
						promise.cancel(message.cancel);
					}
				}
				return;
			}	
			var serviceId = message.serviceId;
			var methodName = message.method;
			var params = message.params;
			var service = _services[serviceId];
			var implementation = service.implementation;
			var method = implementation[methodName];
			
			var type;
			if (methodName==="addEventListener") {
				type = params[0];
				service.listeners[type] = service.listeners[type] || _createListener(serviceId);
				params = [type, service.listeners[type]]; 
			} else if (methodName==="removeEventListener") {
				type = params[0];
				params = [type, service.listeners[type]];
				delete service.listeners[type];
			}
			
			var response = {id: message.id, result: null, error: null};		
			try {
				var promiseOrResult = method.apply(implementation, params);
				if(promiseOrResult && typeof promiseOrResult.then === "function"){ //$NON-NLS-0$
					_activePromises[message.id] = promiseOrResult;
					promiseOrResult.then(function(result) {
						delete _activePromises[message.id];
						response.result = result;
						_publish(response);
					}, function(error) {
						if (_activePromises[message.id]) {
							delete _activePromises[message.id];
							response.error = error ? JSON.parse(JSON.stringify(error, _jsonXMLHttpRequestReplacer)) : error; // sanitizing Error object 
							_publish(response);
						}
					}, function() {
						_publish({requestId: message.id, method: "progress", params: Array.prototype.slice.call(arguments)}); //$NON-NLS-0$
					});
				} else {
					response.result = promiseOrResult;
					_publish(response);
				}
			} catch (error) {
				response.error = error ? JSON.parse(JSON.stringify(error, _jsonXMLHttpRequestReplacer)) : error; // sanitizing Error object 
				_publish(response);
			}
		}
	
		this.updateHeaders = function(headers) {
			if (_connected) {
				throw new Error("Cannot update headers. Plugin Provider is connected");
			}
			_headers = headers;
		};
		
		this.registerService = function(names, implementation, properties) {
			if (_connected) {
				throw new Error("Cannot register service. Plugin Provider is connected");
			}
			
			if (typeof names === "string") {
				names = [names];
			} else if (!Array.isArray(names)) {
				names =[];
			}
			
			var method = null;
			var methods = [];
			for (method in implementation) {
				if (typeof implementation[method] === 'function') { //$NON-NLS-0$
					methods.push(method);
				}
			}
			var serviceId = _services.length;
			_services[serviceId] = {names: names, methods: methods, implementation: implementation, properties: properties || {}, listeners: {}};
		};
		this.registerServiceProvider = this.registerService; // (deprecated) backwards compatibility only
		
		this.connect = function(callback, errback) {
			if (_connected) {
				if (callback) {
					callback();
				}
				return;
			}
	
			if (typeof(window) === "undefined") { //$NON-NLS-0$
				_target = self;
			} else if (window !== window.parent) {
				_target = window.parent;
			} else if (window.opener !== null) {
				_target = window.opener;
			} else {
				if (errback) {
					errback("No valid plugin target");
				}
				return;
			}
			addEventListener("message", _handleRequest, false); //$NON-NLS-0$
			var message = {
				method: "plugin", //$NON-NLS-0$
				params: [_getPluginData()]
			};
			_publish(message);
			_connected = true;
			if (callback) {
				callback();
			}
		};
		
		this.disconnect = function() {
			if (_connected) { 
				removeEventListener("message", _handleRequest); //$NON-NLS-0$
				_target = null;
				_connected = false;
			}
			// Note: re-connecting is not currently supported
		};
	}
	return PluginProvider;
});