package customsocket;

import java.net.InetSocketAddress;


import com.corundumstudio.socketio.AckCallback;
import com.corundumstudio.socketio.SocketIOClient;


import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.objects.collections.List;
import anywheresoftware.b4a.objects.collections.Map;
import io.socket.engineio.client.Socket;


/**
 * NettySocketWrapper dipakai di class jSocketIOServer
 * sebagai client yang connected ke jSocketIOServer
 * @author rdkartono
 *
 */
@BA.ShortName("NettySocketWrapper")
@BA.Events(values= {
			"log(msg as string)"
	})
public class NettySocketWrapper implements SocketIOClientInterface {

	private final SocketIOClient _socket;
	
	private final String _id;
	private final String _namespace;
	private final String _remote;
	private final String _local;
	private final boolean _isvalid;
	
	private final Object Me = this;
	private BA bax;
	private Object callerobject;
	private String event;
	private boolean _inited = false;
	private boolean _connected = false;
	
	private boolean need_log_event = false;
	
	
	
	public NettySocketWrapper() {
		// gak ngapa-ngapain, nyediain B4X doang
		_socket = null;
		
		_id = "";
		_namespace = "";
		_remote = "N/A";
		_isvalid = false;
		_local = "N/A";
		_connected = false;
		
		
	}
	
	@BA.Hide
	public NettySocketWrapper(SocketIOClient sock) {
		_socket = sock;
		if (_socket instanceof SocketIOClient) {
			
			_id = _socket.getSessionId().toString();
			_namespace = _socket.getNamespace().getName();
			InetSocketAddress xx = _socket.getHandshakeData().getAddress();
			_remote = xx.getHostString()+":"+xx.getPort();
			InetSocketAddress yy = _socket.getHandshakeData().getLocal();
			_local = yy.getHostString()+":"+yy.getPort();
			_isvalid = true;
			_connected = true;
			
		} else {
			_isvalid = false;
			_id = "";
			_remote = "N/A";
			_local = "N/A";
			_namespace ="";
			_connected = false;
			
			
		}
		
	}
	
	/**
	 * Setup Events
	 * @param caller : caller object
	 * @param eventname : event name
	 */
	public void SetupEvent(BA ba, Object caller, String eventname) {
		bax = ba;
		callerobject = caller;
		event = eventname;
		if (bax instanceof BA) {
			if (callerobject!=null) {
				if (event instanceof String) {
					if (!event.isEmpty()) {
						need_log_event = bax.subExists(event+"_log");
						_inited = true;
						if (need_log_event) raise_log("Will raise log event");
					}
				}
			}
		}
	}
	
	/**
	 * Check if Event is Initialized
	 * @return true if initialized
	 */
	public boolean EventInited() {
		return _inited;
	}
	
	@Override
	public void Disconnect() {
		if (_isvalid) {
			_socket.disconnect();
		}
		_connected = false;
	}
	
	@Override
	public boolean IsConnected() {
		return _connected;
	}
	
	@Override
	public String getID() {
		return _id;
	}
	
	@Override
	public String getRemote() {
		return _remote;
	}
	
	@Override
	public String getLocal() {
		return _local;
	}
	
	/**
	 * Get Namespace in String
	 * @return Namespace
	 */
	public String getNamespace() {
		return _namespace;
	}
	
	/**
	 * Description string in format ID=[ID], Remote=[Remote], Namespace=[Namespace]
	 * @return Description String
	 */
	public String CompleteDescription() {
		return "ID="+getID()+", Remote="+getRemote()+", Namespace="+getNamespace();
	}
		
	/**
	 * Emit List to target socket
	 * @param emitname : event name
	 * @param value : value in B4X List Object
	 * @param showtype : if true, then will show each object type of List members
	 * @return true if can be send
	 */
	public boolean Emit_List(String emitname, List value, boolean showtype) {
		if (value instanceof List) {
			if (value.IsInitialized()) {
				
				int valuesize = value.getSize();
				if (valuesize>0) {
					return Emit(emitname, value.getObject());
					
				}
			}
		}
		return false;
	}
	
	/**
	 * Emit a command with List value to target socket, but expect return value
	 * <br/>will raise event <b>emitname_complete(valueback as Object)</b>
	 * <br/>Must check valueback type using <b>GetType(valueback)</b> or using <b>If (valueback Is ...) Then</b> 
	 * @param emitname emint name
	 * @return true if can be emitted
	 */
	public boolean Emit_List_withCallback(final BA ba,String emitname, List value) {
	
		if (emitname != null && emitname.length()>0) {
			final String callbackevent = emitname+"_complete";
			if (_connected) {
				_socket.sendEvent(emitname, new AckCallback<Object>(Object.class,5000) {

					@Override
					public void onSuccess(Object result) {
						ba.raiseEventFromDifferentThread(Me, null, 0, callbackevent, false, new Object[] {result});
					}
					
				}, value.getObject());
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Emit Map to target socket
	 * @param emitname : event name
	 * @param value : value in B4X Map object
	 * @return true if can be send
	 */
	public boolean Emit_Map(String emitname, Map value) {
		if (value instanceof Map) {
			if (value.IsInitialized()) {
				
				int valuesize = value.getSize();
				if (valuesize>0) {
					return Emit(emitname, value.getObject());
					
				}
			}
		}
		return false;
	}
	
	/**
	 * Emit a command with Map value to target socket, but expect return value
	 * <br/>will raise event <b>emitname_complete(valueback as Object)</b>
	 * <br/>Must check valueback type using <b>GetType(valueback)</b> or using <b>If (valueback Is ...) Then</b> 
	 * @param emitname emint name
	 * @return true if can be emitted
	 */
	public boolean Emit_Map_withCallback(final BA ba,String emitname, Map value) {
		if (emitname != null && emitname.length()>0) {
			final String callbackevent = emitname+"_complete";
			if (_connected) {
				
				_socket.sendEvent(emitname, new AckCallback<Object>(Object.class, 5000) {

					@Override
					public void onSuccess(Object result) {
						ba.raiseEventFromDifferentThread(Me, null, 0, callbackevent, false, new Object[] {result});
					}
					
				}, value.getObject());
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Emit a command without value to target socket
	 * @param emitname : event name
	 * @return true if can be send
	 */
	public boolean Emit_noValue(String emitname) {
		return Emit(emitname);
	}
	
	/**
	 * Emit a command with no value to target socket, but expect return value
	 * <br/>will raise event <b>emitname_complete(valueback as Object)</b>
	 * <br/>Must check valueback type using <b>GetType(valueback)</b> or using <b>If (valueback Is ...) Then</b> 
	 * @param emitname emint name
	 * @return true if can be emitted
	 */
	public boolean Emit_noValue_withCallback(final BA ba,String emitname) {
		if (emitname != null && emitname.length()>0) {
			final String callbackevent = emitname+"_complete";
			if (_connected) {
				_socket.sendEvent(emitname, new AckCallback<Object>(Object.class,5000) {

					@Override
					public void onSuccess(Object result) {
						ba.raiseEventFromDifferentThread(Me, null, 0, callbackevent, false, new Object[] {result});
					}
					
				}, new Object[0]);
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Emit a String to target socket
	 * @param emitname : event name
	 * @param value : String to send
	 * @return true if can be send
	 */
	public boolean Emit_String(String emitname, String value) {
		if (value instanceof String) {
			if (!value.isEmpty()) {
				return Emit(emitname, value);
			}
		}
		return false;
	}
	
	/**
	 * Emit a command with String value to target socket, but expect return value
	 * <br/>will raise event <b>emitname_complete(valueback as Object)</b>
	 * <br/>Must check valueback type using <b>GetType(valueback)</b> or using <b>If (valueback Is ...) Then</b> 
	 * @param emitname emint name
	 * @return true if can be emitted
	 */
	public boolean Emit_String_withCallback(final BA ba,String emitname, String value) {
		if (emitname != null && emitname.length()>0) {
			final String callbackevent = emitname+"_complete";
			if (_connected) {
				_socket.sendEvent(emitname, new AckCallback<Object>(Object.class,5000) {

					@Override
					public void onSuccess(Object result) {
						ba.raiseEventFromDifferentThread(Me, null, 0, callbackevent, false, new Object[] {result});
					}
					
				}, value);
				return true;
			}
		}
		return false;
	}
	

	@Override
	public boolean Emit(String emitname, Object... value) {
		if (_connected) {
			_socket.sendEvent(emitname, value);
			return true;
		}
		return false;
	}
	
	/**
	 * Emit a command with Object value to target socket, but expect return value
	 * <br/>will raise event <b>emitname_complete(valueback as Object)</b>
	 * <br/>Must check valueback type using <b>GetType(valueback)</b> or using <b>If (valueback Is ...) Then</b> 
	 * @param emitname emint name
	 * @return true if can be emitted
	 */
	public boolean Emit_withCallback(final BA ba,String emitname, Object... value) {
		if (emitname != null && emitname.length()>0) {
			final String callbackevent = emitname+"_complete";
			if (_connected) {
				_socket.sendEvent(emitname, new AckCallback<Object>(Object.class,5000) {

					@Override
					public void onSuccess(Object result) {
						ba.raiseEventFromDifferentThread(Me, null, 0, callbackevent, false, new Object[] {result});
					}
					
				}, value);
				return true;
			}
		}
		return false;
	}
	
	@Override
	public boolean SendMessage(String value) {
		return Emit(Socket.EVENT_MESSAGE, value);		
	}
	
	
	private void raise_log(String msg) {
		if (need_log_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
	}

	@SuppressWarnings("unused")
	private void printlog(String... strings) {
		if (strings!=null) {
			if (strings.length==1) {
				BA.Log(strings[0]);
			} else {
				StringBuilder str = new StringBuilder();
				for(String xx : strings) str.append(xx);
				BA.Log(str.toString());
			}
		}
	}
}
