package edu.umich.soar.hpp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import sml.Agent;
import sml.Identifier;
import sml.Kernel;
import sml.StringElement;
import edu.umich.soar.hpp.HPPlan.PlanType;

/**
 * A script is an action sequence that is consistent with
 * a hierarchical-probabilistic plan (HPP).
 */
public class HPPScript {
	
	/**
	 * Supports read-only access to produced kb
	 * 
	 * @return produced kb
	 */
	public HPPKnowledgeBase getKB() {
		return myKB;
	}
	
	/**
	 * Supports read-only access to produced script
	 * 
	 * @return iterator to action sequence
	 */
	public Iterator<String> getScript() {
		return myScript.iterator();
	}
	
	/**
	 * Converts an HPPlan to input-link structures, recurses as necessary
	 * 
	 * @param plan input plan
	 * @param id input-link identifier to serve as root
	 */
	static private void planToSML(HPPlan plan, Identifier id) {		
		
		// action control scheme (sequence vs. set)
		id.CreateStringWME("type", plan.GetType().toString());
		
		// variables
		Identifier vars = id.CreateIdWME("vars");
		{
			// for each variable
			Iterator<HPPVariable> varIt = plan.GetVariables();
			while (varIt.hasNext()) {
				HPPVariable var = (HPPVariable) varIt.next();
				
				// basics (name, type)
				Identifier varId = vars.CreateIdWME("var");
				varId.CreateStringWME("name", var.getName());
				varId.CreateStringWME("type", var.getType());
				
				// if given, just add value; multi requires CPT
				if (var instanceof HPPGivenVariable) {
					varId.CreateStringWME("value", ((HPPGivenVariable) var).getValue());
				} else if (var instanceof HPPMultiVariable) {
					HPPMultiVariable multiVar = (HPPMultiVariable) var;
					
					Identifier cptId = varId.CreateIdWME("cpt");
					
					// default if supplied
					if (multiVar.getDefaultValue() != null) {
						cptId.CreateStringWME("default", multiVar.getDefaultValue());
					}
					
					// for each cpt entry
					Iterator<HPPCPTEntry> entryIt = multiVar.getCPT();
					while (entryIt.hasNext()) {
						HPPCPTEntry entry = (HPPCPTEntry) entryIt.next();
						
						Identifier entryId = cptId.CreateIdWME("entry");
						
						// for each condition
						Iterator<HPPConstraint> conditionIt = entry.getConditions();
						Identifier conditionId = null;
						while (conditionIt.hasNext()) {
							HPPConstraint condition = (HPPConstraint) conditionIt.next();
							
							// only add "conditions" id if at least one condition
							if (conditionId==null) {
								conditionId = entryId.CreateIdWME("conditions");
							}
							
							// condition name/value
							conditionId.CreateStringWME(condition.getName(), condition.getValue());
						}
						
						// associated value distribution
						Iterator<HPPCPTValue> valueIt = entry.getValues();
						Identifier valueId = null;
						while (valueIt.hasNext()) {
							HPPCPTValue value = (HPPCPTValue) valueIt.next();
							
							// only add "values" id if at least one value
							// note: kinda scary if no values...
							if (valueId==null) {
								valueId = entryId.CreateIdWME("values");
							}
							
							// value name/probability
							valueId.CreateFloatWME(value.getValue(), value.getProbability());
						}
					}
				}
			}
		}
			
		// actions (very different structure for sequence vs. set)
		{
			Identifier prevActionId = null;
			
			Iterator<HPPAction> actionIt = plan.GetActions();
			while (actionIt.hasNext()) {
				HPPAction action = (HPPAction) actionIt.next();
				
				Identifier actionId = null;
				if (plan.GetType()==PlanType.seq) {
					
					// sequence gets a linked list, rooted at (<plan> ^actions)
					if (prevActionId==null) {
						actionId = id.CreateIdWME("actions");
					} else {
						actionId = prevActionId.CreateIdWME("next");
					}
					
					// need to keep track of last parent
					prevActionId = actionId;
					
				} else if (plan.GetType()==PlanType.set) {
					
					// sets just point via a multi-valued attribute
					actionId = id.CreateIdWME("action");
					
				}
				
				// common (given the parent)
				actionId.CreateStringWME("name", action.getName());
				
				// for all conditions
				Iterator<HPPConstraint> conditionIt = action.getConditions();
				Identifier conditionId = null;
				while (conditionIt.hasNext()) {
					HPPConstraint condition = (HPPConstraint) conditionIt.next();
					
					// only add "conditions" if at least one
					if (conditionId==null) {
						conditionId = actionId.CreateIdWME("conditions");
					}
					
					// condition name/value
					conditionId.CreateStringWME(condition.getName(), condition.getValue());
				}
				
				// if no sub-plan, indicate as such; else, recurse!
				if (action.getSubPlan()==null) {					
					actionId.CreateStringWME("sub-plan", "nil");					
				} else {
					planToSML(action.getSubPlan(), actionId.CreateIdWME("sub-plan"));					
				}
			}
			
			// when done with actions, indicate as such
			if (prevActionId!=null) {
				prevActionId.CreateStringWME("next", "nil");
			}
		}
	}
	
	/**
	 * Creates the script given an HPPlan, Soar agent location, and optional random seed
	 * 
	 * @param plan script structure
	 * @param agentLocation file-system location of scripter.soar
	 * @param rngSeed random seed with which to initialize Soar's RNG, or null to ignore
	 */
	public HPPScript(HPPlan plan, String agentLocation, Integer rngSeed) {
		
		// initialize return data structures
		myKB = new HPPKnowledgeBase();
		myScript = new LinkedList<String>();
		
		// prepare soar kernel/agent
		Kernel kernel = Kernel.CreateKernelInNewThread();
		Agent agent = kernel.CreateAgent("scripter");
		
		// initialize random seed, if supplied
		if (rngSeed!=null) {
			agent.ExecuteCommandLine("srand " + rngSeed);
		}
		
		// attempt loading the supplied production rules
		if (!agent.LoadProductions(agentLocation)) {
			System.err.println(agent.GetLastErrorDescription());
			System.exit(1);
		}
		
		// the agent has example input-link rules that test for
		// the absense of (<il> ^real t), which we supply here
		Identifier inputLink = agent.GetInputLink();
		inputLink.CreateStringWME("real", "t");
		
		// recursive call to convert each plan to input-link structures
		planToSML( plan, inputLink.CreateIdWME("plan") );
		
		// run till completion (or badness)
		agent.RunSelfForever();
		
		// output occurs in batch at the end, and is quite simple
		// so if no output-link (i.e. no output), something went wrong
		if (agent.GetOutputLink()==null) {
			
			// output enough information to debug remotely
			System.out.println("Soar failed, port: "+agent.ExecuteCommandLine("port"));
			System.out.println("You can now connect remotely to debug; type something below and press enter to exit.");
			
			// block till user is ready
			try {
				new BufferedReader(new InputStreamReader(System.in)).readLine();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			// bail
			System.exit(1);
		}
		
		// at this point, gather output data (assume correct)
		Identifier kbId = agent.GetOutputLink().FindByAttribute("kb", 0).ConvertToIdentifier();
		Identifier scriptId = agent.GetOutputLink().FindByAttribute("script", 0).ConvertToIdentifier();
		
		// collect kb (i.e. variable assignments)
		{			
			for (int i=0; i<kbId.GetNumberChildren(); i++) {
				StringElement child = kbId.GetChild(i).ConvertToStringElement();
				if (child!=null) {
					myKB.addVariable(child.GetAttribute(), child.GetValue());
				}
			}
		}
		
		// collect script (i.e. action sequence)
		while (scriptId!=null)
		{
			String actionName = scriptId.FindByAttribute("name", 0).ConvertToStringElement().GetValue();
			if (actionName!=null) {
				myScript.add(actionName);
			}
			scriptId = scriptId.FindByAttribute("next", 0).ConvertToIdentifier();
		}
		
		// kill soar
		kernel.Shutdown();
	}
		
	private final HPPKnowledgeBase myKB;
	private final List<String> myScript;
}
