
package com.necla.am.zwutils.Reflection;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.googlecode.mobilityrpc.controller.MobilityController;
import com.googlecode.mobilityrpc.network.ConnectionId;
import com.googlecode.mobilityrpc.session.MobilitySession;
import com.googlecode.mobilityrpc.session.impl.SessionClassLoader;
import com.necla.am.zwutils.Logging.GroupLogger;
import com.necla.am.zwutils.Logging.IGroupLogger;
import com.necla.am.zwutils.Misc.Misc;


public class RemoteClassLoaders {
	
	public static class viaMobilityRPC extends ClassLoader {
		
		protected static final String LogGroup = "ZWUtils.Reflection.RemoteClassLoaders.MobilityRPC";
		protected static final IGroupLogger CLog = new GroupLogger(LogGroup);
		
		public MobilitySession RPCSession;
		public final ConnectionId RPCConnection;
		
		protected int RetryCount = 3;
		
		protected static class RPCEndPoint {
			public final MobilityController Controller;
			public final InetSocketAddress RemoteAddr;
			
			public RPCEndPoint(MobilityController controller, InetSocketAddress remoteAddr) {
				Controller = controller;
				RemoteAddr = remoteAddr;
			}
			
			@Override
			public int hashCode() {
				return Controller.hashCode() ^ RemoteAddr.hashCode();
			}
			
			@Override
			public boolean equals(Object obj) {
				RPCEndPoint OtherEndPoint = (RPCEndPoint) obj;
				return (Controller == OtherEndPoint.Controller)
								&& RemoteAddr.equals(OtherEndPoint.RemoteAddr);
			}
			
		}
		
		protected static final Map<RPCEndPoint, viaMobilityRPC> RemoteMap = new ConcurrentHashMap<>();
		
		public static viaMobilityRPC Create(MobilityController RPCController,
				InetSocketAddress RemoteAddr) {
			RPCEndPoint EndPoint = new RPCEndPoint(RPCController, RemoteAddr);
			viaMobilityRPC Ret = RemoteMap.get(EndPoint);
			if (Ret == null) {
				Ret = new viaMobilityRPC(RPCController.newSession(), RemoteAddr);
				viaMobilityRPC Collision = RemoteMap.putIfAbsent(EndPoint, Ret);
				if (Collision != null) {
					Ret = Collision;
				}
			}
			return Ret;
		}
		
		public static void Cleanup(MobilityController RPCController) {
			Iterator<RPCEndPoint> Iter = RemoteMap.keySet().iterator();
			while (Iter.hasNext()) {
				RPCEndPoint EndPoint = Iter.next();
				if (EndPoint.Controller == RPCController) {
					CLog.Fine("Cleanning up MobilityRPC RemoteClassLoader %s:%d",
							EndPoint.RemoteAddr.getHostString(), EndPoint.RemoteAddr.getPort());
					Iter.remove();
				}
			}
		}
		
		protected viaMobilityRPC(MobilitySession Session, InetSocketAddress RemoteAddr) {
			super();
			
			RPCSession = Session;
			RPCConnection = new ConnectionId(RemoteAddr.getHostString(), RemoteAddr.getPort());
		}
		
		@Override
		public Class<?> findClass(String name) throws ClassNotFoundException {
			// Convert class name to resource name...
			String BinaryResource = name.replace('.', '/') + ".class";
			URL Binary = findResource(BinaryResource);
			if (Binary == null) throw new ClassNotFoundException(
					String.format("Unable to locate class '%s' from remote", name));
			
			try (InputStream BinaryStream = Binary.openStream()) {
				byte[] BinaryData = new byte[BinaryStream.available()];
				if (BinaryStream.read(BinaryData, 0, BinaryData.length) != BinaryData.length) {
					Misc.FAIL("Failed to complete receive class bytecode");
				}
				return defineClass(name, BinaryData, 0, BinaryData.length);
			} catch (IOException e) {
				Misc.CascadeThrow(e, "Unable to load class '%s'", name);
			}
			return null;
		}
		
		@Override
		public URL findResource(String name) {
			CLog.Fine("Remote loading resource '%s'...", name);
			int Trial = 0;
			while (Trial++ <= RetryCount) {
				try {
					SessionClassLoader RPCClassLoader = RPCSession.getSessionClassLoader();
					RPCClassLoader.setThreadLocalConnectionId(RPCConnection);
					return RPCClassLoader.getResource(name);
				} catch (IllegalStateException e) {
					CLog.logExcept(e, "Error loading remote resource '%s' (#%d) - %s", name, Trial,
							e.getLocalizedMessage());
					
					// Give up current session and get a new one
					MobilitySession NewSession = RPCSession.getMobilityController().newSession();
					RPCSession.release();
					RPCSession = NewSession;
				}
			}
			return null;
		}
		
		@Override
		public Enumeration<URL> findResources(String name) throws IOException {
			URL resourceUrl = findResource(name);
			return resourceUrl == null? Collections.enumeration(Collections
					.<URL> emptySet()) : Collections.enumeration(Collections.<URL> singleton(resourceUrl));
		}
		
	}
	
}

@@ -565,24 +565,67 @@
 assLoader;%0D%0A
+import com.necla.am.zwutils.GlobalConfig;%0D%0A
 import com.n
@@ -4312,21 +4312,13 @@
 Log.
-logExcept(e, 
+Warn(
 %22Err
@@ -4352,14 +4352,8 @@
 '%25s'
- (#%25d)
  - %25
@@ -4400,24 +4400,156 @@
 essage());%0D%0A
+%09%09%09%09%09if (GlobalConfig.DEBUG_CHECK) %7B%0D%0A%09%09%09%09%09%09CLog.logExcept(e);%0D%0A%09%09%09%09%09%7D%0D%0A%09%09%09%09%09CLog.Warn(%22Retrying %25d of %25d...%22, Trial, RetryCount);%0D%0A
 %09%09%09%09%09%0D%0A%09%09%09%09%09

Improve debug logging