package nbtool.nio;
import java.awt.GridLayout;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javax.swing.JPanel;
import nbtool.data.json.JsonObject;
import nbtool.data.json.Json.JsonValue;
import nbtool.data.json.JsonParser.JsonParseException;
import nbtool.data.json.Json;
import nbtool.data.json.JsonArray;
import nbtool.data.log.Log;
import nbtool.gui.FlagPanel;
import nbtool.io.CommonIO;
import nbtool.io.CommonIO.GIOFirstResponder;
import nbtool.io.CommonIO.IOFirstResponder;
import nbtool.io.CommonIO.IOInstance;
import nbtool.io.CommonIO.IOState;
import nbtool.nio.LogRPC.RemoteCall;
import nbtool.util.Debug;
import nbtool.util.Debug.DebugSettings;
import nbtool.util.Events;
import nbtool.util.SharedConstants;
import nbtool.util.ToolSettings;
import nbtool.util.Utility;
public class RobotConnection extends IOInstance {
private static final DebugSettings debug = Debug.createSettings(true, true, true, Debug.EVENT, null);
private static final LinkedList<RobotConnection> instances = new LinkedList<>();
/* STATIC METHODS */
/* -------------------------------------------------- */
/* returns RobotConnection on success, null on failure */
public static RobotConnection connectToRobot(String hostName, IOFirstResponder responder) {
RobotConnection conn = new RobotConnection(hostName,
SharedConstants.ROBOT_PORT());
conn.ifr = responder;
if (conn.tryStart()) {
synchronized(instances) {
instances.add(conn);
}
return conn;
}
return null;
}
public static RobotConnection getByIndex(int index) {
synchronized(instances) {
if (index < instances.size())
return instances.get(index);
else return null;
}
}
public static RobotConnection getByHost(String host) {
synchronized(instances) {
for (RobotConnection si : instances) {
if (si.host.equals(host))
return si;
}
return null;
}
}
public static RobotConnection[] getAll() {
synchronized(instances) {
return instances.toArray(new RobotConnection[0]);
}
}
private static void remove(RobotConnection toRem) {
synchronized(instances) {
if (instances.contains(toRem))
instances.remove(toRem);
}
}
public static class RobotFlag {
public int index;
public String name;
public boolean value;
public RobotFlag(int i, String n, boolean v) {
this.index = i; this.name = n; this.value = v;
}
public RobotFlag(JsonObject obj) {
name = obj.get("name").asString().value;
index = obj.get("index").asNumber().asInt();
value = obj.get("value").asBoolean().bool();
}
public static RobotFlag[] parseLog(Log flags) {
assert(flags.logClass.equals(SharedConstants.LogClass_Flags()));
JsonArray fArray = null;
try {
fArray = flags.blocks.get(0).parseAsJson().asArray();
} catch (JsonParseException e) {
e.printStackTrace();
assert(false);
}
RobotFlag[] allFlags = new RobotFlag[fArray.size()];
for (int i = 0; i < allFlags.length; ++i)
allFlags[i] = new RobotFlag(fArray.get(i).asObject());
return allFlags;
}
}
/* INSTANCE STRUCTURE */
/* -------------------------------------------------- */
public void addControlCall(IOFirstResponder caller, String functionName, Log ... args) {
RemoteCall call = new RemoteCall(caller, functionName, args);
synchronized(calls) {
calls.add(call);
calls.notify();
}
}
private class RobotConnectionThreadBody implements Runnable {
private final RobotConnection conn;
private final boolean which;
protected RobotConnectionThreadBody(RobotConnection conn, boolean w)
{this.conn = conn; this.which = w;}
@Override
public void run() {
try {
if (which) conn.input();
else conn.output();
} catch (Exception e) {
debug.warn("RobotConnectionThreadBody %s of %s thread caught exception, killing...",
which ? "INPUT" : "OUTPUT", conn);
e.printStackTrace();
} finally {
debug.warn("RobotConnectionThreadBody %s of %s ending...",
which ? "INPUT" : "OUTPUT", conn);
conn.kill();
if (which) {
try {
outputThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
socket = null;
debug.info("RobotConnectionThreadBody INPUT of %s cleaning up...", conn);
conn.cleanup();
conn.finish();
remove(conn);
Events.GRobotConnectionStatus.generate(conn, false);
}
}
}
}
protected RobotConnection(String host, int port) {
this.host = host; this.port = port;
}
private Thread inputThread, outputThread;
private final RobotConnectionThreadBody inputBody =
new RobotConnectionThreadBody(this, true);
private final RobotConnectionThreadBody outputBody =
new RobotConnectionThreadBody(this, false);
private final LinkedList<RemoteCall> calls = new LinkedList<>();
private final Map<String, RemoteCall> pending = new HashMap<>();
private final Log heartbeat = Log.explicitLog(null, null,
SharedConstants.LogClass_Null(), 0);
public RobotFlag[] flags = null;
protected boolean tryStart() {
assert(socket == null);
assert(state() == IOState.STARTING);
try {
this.socket = CommonIO.setupNetSocket(host, port);
} catch(Exception e) {
debug.warn("connection attempt to %s:%d FAILED", host, port);
e.printStackTrace();
this.state = IOState.FINISHED;
return false;
}
assert(socket != null);
assert(socket.isConnected());
this.state = IOState.RUNNING;
inputThread = new Thread(inputBody);
inputThread.setDaemon(true);
inputThread.setName(this.toString() + "(INPUT THREAD)");
outputThread = new Thread(outputBody);
outputThread.setDaemon(true);
outputThread.setName(this.toString() + "(OUTPUT THREAD)");
inputThread.start();
outputThread.start();
Events.GRobotConnectionStatus.generate(this, true);
return true;
}
@Override
public void run() {
debug.error("run() called on RobotConnection, that's not how this works!");
assert(false);
}
private void input() throws Exception {
debug.info("%s input thread...", this);
assert(socket != null);
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
while (state() == IOState.RUNNING) {
Log input = Log.parseFromStream(bis);
if (input.version() != ToolSettings.VERSION) {
debug.warn("%s talking to robot of different version! tool:%d robot:%d",
ToolSettings.VERSION, input.version());
}
String lclass = input.logClass;
if (lclass.equals(SharedConstants.LogClass_Null())) {
debug.info("%s got heartbeat %d", this, input.createdWhen);
} else if (lclass.equals(SharedConstants.LogClass_RPC_Return())) {
String key = input.topLevelDictionary.get(SharedConstants.RPC_KEY()).asString().value;
if (pending.containsKey(key)) {
RemoteCall call = pending.remove(key);
if (call.callName().equals("GetFlags")) {
debug.info("RobotConnection piggy-backing flags from %s",
this.host);
this.flags = RobotFlag.parseLog(input.blocks.get(0).parseAsLog());
}
call.finish(this, input);
} else {
debug.error("%s got RPCReturn of %s key %s WITH NO MATCHING CALLS PENDING",
input.topLevelDictionary.get(SharedConstants.RPC_NAME()).asString().value,
key);
}
} else {
if (input.host_addr == null || input.host_addr.isEmpty() ||
input.host_addr.equals("n/a")) {
input.host_addr = this.host();
}
GIOFirstResponder.generateReceived(this, ifr, 0, input);
// debug.error("RobotConnection.input() not actually calling generateReceived yet!");
}
}
}
private void output() throws Exception {
debug.info("%s output thread...", this);
assert(socket != null);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
int timeout = (SharedConstants.REMOTE_HOST_TIMEOUT() / 1000) / 2;
while (state() == IOState.RUNNING) {
RemoteCall call = null;
synchronized(calls) {
if (calls.isEmpty()) {
calls.wait(timeout);
}
call = calls.pollFirst();
}
if (call == null) {
++heartbeat.createdWhen;
debug.info("%s sending heartbeat %d...", this, heartbeat.createdWhen);
heartbeat.writeTo(bos);
bos.flush();
} else {
debug.warn("%s sending %s...", this, call.callName());
pending.put(call.key, call);
call.call.writeTo(bos);
call.call = null;
bos.flush();
}
}
}
private void cleanup() {
for (RemoteCall call : calls) {
GIOFirstResponder.generateFinished(this, call.caller);
}
calls.clear();
for (RemoteCall call : pending.values()) {
GIOFirstResponder.generateFinished(this, call.caller);
}
pending.clear();
GIOFirstResponder.generateFinished(this, ifr);
ifr = null;
}
@Override
public String name() {
return String.format("RobotConnection(%s,%d)", host, port);
}
public static void main(String[] _ARGS_) throws InterruptedException {
Debug.warn("RobotConnection stand-alone main...");
RobotConnection conn = RobotConnection.connectToRobot("127.0.0.1", new IOFirstResponder(){
@Override
public void ioFinished(IOInstance instance) {
Debug.print("got ioFinished: %s", instance);
}
@Override
public void ioReceived(IOInstance inst, int ret, Log... out) {
Debug.print("got ioReceived: %s", inst);
}
@Override
public boolean ioMayRespondOnCenterThread(IOInstance inst) {
return false;
}
});
Debug.print("running...");
for(;;) Thread.sleep(1000);
}
}