package edu.neu.ccs.task.agent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.namespace.QName;
import edu.neu.ccs.task.Plan;
import edu.neu.ccs.task.Slot;
import edu.neu.ccs.task.Task;
import edu.neu.ccs.task.TaskEngine;
import edu.neu.ccs.task.TaskModelSet;
import edu.neu.ccs.task.TaskPlan;
import edu.neu.ccs.task.flash.FlashTranslator;
import edu.neu.ccs.task.flash.SpeechServerConfig;
import edu.neu.ccs.task.util.Bool;
import edu.neu.ccs.task.util.Decorator;
import edu.neu.ccs.task.util.Pair;
public class AgentSession {
private final Agent agent;
private final String top;
private final Map<String, String> topInputs;
private final SpeechServerConfig speechConfig;
private final List<OutputFilter> outputFilters;
protected final PrintStream out;
protected final BufferedReader in;
private boolean nullTermOutput = false;
public AgentSession(Agent agent,
String top,
Map<String, String> topInputs,
SpeechServerConfig speechConfig,
List<OutputFilter> outputFilters,
Socket socket) throws IOException {
this.agent = agent;
this.top = top;
this.topInputs = topInputs;
this.speechConfig = speechConfig;
this.outputFilters = new ArrayList<OutputFilter>(outputFilters);
this.out = new PrintStream(socket.getOutputStream());
this.in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
}
public void run() throws IOException {
String login = receive("<USER_LOGIN", "<CLIENT_LOGIN");
String user = parseAttrib("ID", login, null);
// autodetect flash-based clients, which use a speech server.
// If we've got one, we'll need to do 2 things different:
// 1) Use null-terminated output
// 2) Use a TTS server for all agent speech
if (login.startsWith("<CLIENT_LOGIN")) {
nullTermOutput = true;
outputFilters.add(new FlashTranslator(speechConfig));
}
initPlan(user);
send("<SESSION OK=\"true\"/>");
send("<PERFORM><PAGE URL=\"NONE\"/><PERFORM/>");
receive("<PERFORM_COMPLETE");
while (agent.isLive()) {
if (agent.nextTurn())
performTurn();
}
send("<PERFORM><DISPLAY CMD=\"HIDE\"/></PERFORM>");
receive("<PERFORM_COMPLETE");
send("<SESSION EXIT=\"SOMETHING\"/>");
}
private void initPlan(String user) {
TaskEngine engine = agent.getEngine();
TaskModelSet modelSet = engine.getModelSet();
QName topName = new QName(modelSet.getDefaultModel().getURI(), top);
TaskPlan topPlan = engine.newTaskPlan(topName);
Task topTask = topPlan.getTask();
for (Map.Entry<String, String> e : topInputs.entrySet())
topTask.setSlotValueScript(e.getKey(), e.getValue(), "init agent");
if (user != null) {
Slot userSlot = topTask.getType().getSlotIfExists("user");
if ((userSlot != null) &&
userSlot.isInput() &&
userSlot.getType().equals("string") &&
!topInputs.containsKey("user"))
topTask.setSlotValue("user", user);
}
if (Bool.isFalse(topTask.isApplicable()))
throw new IllegalArgumentException("task not applicable: " + topTask);
List<String> undefs = topTask.getUndefinedInputs();
if ( ! undefs.isEmpty())
throw new IllegalArgumentException("undefined inputs: " + undefs);
agent.setFocus(topPlan);
}
private void performTurn() throws IOException {
String output = agent.getCurrentAnnotatedOutput();
if (output != null)
speech(output);
List<Pair<String, Integer>> inputs = agent.getCurrentInputs();
if (inputs != null) {
while (true) {
int choice = menu(inputs);
if (choice < 0)
speech(agent.getCurrentAnnotatedAltOutput());
else {
agent.applyCurrentTurn(choice);
break;
}
}
} else
agent.applyCurrentTurn();
}
protected void speech(String msg) throws IOException {
for (OutputFilter of : outputFilters)
msg = of.process(msg);
send("<PERFORM>" + msg + "</PERFORM>");
receive("<PERFORM_COMPLETE");
}
protected int menu(List<Pair<String, Integer>> prompts) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("<PERFORM><MENU>");
for (Pair<String, Integer> p : prompts)
sb.append("<ITEM>" + p.first.trim() + "</ITEM>");
sb.append("</MENU></PERFORM>");
send(sb.toString());
String response = receive("<USER_INPUT MENU");
return prompts.get(parseInt("MENU", response)).second;
}
public String parseAttrib(String attrib, String input, String defaultVal) {
Matcher m = Pattern.compile(attrib + "=\"([^\"]+)\"").matcher(input);
if (m.find())
return m.group(1);
else
return defaultVal;
}
public String parseAttrib(String attrib, String input) {
String value = parseAttrib(attrib, input, null);
if (value == null)
throw new IllegalArgumentException("bad input: " + input);
return value;
}
public int parseInt(String attrib, String input) {
return Integer.parseInt(parseAttrib(attrib, input));
}
protected void send(String msg) {
out.println(msg);
if (nullTermOutput)
out.print((char) 0); // Flash requires null-terminated output
out.flush();
System.out.println("Sent: " + msg);
}
protected String receive(String... types) throws IOException {
while (true) {
String line = receive();
for (String type : types)
if (line.startsWith(type))
return line;
receiveUnexpected(line);
}
}
protected String receive() throws IOException {
String line = in.readLine();
if (line == null)
throw new RuntimeException("user disconnected");
// Flash sends null-byte terminated strings
// Because of this, we may see a null byte at the beginning of the
// next input. Strip it off...
if (line.charAt(0) == 0)
line = line.substring(1);
System.out.println("Received: " + line);
return line;
}
protected void receiveUnexpected(String msg) {
if (msg.startsWith("<DUMP_REQUEST TYPE=\"USER"))
dumpUserModel();
else if (msg.startsWith("<DUMP_REQUEST TYPE=\"PLAN"))
dumpPlan();
else if (msg.startsWith("<USER_EXIT"))
throw new RuntimeException("user quit");
}
private void dumpUserModel() {
dump("USER", agent.getEngine().dumpUserModel());
}
private void dumpPlan() {
StringWriter sw = new StringWriter();
Plan plan = agent.getTop();
if (plan != null)
plan.printTree(new PrintWriter(sw), true, new Decorator() {
public String decorate(Object o) {
return (o == agent.getFocus()) ? " <-focus" : "";
}
});
dump("PLAN", sw.toString());
}
private void dump(String type, String msg) {
send("<DUMP TYPE=\"" + type + "\">" +
msg.replaceAll("\\r(\\n?)", "\\\\n") +
"</DUMP>");
}
}