var qmatic = qmatic || {};
qmatic.webwidget = qmatic.webwidget || {};

(function() {

	qmatic.Errors = {
		INVALID_ARGUMENT : 'Invalid argument'
	};

	/**
	 * Predefined topics (R4)
	 */
	//    qmatic.webwidget.topic = {
	//        ALARM                      : 'com.qmatic.elk.topics.alarm',                   // 1
	//        TICKET_CREATE              : 'com.qmatic.elk.topics.ticketCreate',            // 3
	//        CUSTOMER_CALLED_FROM_QUEUE : 'com.qmatic.elk.topics.customerCalledFromQueue', // 4
	//        TICKET_CALL                : 'com.qmatic.elk.topics.ticketCall',              // 5
	//        WORKSTATION_CLOSE          : 'com.qmatic.elk.topics.workstationClose',        // 6
	//        TICKET_PRINTED             : 'com.qmatic.elk.topics.ticketPrinted',           // 7
	//        TICKET_CREATED_ON_PRINTER  : 'com.qmatic.elk.topics.ticketCreatedOnPrinter',  // 8
	//        MONITOR_EVENT              : 'com.qmatic.elk.topics.monitorEvent',            // 9
	//        CARD_INSERT                : 'com.qmatic.elk.topics.cardInsert',              // 10
	//        WORKSTATION_OPEN           : 'com.qmatic.elk.topics.workstationOpen',         // 16
	//        TICKET_CLOSE               : 'com.qmatic.elk.topics.ticketClose',             // 20
	//        INVALID_CARD               : 'com.qmatic.elk.topics.invalidCard',             // 21
	//        TICKET_CONFIRM             : 'com.qmatic.elk.topics.ticketConfirm',           // 24
	//        WIDGET_LOG_TOPIC           : 'com.qmatic.widget.log',                         // Internal
	//        HOST_SWITCH_PAGE           : 'com.qmatic.elk.topics.switchHostPage',          // Internal -- makes touch switch page
	//        HOST_ISSUE_TICKET          : 'com.qmatic.elk.topics.issueTicket'              // Internal -- makes touch issue ticket
	//    };
	qmatic.webwidget.topic = {
		VISIT_CALL : 'com.qmatic.qp.topic.event.VISIT_CALL',
		VISIT_NEXT : 'com.qmatic.qp.topic.event.VISIT_NEXT',
		VISIT_END : 'com.qmatic.qp.topic.event.VISIT_END',
		SERVICE_POINT_OPEN : 'com.qmatic.qp.topic.event.SERVICE_POINT_OPEN',
		SERVICE_POINT_CLOSE : 'com.qmatic.qp.topic.event.SERVICE_POINT_CLOSE',
		HOST_SWITCH_PAGE : 'com.qmatic.qp.topic.operation.SWITCH_HOST_PAGE',
        HOST_SWITCH_PAGE_COMPLETE: 'com.qmatic.qp.topic.event.SWITCH_HOST_PAGE_COMPLETE',
		WIDGET_LOG_TOPIC : 'com.qmatic.qp.topic.operation.WIDGET_LOG_TOPIC',
		HOST_ISSUE_TICKET : 'com.qmatic.qp.topic.operation.HOST_ISSUE_TICKET',
		WIDGET_CLEANUP : 'com.qmatic.qp.topic.operation.WIDGET_CLEANUP',
        WIDGET_LOADED: 'com.qmatic.qp.topic.operation.WIDGET_LOADED',
        WIDGETS_LOADED: 'com.qmatic.qp.topic.event.WIDGETS_LOADED',
		IDENTIFICATION_DATA : 'com.qmatic.qp.topic.event.CARD_DATA',
		CUSTOMER_IDENTIFIED : 'com.qmatic.qp.topic.event.CUSTOMER_IDENTIFIED',
		PUT_CACHE              : 'com.qmatic.qp.topic.operation.PUT_CACHE',
		GET_CACHE              : 'com.qmatic.qp.topic.operation.GET_CACHE',
		CLEAR_CACHE            : 'com.qmatic.qp.topic.operation.CLEAR_CACHE',
		GET_CURRENT_PAGE	   : 'com.qmatic.qp.topic.operation.GET_CURRENT_PAGE'
	};

	/*
	 * Widget client singleton that allows widgets to communicate with an
	 * Orchestra host page.
	 */
	qmatic.webwidget.client = (function() {

		// Private
		var QMATIC_TOPIC_PREFIX = 'com.qmatic.qp.topic.', iframeId, unitId, unitAndDeviceId, branchId, locale, displayType, hubClient, retObj, connectError = false, connectErrorMessage, configuration, userCallbacks = {
			onCleanUpCallback : undefined, // Called when host page wants widgets to clean themselves up
			onLoadedCallback : undefined, // Called when widget client has finished loading/initialising
			onLoadErrorCallback : undefined
		// Called if an error occurs during startup
		}, cleanUpSignalled = false;

		function onHubClientSecurityAlert(message) {
			retObj.log('WARN', 'Security alert from widget: ' + message);
		}

		function getWidgetIdFromPath() {
			var path = window.location.pathname;
			if (path.match(/^\/wookie\/wservices\//)) {
				path = path.substring(18);
			}
			return path;
		}

		function runCallback(callback, args) {
			try {
				callback.apply(null, args);
			} catch (callbackError) {
				qmatic.webwidget.client.logError('Callback error from widget ('
						+ getWidgetIdFromPath() + '): ' + callbackError);
			}
		}

		function onConfigurationHandler(topic, publisherData, subscriberData) {
			configuration = publisherData;
			displayType = configuration.displayType;
			if (userCallbacks.onLoadedCallback) {
				runCallback(userCallbacks.onLoadedCallback, [ configuration ]);
			}
		}

		function onCleanUpHandler(topic, publisherData, subscriberData) {
			cleanUpSignalled = true;
			if (userCallbacks.onCleanUpCallback) {
				runCallback(userCallbacks.onCleanUpCallback);
			}
			hubClient.disconnect();
		}

		function onHostPageConnectCompleted(hub, success, error) {
			if (success) {
				// This shall always be performed if the widget has custom attributes.
				// basically, we subscribe to a widgetinstance-specific topic where we'll get our attributes.
				// We post a 'STARTED' event to another widgetinstance-specific topic so the host page is aware that
				// we are ready to recieve the event with our attributes.
				if (iframeId !== null) {
					hubClient.subscribe(QMATIC_TOPIC_PREFIX + iframeId
							+ '.configuration', onConfigurationHandler);
					hubClient.subscribe(
							"com.qmatic.qp.topic.operation.WIDGET_CLEANUP",
							onCleanUpHandler);
					hubClient.publish(QMATIC_TOPIC_PREFIX + iframeId
							+ '.lifecycle', 'STARTED');
				}

			} else {
				retObj.log('ERROR', 'Failed to connect to host page. Error: '
						+ error);
				connectError = true;
				connectErrorMessage = error;
				if (userCallbacks.onLoadErrorCallback) {
					runCallback(userCallbacks.onLoadErrorCallback, [ error ]);
				}
			}
		}

		function initialise() {

			// Retrieve Orchestra specific host data from widget page url
			var match = RegExp('[?&]' + 'orchestra_widget_id' + '=([^&]*)')
					.exec(window.location.search);
			iframeId = match[1];

			match = RegExp('[?&]' + 'unit_id' + '=([^&]*)').exec(
					window.location.search);

			unitAndDeviceId = match[1];
			
			match = new RegExp('([^&^:]*:[^&^:]*)').exec(unitAndDeviceId);
			unitId = match[0];

			match = RegExp('[?&]' + 'branch_id' + '=([^&]*)').exec(
					window.location.search);
			branchId = match[1];

			match = RegExp('[?&]' + 'locale' + '=([^&]*)').exec(
					window.location.search);
			if (match) {
				locale = match[1];
			}

			hubClient = new OpenAjax.hub.IframeHubClient({
				HubClient : {
					onSecurityAlert : onHubClientSecurityAlert,
					scope : retObj
				}
			});

			hubClient.connect(onHostPageConnectCompleted, retObj);
		}

		function getXmlHttpRequest() {
			try {
				// Firefox, Opera 8.0+, Safari
				return new XMLHttpRequest();
			} catch (e) {
				// Internet Explorer
				try {
					return new ActiveXObject("Msxml2.XMLHTTP");
				} catch (e) {
					try {
						return new ActiveXObject("Microsoft.XMLHTTP");
					} catch (e) {
						loadError = true;
						display.showError('error.unsupported.browser');
						return null;
					}
				}
			}
		}

		function getRequest(path, callback) {
			var xhr = getXmlHttpRequest();
			xhr.onreadystatechange = function() {
				if (xhr.readyState == 4) {
					if (xhr.status == 200) {
						try {
							var json = eval('(' + xhr.responseText + ')');
							callback(json);
						} catch (err) {
							// Seems to work anyway...
						}
					} else {
						loadError = true;
					}
				}
			};
			xhr.open("GET", path, true);
			xhr.setRequestHeader("Accept", "application/json");
			xhr.send(null);
		}
		;

		// retObj object will be returned to qmatic.webwidget.client
		// Everything inside the object is publicly accessible
		retObj = {
			/**
			 * Log information to Orchestra's Web Widget log.
			 */
			log : function(severity, message) {
				hubClient.publish(qmatic.webwidget.topic.WIDGET_LOG_TOPIC, {
					'severity' : severity,
					'identifier' : iframeId,
					'message' : message
				});
			},

			/**
			 * Log informational message to host page
			 */
			logInfo : function(message) {
				if (window.console && window.console.info) {
					window.console.info(message);
				}
				this.log('info', message);
			},

			logWarn : function(message) {
				if (window.console && window.console.warn) {
					window.console.warn(message);
				}
				this.log('warn', message);
			},

			logError : function(message) {
				if (window.console && window.console.error) {
					window.console.error(message);
				}
				this.log('error', message);
			},

			switchHostPage : function(pageName, isStayOnPage) {
				hubClient
						.publish(
								qmatic.webwidget.topic.HOST_SWITCH_PAGE,
								{
									pageName : pageName,
									isStayOnPage : (typeof isStayOnPage === 'boolean' ? isStayOnPage
											: false)
								});
			},

			/**
			 * Subscribe to a topic to receive published data.
			 * topic : string             : The topic to listen to.
			 * onData : function         : The callback to invoke when someone publishes data on the topic.
			 * == optional params ==
			 * scope : object             : The object to use as scope for the callback. 'this' will then point to the provided object when the callback is invoked.
			 * onComplete : function     : A callback which will be called when the subscribe operation completes successfully.
			 * subscriberData : object  : Data that will be passed back to client in the onData callback when data arrives on the topic.
			 * 
			 * Returns an ID that you can use to identify yourself as subscriber if you wish to unsubscribe later.
			 */
			subscribe : function(topic, onData, scope, onComplete,
					subscriberData) {
				if (typeof topic !== 'string') {
					throw new Error(
							qmatic.Errors.INVALID_ARGUMENT
									+ ". Topic must be a string, e.g. com.example.topic1, a.b.c etc.");
				}

				if (typeof onData !== 'function') {
					throw new Error(
							qmatic.Errors.INVALID_ARGUMENT
									+ ". Please provide a function to use as callback for data handling.");
				}

				return hubClient.subscribe(topic, onData, scope, onComplete,
						subscriberData);
			},

			/**
			 * Unsubscribe from topic.
			 * subscriptionId : string    : ID of the subscription to unsubscribe to stop receiving topic events.
			 * == optional params ==
			 * onCompleted : function    : Callback which will be invoked when the unsubscribe operation completes.
			 * scope : object            : Scope for the callback. 'this' will point to the specified object when callback invokes.
			 */
			unsubscribe : function(subscriptionId, onCompleted, scope) {
				hubClient.unsubscribe(subscriptionId, onCompleted, scope);
			},

			/**
			 * Send data to subscribers on specified topic.
			 */
			publish : function(topic, data) {
				if (typeof topic !== 'string') {
					throw new Error(qmatic.Errors.INVALID_ARGUMENT);
				}

				hubClient.publish(topic, data);
			},

			setOnLoaded : function(callback) {
				if (typeof callback !== 'function') {
					throw new Error(
							qmatic.Errors.INVALID_ARGUMENT
									+ ". Please provide a function to use as callback when widget loads.");
				}

				userCallbacks.onLoadedCallback = callback;
				if (configuration) {
					// Since we have attributes already, trigger the callback immediately
					runCallback(callback, [ configuration ]);
				}
			},

			setOnCleanUp : function(callback) {
				if (typeof callback !== 'function') {
					throw new Error(
							qmatic.Errors.INVALID_ARGUMENT
									+ ". Please provide a function to use as callback when widget needs to clean up.");
				}

				userCallbacks.onCleanUpCallback = callback;
				if (cleanUpSignalled) {
					// clean up already signalled, clean up widget
					runCallback(callback);
					hubClient.disconnect();
				}
			},

			/**
			 * Sets user defined callback to call when widget client has finished loading.
			 * If error has already ocurred when user sets the callback, then the callback
			 * will be triggered immediately with the occurred error as parameter.
			 */
			setOnLoadError : function(callback) {
				if (typeof callback !== 'function') {
					throw new Error(
							qmatic.ERRORS.INVALID_ARGUMENT
									+ ". Please provide a function to use as callback upon widget load errors.");
				}

				userCallbacks.onLoadErrorCallback = callback;
				if (connectError) {
					runCallback(callback, [ connectErrorMessage ]);
				}
			},

			getUnitId : function() {
				return unitId;
			},
			
			getUnitAndDeviceId : function() {
				return unitAndDeviceId;
			},

			getBranchId : function() {
				return branchId;
			},

			getLocale : function() {
				return locale;
			},

			getDisplayType : function() {
				return displayType;
			},

			getConfiguration : function() {
				return configuration;
			},

			/**
			 * Call a REST-based service. The request will be forwarded by the local proxy servlet to Orchestra.
			 * 
			 * @param uri
			 *         The path to the REST service. (e.g. something like /rest/{connectorname}/resource/{id}/)
			 * @callback
			 *      The method that will process the returned value.
			 */
			callConnector : function(uri, callback) {
				getRequest(uri, callback);
			},

			/**
			 * Makes your widget listen to specific results posted to a "cache read results" topic specific
			 * for this widget. (Based on the iframeId)
			 */
			subscribeToCacheResults : function(onData, scope, onComplete, subscriberData) {

				if (typeof onData !== 'function') {
					throw new Error(
							qmatic.Errors.INVALID_ARGUMENT
									+ ". Please provide a function to use as callback for cache reads.");
				}
				var topic = QMATIC_TOPIC_PREFIX + 'cache.response.' + iframeId;
				return hubClient.subscribe(topic, onData, scope,
						onComplete, subscriberData);
			},

			/**
			 * Store a key value pair in the global app cache.
			 */
			putDataIntoAppCache : function(key, value) {
				var data = {
					'key':key,
					'value':value
				};
				var topic = qmatic.webwidget.topic.PUT_CACHE;
				return this.publish(topic, data);
			},

			/**
			 * Read a value from the global app cache
			 */
			getDataFromAppCache : function(key) {
				var data = {
					'key' : key,
					'clientId' : iframeId
				};
				
				var topic = qmatic.webwidget.topic.GET_CACHE;
				return this.publish(topic, data);
			},
			
			clearCache : function() {
			
				var data = {};
				var topic = qmatic.webwidget.topic.CLEAR_CACHE;
				return this.publish(topic, data);
			},

            /**
             * Asks the host application which page it's currently showing. (startpage etc.).
             * Any calls to this method relies on having called wwClient.subscribeToGetGetCurrentHostPageResults(...)
             * first to create the necessary subscription magic happen in the background.
             *
             * It's possible to listen to page switches from a widget on the topic
             * com.qmatic.qp.topic.event.SWITCH_HOST_PAGE_COMPLETE.
             */
            getCurrentHostPage : function() {
                var data = {
                    'clientId' : iframeId
                };

                var topic = qmatic.webwidget.topic.GET_CURRENT_PAGE;
                return this.publish(topic, data);
            },

            /**
             * Makes your widget listen to specific results posted to a "getCurrentHostPage" topic specific
             * for this widget. (Based on the iframeId). Setup the subscription with this method before calling getCurrentHostPage.
             *
             * @param {Function|String} onData
             *     Callback function that is invoked whenever an event is published on the topic.
             *     Takes four parameters name, msg, d, pcont where msg is the name of the host page:
             *     subscribeToGetCurrentHostPageResults(function(name, msg, d, pcont) {//do something with msg}), ...);
             * @param {Object} [scope]
             *     When onData callback is invoked, the JavaScript "this" keyword refers to this scope object.
             *     If no scope is provided, default is window.
             * @param {Function} [onComplete]
             *     Invoked to tell the client application whether the
             *     subscribe operation succeeded or failed.
             * @param {*} [subscriberData]
             *     Client application provides this data, which is handed
             *     back to the client application in the subscriberData
             *     parameter of the onData and onComplete callback functions.
             */
			subscribeToGetCurrentHostPageResults : function(onData, scope, onComplete, subscriberData) {
				if (typeof onData !== 'function') {
					throw new Error(
							qmatic.Errors.INVALID_ARGUMENT
									+ ". Please provide a function to use as callback for cache reads.");
				}
				var topic = QMATIC_TOPIC_PREFIX + 'currentpage.response.' + iframeId;
				return hubClient.subscribe(topic, onData, scope,
						onComplete, subscriberData);
			},

            widgetLoaded : function() {
                hubClient.publish(qmatic.webwidget.topic.WIDGET_LOADED, {
                    'identifier' : iframeId
                });
            }
		};

		initialise();

		return retObj;

	})();

	/**
	 * Helper class for parsing attributes
	 * Usage: 
	 *        var attributeParser = new qmatic.webwidget.AttributeParser(myAttributesFromCallback),
	 *            fontSize = attributeParser.getFontSize();
	 *        $('body').css('font-size', fontSize);
	 */
	qmatic.webwidget.AttributeParser = (function() {

		// private statics shared by all instances
		var FONT_FAMILY_INDEX = 0, FONT_SIZE_INDEX = 1, FONT_STYLE_INDEX = 2, FONT_WEIGHT_INDEX = 3, constructor;

		/**
		 * Note that our font format is [font-family];[font-size];[font-style];[font-weight]
		 * Example: Verdana,Helvetica,serif;40px;italic;bold
		 */
		function getFontData(self, key, fontIndex) {
			var fontData, fontArray;

			fontData = self._p.attributes[key];
			if (fontData) {

				// Remove any trailing ;                
				if (fontData.charAt(fontData.length - 1) == ";") {
					fontData = fontData.slice(0, -1);
				}

				fontArray = fontData.split(';');

				// Accept 2 to 4 parts (we require font-family;font-size
				if (fontArray.length >= 2 && fontArray.length > fontIndex) {
					return fontArray[fontIndex].trim();
				}
			}

			return '';
		}

		function processAttributes(attribs) {
			var retObj, anAttribute;

			if (attribs) {
				retObj = {};

				for ( var key in attribs) {
					if (attribs.hasOwnProperty(key)) {
						// We need to handle both the full-blown attributes map as well as a simple key->value defaults map.
						anAttribute = attribs[key];
						if (anAttribute.value) {
							retObj[anAttribute.key] = anAttribute.value;
						} else {
							retObj[key] = anAttribute;
						}
					}
				}
			}

			return retObj;
		}

		// AttributeParser constructor
		constructor = function(attribs) {
			var self = this;
			if (!(this instanceof qmatic.webwidget.AttributeParser)) {
				throw new Error(
						'Invalid type. Did you forget the "new" keyword for instantiation?');
			}

			if (!attribs) {
				throw new Error(
						'Invalid arguments. No attributes provided for attribute parser');
			}

			self._p = {
				attributes : processAttributes(attribs)
			};
		};

		/**
		 * Get value associated with provided key. If key is not found or if value is undefined or null,
		 * the provided defaultValue will be returned.
		 */
		constructor.prototype.getString = function(key, defaultString) {
			return ('' + (this._p.attributes[key] || ''))
					|| ('' + defaultString);
		};

		/**
		 * Returns a number from attributes. If not found or value is not a number, then return
		 * provided default value.
		 * 
		 * if(convertedValue == NaN || convertedValue == undefined || convertedValue === '') {
		        return defaultInteger * 1
		    }
		 */
		constructor.prototype.getInteger = function(key, defaultInteger) {
			var self = this, theValue, convertedValue;

			theValue = self._p.attributes[key];
			convertedValue = theValue * 1;
			if (convertedValue === 0) {
				return 0;
			}
			return convertedValue || defaultInteger * 1;
		};

		/**
		 * Returns a font's size from the specified font data. If font data was not found, or if
		 * there is an error with the font data string, return default size instead.
		 */
		constructor.prototype.getFontSize = function(key, defaultSize) {
			return getFontData(this, key, FONT_SIZE_INDEX) || defaultSize;
		};

		constructor.prototype.getFontFamily = function(key, defaultFamily) {
			return getFontData(this, key, FONT_FAMILY_INDEX) || defaultFamily;
		};

		constructor.prototype.getFontStyle = function(key, defaultStyle) {
			return getFontData(this, key, FONT_STYLE_INDEX) || defaultStyle;
		};

		constructor.prototype.getFontWeight = function(key, defaultWeight) {
			return getFontData(this, key, FONT_WEIGHT_INDEX) || defaultWeight;
		};

		constructor.prototype.getBoolean = function(key, defaultBool) {
			if (!this._p.attributes[key]) {
				return defaultBool;
			}
			return this._p.attributes[key] == 'true';
		};

		/**
		 * Constructs and returns absolute image URL associated with provided key. If key is not found or if value is undefined or null,
		 * the provided defaultValue will be returned.
		 */
		constructor.prototype.getImageUrl = function(key, defaultString) {
			var value = this._p.attributes[key];
			if (!value) {
				return defaultString;
			}

			var conf = qmatic.webwidget.client.getConfiguration();
			var url = conf.protocol + '//' + conf.host;
			url += (conf.port ? ':' + conf.port : '');
			url += conf.image_resource_path;
			url += ('' + (this._p.attributes[key] || ''))
					|| ('' + defaultString);
			return url;
		};

		constructor.prototype.getAudioUrl = function(key, defaultString) {
			var value = this._p.attributes[key];
			if (!value) {
				return defaultString;
			}

			var conf = qmatic.webwidget.client.getConfiguration();
			var url = conf.protocol + '//' + conf.host;
			url += (conf.port ? ':' + conf.port : '');
			url += conf.audio_resource_path;
			url += ('' + (this._p.attributes[key] || ''))
					|| ('' + defaultString);
			return url;
		};

		return constructor;

	})();

})();
