/**
 * Copyright (C) 2006 Aelitis, All Rights Reserved.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * AELITIS, SAS au capital de 63.529,40 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 *
 */

package com.aelitis.azureus.core.messenger;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.NumberFormat;
import java.util.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.core3.util.Timer;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.cnetwork.ContentNetwork;
import com.aelitis.azureus.core.cnetwork.ContentNetworkManagerFactory;
import com.aelitis.azureus.core.messenger.browser.BrowserMessage;
import com.aelitis.azureus.core.messenger.browser.BrowserMessageDispatcher;
import com.aelitis.azureus.core.messenger.browser.listeners.MessageCompletionListener;
import com.aelitis.azureus.core.messenger.config.PlatformRelayMessenger;
import com.aelitis.azureus.util.*;

import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader;
import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderException;
import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderFactory;

/**
 * @author TuxPaper
 * @created Sep 25, 2006
 *
 */
public class PlatformMessenger
{
	private static final boolean DEBUG_URL = System.getProperty(
			"platform.messenger.debug.url", "0").equals("1");

	private static final String URL_PLATFORM_MESSAGE = "?service=rpc";

	private static final String URL_POST_PLATFORM_DATA = "service=rpc";

	private static final int MAX_POST_LENGTH = 1024 * 512 * 3; // 1.5M

	private static boolean USE_HTTP_POST = true;

	public static String REPLY_EXCEPTION = "exception";

	public static String REPLY_ACTION = "action";

	public static String REPLY_RESULT = "response";

	/** Key: id of queue;  Value: Map of queued messages & listeners */
	static private Map<String, Map> mapQueues = new HashMap();

	private static final String QUEUE_AUTH = "Auth.";

	private static final String QUEUE_NOAZID = "noazid.";

	private static final String QUEUE_NORMAL = "msg.";

	static private AEMonitor queue_mon = new AEMonitor(
			"v3.PlatformMessenger.queue");

	static private Timer timerProcess = new Timer("v3.PlatformMessenger.queue");

	static private TimerEvent timerEvent = null;

	private static boolean initialized;

	private static fakeContext context;

	private static PlatformAuthorizedSender authorizedSender;

	private static boolean authorizedDelayed;

	public static synchronized void init() {
		if (initialized) {
			return;
		}
		initialized = true;

		// The UI will initialize this
		context = new fakeContext();
	}

	public static void setAuthorizedTransferListener(
			PlatformAuthorizedSender authorizedSender) {
		debug("set Authorized Sender");
		PlatformMessenger.authorizedSender = authorizedSender;
	}

	public static PlatformAuthorizedSender getAuthorizedTransferListener() {
		return PlatformMessenger.authorizedSender;
	}

	public static ClientMessageContext getClientMessageContext() {
		if (!initialized) {
			init();
		}
		return context;
	}

	public static void queueMessage(PlatformMessage message,
			PlatformMessengerListener listener) {
		queueMessage(message, listener, true);
	}

	public static void queueMessage(PlatformMessage message,
			PlatformMessengerListener listener, boolean addToBottom) {
		
		if ( COConfigurationManager.getStringParameter( "ui", "az3" ).equals( "az2" )){
			
			Debug.out( "**** PlatformMessenger shouldn't be used with az2 UI ****" );
		}
		
		if (!initialized) {
			init();
		}

		if (message != null) {
			debug("q cn" + message.getContentNetworkID() + " "
					+ message.toShortString() + ": " + message + " @ "
					+ new Date(message.getFireBefore()) + "; in "
					+ (message.getFireBefore() - SystemTime.getCurrentTime()) + "ms");
			if (message.requiresAuthorization() && authorizedDelayed) {
				debug("   authorized msg is delayed");
			}
		} else {
			debug("fire timerevent");
		}
		queue_mon.enter();
		try {
			long fireBefore;
			if (message != null) {
				long networkID = message.getContentNetworkID();
				if (networkID <= 0) {
					debug("Content Network invalid for " + message);
					return;
				}
				String queueID;
				if (message.requiresAuthorization()) {
					queueID = QUEUE_AUTH;
				} else if (!message.sendAZID()) {
					queueID = QUEUE_NOAZID;
				} else {
					queueID = QUEUE_NORMAL;
				}
				queueID += networkID;

				Map<PlatformMessage, PlatformMessengerListener> mapQueue = (Map) mapQueues.get(queueID);
				if (mapQueue == null) {
					mapQueue = new LinkedHashMap<PlatformMessage, PlatformMessengerListener>();
					mapQueues.put(queueID, mapQueue);
				}
				mapQueue.put(message, listener);

				fireBefore = message.getFireBefore();
			} else {
				fireBefore = SystemTime.getCurrentTime();
			}

			if (timerEvent == null || timerEvent.hasRun()) {
				timerEvent = timerProcess.addEvent(fireBefore,
						new TimerEventPerformer() {
							public void perform(TimerEvent event) {
								timerEvent = null;
								Object[] keys = mapQueues.keySet().toArray();
								for (int i = 0; i < keys.length; i++) {
									Map mapQueue = mapQueues.get(keys[i]);
									while (mapQueue != null && mapQueue.size() > 0) {
										processQueue(mapQueue);
									}
								}
							}
						});
			} else {
				// Move the time up if we have to
				try {
					if (fireBefore < timerEvent.getWhen()) {
						timerProcess.adjustAllBy(fireBefore - timerEvent.getWhen());
					}
				} catch (Exception e) {

				}
			}
		} finally {
			queue_mon.exit();
		}
	}

	/**
	 * @param string
	 */
	public static void debug(String string) {
		AEDiagnosticsLogger diag_logger = AEDiagnostics.getLogger("v3.PMsgr");
		diag_logger.log(string);
		if (ConstantsV3.DIAG_TO_STDOUT) {
			System.out.println(Thread.currentThread().getName() + "|"
					+ System.currentTimeMillis() + "] " + string);
		}
	}

	protected static void debug(String string, Throwable e) {
		debug(string + "\n\t" + e.getClass().getName() + ": " + e.getMessage()
				+ ", " + Debug.getCompressedStackTrace(e, 1, 80));
	}

	/**
	 * Sends the message almost immediately, skipping delayauthorization check 
	 * @param message
	 * @param listener
	 *
	 * @since 3.0.5.3
	 */
	public static void pushMessageNow(PlatformMessage message,
			PlatformMessengerListener listener) {
		debug("push " + message.toShortString() + ": " + message);

		Map map = new HashMap(1);
		map.put(message, listener);
		processQueue(map);
	}

	/**
	 * @param requiresAuthorization 
	 * 
	 */
	protected static void processQueue(Map mapQueue) {
		if (!initialized) {
			init();
		}

		final Map mapProcessing = new HashMap();

		boolean loginAndRetry = false;
		boolean requiresAuthorization = false;
		boolean sendAZID = true;
		long contentNetworkID = ContentNetwork.CONTENT_NETWORK_VUZE;

		// Create urlStem (or post data)
		// determine which server to use
		String server = null;
		StringBuffer urlStem = new StringBuffer();
		long sequenceNo = 0;

		queue_mon.enter();
		try {
			// add one at a time, ensure relay server messages are seperate
			boolean first = true;
			boolean isRelayServerBatch = false;
			for (Iterator iter = mapQueue.keySet().iterator(); iter.hasNext();) {
				PlatformMessage message = (PlatformMessage) iter.next();
				Object value = mapQueue.get(message);

				boolean isRelayServer = PlatformRelayMessenger.LISTENER_ID.equals(message.getListenerID());
				if (first) {
					requiresAuthorization = message.requiresAuthorization();
					sendAZID = message.sendAZID();
					isRelayServerBatch = isRelayServer;
					contentNetworkID = message.getContentNetworkID();
					first = false;
				} else if (isRelayServerBatch != isRelayServer) {
					break;
				}

				// build urlStem
				message.setSequenceNo(sequenceNo);

				StringBuffer urlStemSegment = new StringBuffer();
				if (sequenceNo > 0) {
					urlStemSegment.append('&');
				}

				String listenerID = message.getListenerID();
				String messageID = message.getMessageID();
				String params = message.getParameters().toString();
				try {
					urlStemSegment.append("cmd=");
					urlStemSegment.append(URLEncoder.encode(messageID, "UTF-8"));
					urlStemSegment.append(BrowserMessage.MESSAGE_DELIM_ENCODED);
					urlStemSegment.append(sequenceNo);
					urlStemSegment.append(BrowserMessage.MESSAGE_DELIM_ENCODED);
					urlStemSegment.append(URLEncoder.encode(listenerID, "UTF-8"));
					urlStemSegment.append(BrowserMessage.MESSAGE_DELIM_ENCODED);
					urlStemSegment.append(URLEncoder.encode(message.getOperationID(),
							"UTF-8"));
					urlStemSegment.append(BrowserMessage.MESSAGE_DELIM_ENCODED);
					urlStemSegment.append(URLEncoder.encode(params, "UTF-8"));
				} catch (UnsupportedEncodingException e) {
				}

				if (sequenceNo > 0
						&& urlStem.length() + urlStemSegment.length() > MAX_POST_LENGTH) {
					debug("breaking up batch at " + sequenceNo
							+ " because max limit would be exceeded (" + urlStem.length()
							+ " + " + urlStemSegment.length() + ")");
					break;
				}

				urlStem.append(urlStemSegment);
				String curServer = messageID + "-" + listenerID;
				if (server == null) {
					server = curServer;
				} else if (!server.equals(curServer)) {
					server = "multi";
				}

				PlatformMessengerListener listener = (PlatformMessengerListener) mapProcessing.get(message);
				if (listener != null) {
					listener.messageSent(message);
				}
				sequenceNo++;

				// Adjust lists
				mapProcessing.put(message, value);

				iter.remove();

				// split up ones that requre login and retry and ones that don't
				if (mapProcessing.size() == 1) {
					loginAndRetry = message.getLoginAndRetry();
				} else {
					if (loginAndRetry != message.getLoginAndRetry()) {
						break;
					}
				}
			}
		} finally {
			queue_mon.exit();
		}
		//debug("about to process " + mapProcessing.size());

		if (mapProcessing.size() == 0) {
			return;
		}

		if (server == null) {
			server = "default";
		}

		// Build base RPC url based on listener and server

		// one day all this URL hacking should be moved into the ContentNetwork...

		ContentNetwork cn = ContentNetworkManagerFactory.getSingleton().getContentNetwork(
				contentNetworkID);
		if (cn == null) {
			cn = ConstantsV3.DEFAULT_CONTENT_NETWORK;
		}

		String sURL_RPC;
		boolean isRelayServer = (PlatformRelayMessenger.MSG_ID + "-" + PlatformRelayMessenger.LISTENER_ID).equals(server);
		if (isRelayServer) {

			sURL_RPC = ContentNetworkUtils.getUrl(cn, ContentNetwork.SERVICE_RELAY_RPC);

		} else {
			sURL_RPC = ContentNetworkUtils.getUrl(cn, ContentNetwork.SERVICE_RPC)
					+ server;
		}

		// Build full url and data to send
		String sURL;
		String sPostData = null;
		if (USE_HTTP_POST || requiresAuthorization) {
			sURL = sURL_RPC;
			if (requiresAuthorization) {
				String sAuthUrl = ContentNetworkUtils.getUrl(cn,
						ContentNetwork.SERVICE_AUTH_RPC);
				if (sAuthUrl != null) {
					sURL = sAuthUrl;
				}
			}

			sPostData = URL_POST_PLATFORM_DATA + "&" + urlStem.toString();
			sPostData = cn.appendURLSuffix(sPostData, true, sendAZID);

			if (!requiresAuthorization) {
				if (DEBUG_URL) {
					debug("POST for " + mapProcessing.size() + ": " + sURL + "?"
							+ sPostData);
				} else {
					debug("POST for " + mapProcessing.size() + ": " + sURL);
				}
			}
		} else {
			sURL = sURL_RPC + URL_PLATFORM_MESSAGE + "&" + urlStem.toString();

			sURL = cn.appendURLSuffix(sURL, false, sendAZID);

			if (DEBUG_URL) {
				debug("GET: " + sURL);
			} else {
				debug("GET: " + sURL_RPC + URL_PLATFORM_MESSAGE);
			}
		}

		final String fURL = sURL;
		final String fPostData = sPostData;
		final boolean fLoginAndRetry = loginAndRetry;
		final boolean fReqAuth = requiresAuthorization;

		// proccess queue on a new thread
		AEThread2 thread = new AEThread2("v3.PlatformMessenger", true) {
			public void run() {
				try {
					processQueueAsync(fURL, fPostData, mapProcessing, fReqAuth,
							fLoginAndRetry);
				} catch (Throwable e) {
					if (e instanceof ResourceDownloaderException) {
						debug("Error while sending message(s) to Platform: " + e.toString());
					} else {
						debug("Error while sending message(s) to Platform", e);
					}
					for (Iterator iter = mapProcessing.keySet().iterator(); iter.hasNext();) {
						PlatformMessage message = (PlatformMessage) iter.next();
						PlatformMessengerListener l = (PlatformMessengerListener) mapProcessing.get(message);
						if (l != null) {
							try {
								HashMap map = new HashMap();
								map.put("text", e.toString());
								map.put("Throwable", e);
								l.replyReceived(message, REPLY_EXCEPTION, map);
							} catch (Throwable e2) {
								debug("Error while sending replyReceived", e2);
							}
						}
					}
				}
			}
		};
		thread.start();
	}

	/**
	 * @param mapProcessing 
	 * @param surl
	 * @throws Exception 
	 */
	protected static void processQueueAsync(String sURL, String sData,
			Map mapProcessing, boolean requiresAuthorization, boolean loginAndRetry)
			throws Exception {
		URL url;
		url = new URL(sURL);
		AzureusCore core = AzureusCoreFactory.getSingleton();
		final PluginInterface pi = core.getPluginManager().getDefaultPluginInterface();

		String s;
		if (requiresAuthorization && authorizedSender != null) {
			AESemaphore sem_waitDL = new AESemaphore("Waiting for DL");
			authorizedSender.startDownload(url, sData, sem_waitDL, loginAndRetry);
			sem_waitDL.reserve();
			s = authorizedSender.getResults();
			authorizedSender.clearResults();
		} else {
			if (requiresAuthorization) {
				debug("No Authorized Sender.. using non-auth request");
			}
			byte[] bytes = downloadURL(pi, url, sData);
			s = new String(bytes, "UTF8");
		}

		// Format: <sequence no> ; <classification> [; <results>] [ \n ]

		if (s == null || s.length() == 0 || !Character.isDigit(s.charAt(0))) {
			debug("Error while sending message(s) to Platform: reply: " + s
					+ "\nurl: " + sURL + "\nPostData: " + sData);
			for (Iterator iter = mapProcessing.keySet().iterator(); iter.hasNext();) {
				PlatformMessage message = (PlatformMessage) iter.next();
				PlatformMessengerListener l = (PlatformMessengerListener) mapProcessing.get(message);
				if (l != null) {
					try {
						HashMap map = new HashMap();
						map.put("text", "result was " + s);
						l.replyReceived(message, REPLY_EXCEPTION, map);
					} catch (Throwable e2) {
						debug("Error while sending replyReceived" + "\nurl: " + sURL
								+ "\nPostData: " + sData, e2);
					}
				}
			}
			return;
		}

		Map mapSeqToBrowserMsg = new HashMap();

		String[] replies = s.split("\\n");
		for (int i = 0; i < replies.length; i++) {
			String reply = replies[i];

			final String[] replySections = reply.split(BrowserMessage.MESSAGE_DELIM,
					3);
			if (replySections.length < 2) {
				continue;
			}
			long sequenceNo = NumberFormat.getInstance().parse(replySections[0]).longValue();

			Map actionResults = null;

			if (replySections.length == 3) {
				try {
					actionResults = JSONUtils.decodeJSON(replySections[2]);
				} catch (Throwable e) {
					debug("Error while sending message(s) to Platform: reply: " + s
							+ "\nurl: " + sURL + "\nPostData: " + sData, e);
				}
			}

			// Find PlatformMessage associated with sequence
			// TODO: There's a better way to do this
			PlatformMessage message = null;
			PlatformMessengerListener listener = null;
			for (Iterator iter = mapProcessing.keySet().iterator(); iter.hasNext();) {
				PlatformMessage potentialMessage = (PlatformMessage) iter.next();
				if (potentialMessage.getSequenceNo() == sequenceNo) {
					message = potentialMessage;
					listener = (PlatformMessengerListener) mapProcessing.get(message);
				}
			}

			if (message == null) {
				debug("No message with sequence number " + sequenceNo);
				continue;
			}

			debug("Got a " + reply.length() + " byte reply for "
					+ message.toShortString() + "\n\t\t"
					+ reply.substring(0, Math.min(8192, reply.length())));

			final PlatformMessage fMessage = message;
			final PlatformMessengerListener fListener = listener;
			final Map fActionResults = actionResults;

			// test
			if (i == 0 && false) {
				replySections[1] = "action";
				actionResults = new JSONObject();
				actionResults.put("retry-client-message", new Boolean(true));
				JSONArray a = new JSONArray();
				a.add("[AZMSG;1;display;open-url;{\"url\":\"http://yahoo.com\",\"width\":500,\"height\":200}]");
				actionResults.put("messages", a);
			}

			// Todo check array [1] for reply type

			if (replySections[1].equals("action")) {
				final BrowserMessageDispatcher dispatcher = context.getDispatcher();
				if (dispatcher == null) {
					debug("action requested.. no dispatcher");
				} else if (actionResults instanceof Map) {
					final boolean bRetry = MapUtils.getMapBoolean(actionResults,
							"retry-client-message", false);

					List array = (List) MapUtils.getMapObject(actionResults, "messages",
							null, List.class);
					if (actionResults.containsKey("messages")) {
						for (int j = 0; j < array.size(); j++) {
							final String sMsg = (String) array.get(j);
							debug("handling (" + ((bRetry) ? " with retry" : " no retry")
									+ "): " + sMsg);

							final BrowserMessage browserMsg = new BrowserMessage(sMsg);
							int seq = browserMsg.getSequence();
							BrowserMessage existingBrowserMsg = (BrowserMessage) mapSeqToBrowserMsg.get(new Long(
									seq));
							if (existingBrowserMsg != null) {
								existingBrowserMsg.addCompletionListener(new MessageCompletionListener() {
									public void completed(boolean success, Object data) {
										debug("got complete for " + sMsg);
										if (success) {
											queueMessage(fMessage, fListener);
										} else {
											if (fListener != null) {
												try {
													fListener.replyReceived(fMessage, replySections[1],
															fActionResults);
												} catch (Throwable e2) {
													debug("Error while sending replyReceived", e2);
												}
											}
										}
									}
								});
								continue;
							}

							if (bRetry) {
								mapSeqToBrowserMsg.put(new Long(seq), browserMsg);

								browserMsg.addCompletionListener(new MessageCompletionListener() {
									public void completed(boolean success, Object data) {
										debug("got complete for " + sMsg + ";" + success);
										if (success) {
											queueMessage(fMessage, fListener);
										} else {
											if (fListener != null) {
												try {
													fListener.replyReceived(fMessage, replySections[1],
															fActionResults);
												} catch (Throwable e2) {
													debug("Error while sending replyReceived", e2);
												}
											}
										}
									}
								});
							}

							new AEThread2("v3.Msg.Dispatch", true) {
								public void run() {
									dispatcher.dispatch(browserMsg);
								}
							}.start();
						}
					}
					if (bRetry) {
						continue;
					}
				}
			}

			if (listener != null) {
				try {
					listener.replyReceived(message, replySections[1], actionResults);
				} catch (Exception e2) {
					debug("Error while sending replyReceived", e2);
				}
			}
		}
		BrowserMessageDispatcher dispatcher = context.getDispatcher();
		if (dispatcher != null) {
			dispatcher.resetSequence();
		}
	}

	private static byte[] downloadURL(PluginInterface pi, URL url, String postData)
			throws Exception {
		ResourceDownloaderFactory rdf = pi.getUtilities().getResourceDownloaderFactory();

		ResourceDownloader rd = rdf.create(url, postData);

		rd = rdf.getRetryDownloader(rd, 3);
		// We could report percentage to listeners, but there's no need to atm
		//		rd.addListener(new ResourceDownloaderListener() {
		//		
		//			public void reportPercentComplete(ResourceDownloader downloader,
		//					int percentage) {
		//			}
		//		
		//			public void reportActivity(ResourceDownloader downloader, String activity) {
		//			}
		//		
		//			public void failed(ResourceDownloader downloader,
		//					ResourceDownloaderException e) {
		//			}
		//		
		//			public boolean completed(ResourceDownloader downloader, InputStream data) {
		//				return true;
		//			}
		//		});

		InputStream is = rd.download();

		byte data[];

		try {
			int length = is.available();

			data = new byte[length];

			is.read(data);

		} finally {

			is.close();
		}

		return (data);
	}

	private static class fakeContext
		extends ClientMessageContextImpl
	{
		private ContentNetwork contentNetwork = ConstantsV3.DEFAULT_CONTENT_NETWORK;

		private void log(String str) {
			if (System.getProperty("browser.route.all.external.stimuli.for.testing",
					"false").equalsIgnoreCase("true")) {

				System.err.println(str);
			}
			debug(str);
		}

		public fakeContext() {
			super("fakeContext", null);
		}

		public void deregisterBrowser() {
			log("deregisterBrowser");
		}

		public void displayBrowserMessage(String message) {
			log("displayBrowserMessage - " + message);
		}

		public boolean executeInBrowser(String javascript) {
			log("executeInBrowser - " + javascript);
			return false;
		}

		public Object getBrowserData(String key) {
			log("getBrowserData - " + key);
			return null;
		}

		public boolean sendBrowserMessage(String key, String op) {
			log("sendBrowserMessage - " + key + "/" + op);
			return false;
		}

		public boolean sendBrowserMessage(String key, String op, Map params) {
			log("sendBrowserMessage - " + key + "/" + op + "/" + params);
			return false;
		}

		public void setBrowserData(String key, Object value) {
			log("setBrowserData - " + key + "/" + value);
		}

		public boolean sendBrowserMessage(String key, String op, Collection params) {
			log("sendBrowserMessage - " + key + "/" + op + "/" + params);
			return false;
		}

		public void setTorrentURLHandler(torrentURLHandler handler) {
			log("setTorrentURLHandler - " + handler);
		}

		// @see com.aelitis.azureus.core.messenger.ClientMessageContext#getContentNetwork()
		public ContentNetwork getContentNetwork() {
			return contentNetwork ;
		}

		// @see com.aelitis.azureus.core.messenger.ClientMessageContext#setContentNetwork(com.aelitis.azureus.core.cnetwork.ContentNetwork)
		public void setContentNetwork(ContentNetwork contentNetwork) {
			this.contentNetwork = contentNetwork;
		}
	}

	/**
	 * @param b
	 *
	 * @since 3.0.5.3
	 */
	public static void setAuthorizedDelayed(boolean authorizedDelayed) {
		debug("setDelayAuthorized " + authorizedDelayed);
		PlatformMessenger.authorizedDelayed = authorizedDelayed;
		if (!authorizedDelayed) {
			boolean fireQueue = false;
			queue_mon.enter();
			try {
				for (String key : mapQueues.keySet()) {
					if (key.startsWith(QUEUE_AUTH)) {
						Map map = mapQueues.get(key);
						if (map != null && map.size() > 0) {
							fireQueue = true;
							break;
						}
					}
				}
			} finally {
				queue_mon.exit();
			}
			if (fireQueue) {
				queueMessage(null, null);
			}
		}
	}

	public static boolean isAuthorizedDelayed() {
		return authorizedDelayed;
	}
}
