package Planning;

import JavaAgent.resource.fopl.Literal;
import JavaAgent.resource.fopl.Substitution;
import JavaAgent.resource.fopl.UnificationException;

import java.io.*;
import java.util.*;

public class Plan implements Search.SearchState {

  public Plan() {
    theSteps.addElement(
        new Step(stepCount++, null, new Vector(), new Vector()));
    theSteps.addElement(
        new Step(stepCount++, null, new Vector(), new Vector()));
    theOrder.addStep((Step)theSteps.elementAt(0));
    theOrder.addStep((Step)theSteps.elementAt(1));
    theOrder.addBefore(
        (Step)theSteps.elementAt(0), (Step)theSteps.elementAt(1));
  }

  public Object clone() {
    Plan res = new Plan();
    res.theSteps = (Vector)theSteps.clone();
    res.theFlaws = (Vector)theFlaws.clone();
    res.theLinks = (Vector)theLinks.clone();
    res.theOrder = (Ordering)theOrder.clone();
    res.varBindings = varBindings;
    res.theOps = theOps;
    res.stepCount = stepCount;
    return res;
  }

  public boolean isGoalState() {
    return theFlaws.isEmpty();
  }

  public Vector getSuccessors() {
    Vector succs = new Vector();

    // select a flaw:
    Flaw cFlaw = (Flaw)theFlaws.firstElement();
    Literal cFlawLit = 
        (Literal)cFlaw.theStep.thePres.elementAt(cFlaw.preCondNr);

    // try existing steps:
    useExistingEffects(succs, cFlaw, cFlawLit);
    // try new operators:
    useNewOperators(succs, cFlaw, cFlawLit);

    return succs;
  }

  private void useExistingEffects(Vector succs, Flaw cFl, Literal cFlawL) {
    for (Enumeration se=theSteps.elements(); se.hasMoreElements(); ) {
      Step cStep = (Step)se.nextElement();
      for (Enumeration ee=cStep.theEffs.elements(); ee.hasMoreElements(); )
	tryEffect(succs, cFl, cFlawL, cStep, null, (Literal)ee.nextElement());
    }
  }

  private void useNewOperators(Vector succs, Flaw cFlaw, Literal cFlawL) {
    for (Enumeration oe=theOps.elements(); oe.hasMoreElements(); ) {
      Operator cOp = (Operator)((Operator)oe.nextElement()).clone();
      for (Enumeration ee=cOp.getEffects().elements(); ee.hasMoreElements(); )
	tryEffect(succs, cFlaw, cFlawL, null, cOp, (Literal)ee.nextElement());
    }
  }

  private void tryEffect(Vector succs, Flaw cFlaw, Literal cFlawLit, 
      Step cStep, Operator cOp, Literal cEffect) {
    if (cFlaw.theStep == cStep)
      return;
    try {
      Substitution newVB = (Substitution)varBindings.clone();
      if (cFlawLit.signedUnify(cEffect, newVB)) {
	Plan newPlan = (Plan)clone();
	newPlan.theFlaws.removeElement(cFlaw);
	newPlan.varBindings = newVB;
	if (cStep == null)
	  cStep = newPlan.addStep(cOp);
	if (! newPlan.theOrder.addBefore(cStep, cFlaw.theStep))
	  return;
	CausalLink nL = new CausalLink(
            cStep, cEffect, cFlawLit, cFlaw.theStep);
	newPlan.theLinks.addElement(nL);
	if (cOp == null)
	  newPlan.testAndAddEx(succs, nL, theSteps.size()-1);
	else 
	  newPlan.testAndAddNew(succs, nL, theLinks.size()-2);
      }
    } catch (JavaAgent.resource.fopl.UnificationException ue) {
      throw new UnknownError("Failed to unify flaw and effect!");
    }
  }

  public Step addStep(Operator cOp) {
    Step newStep = new Step(stepCount++, cOp, 
        cOp.getPreconditions(), cOp.getEffects());
    theSteps.addElement(newStep);
    theOrder.addStep(newStep);
    for (int pcn=newStep.thePres.size()-1; pcn>=0; pcn--)
      theFlaws.addElement(new Flaw(newStep, pcn));
    return newStep;
  }

  private void testAndAddNew(Vector succs, CausalLink l, int cli) 
      throws UnificationException {
    while (cli >= 0) {
      CausalLink cl = (CausalLink)theLinks.elementAt(cli--);
      if (l.theProd.clobbers(cl, this)) {
	Plan alt = (Plan)clone();
	if (alt.theOrder.addBefore(l.theProd, cl.theProd))
	  alt.testAndAddNew(succs, l, cli);
	if (theOrder.addBefore(cl.theCons, l.theProd))
	  testAndAddNew(succs, l, cli);
      }
    }
    testAndAddEx(succs, l, theSteps.size()-1);
  }

  private void testAndAddEx(Vector succs, CausalLink l, int si) 
      throws UnificationException {
    while (si >= 0) {
      Step s = (Step)theSteps.elementAt(si--);
      if (s.clobbers(l, this)) {
	Plan alt = (Plan)clone();
	if (alt.theOrder.addBefore(s, l.theProd))
	  alt.testAndAddEx(succs, l, si);
	if (theOrder.addBefore(l.theCons, s))
	  testAndAddEx(succs, l, si);
      }
    }
    succs.addElement(this);
  }

  public boolean equals(Object otherState) {
    return (this == otherState);
  }

  public void addOperator(Operator op) {
    theOps.addElement(op);
  }

  public void addInitial(Literal iLit) {
    ((Step)theSteps.elementAt(0)).theEffs.addElement(iLit);
  }

  public void addGoal(Literal oLit) {
    ((Step)theSteps.elementAt(1)).thePres.addElement(oLit);
    theFlaws.addElement(new Flaw((Step)theSteps.elementAt(1), 
        ((Step)theSteps.elementAt(1)).thePres.size()-1));
  }

  public String toString() {
    return "(PLAN Steps: " + theSteps + " Causal Links: " + theLinks + 
        " Orderings: " + theOrder + " Flaws: " + theFlaws + 
        " Variables: " + varBindings + ')';
  }

  protected Vector theSteps = new Vector();
  protected Vector theFlaws = new Vector();
  protected Substitution varBindings = new Substitution();
  protected Vector theLinks = new Vector();
  protected Ordering theOrder = new Ordering();

  private Vector theOps = new Vector();
  private int stepCount = 0;
}

class Step {
  Step(int n, Operator op, Vector pres, Vector effs) {
    stepNr = n; sType = op; thePres = pres; theEffs = effs;
  }
  boolean clobbers(CausalLink cl, Plan pl) throws UnificationException {
    if ((this == cl.theProd) || (this == cl.theCons) || 
	(pl.theOrder.before(cl.theCons, this, new Vector())) ||
        (pl.theOrder.before(this, cl.theProd, new Vector())))
      return false;
    for (Enumeration ee=theEffs.elements(); ee.hasMoreElements(); )
      if (cl.thePrec.signedUnify((Literal)ee.nextElement(), 
          (Substitution)pl.varBindings.clone()))
	return true;
    return false;
  }
  public int hashCode() {
    return stepNr;
  }
  public String toString() {
    return "(STEP-" + stepNr + " PreConds: " + thePres + 
        " Effects: " + theEffs + ')';
  }
  int stepNr;
  Operator sType;
  Vector thePres;
  Vector theEffs;
}

class Flaw {
  Flaw(Step s, int n) {
    theStep = s; preCondNr = n;
  }
  public String toString() {
    return "S" + theStep.stepNr + 'p' + preCondNr + ':' + 
        theStep.thePres.elementAt(preCondNr);
  }
  Step theStep;
  int preCondNr;
}

class Ordering {
  Ordering() {
    theOrd = new Hashtable();
  }
  public Object clone() {
    Ordering res = new Ordering();
    for (Enumeration ke=theOrd.keys(); ke.hasMoreElements(); ) {
      Object s = ke.nextElement();
      res.theOrd.put(s, ((Vector)theOrd.get(s)).clone());
    }
    return res;
  }
  void addStep(Step s) {
    theOrd.put(s, new Vector());
  }
  boolean addBefore(Step s1, Step s2) {
    if (s1 == s2)
      return false;
    if (before(s2, s1, new Vector()))
      return false;
    if (before(s1, s2, new Vector()))
      return true;
    Vector preS1s = getBefore(s1);
    preS1s.addElement(s1);
    for (Enumeration ps1e=preS1s.elements(); ps1e.hasMoreElements(); ) {
      Step ps1 = (Step)ps1e.nextElement();
      Vector ps1Succs = (Vector)theOrd.get(ps1);
      for (int si=ps1Succs.size()-1; si>=0; si--) {
	Step ps1Succ = (Step)ps1Succs.elementAt(si);
	if ((s2 == ps1Succ) || (before(s2, ps1Succ, new Vector())))
	  ps1Succs.removeElementAt(si);
      }
    }
    ((Vector)theOrd.get(s1)).addElement(s2);
    return true;
  }
  boolean before(Step s1, Step s2, Vector okSteps) {
    if (okSteps.contains(s1))
      return false;
    Vector s1Succs = (Vector)theOrd.get(s1);
    if (s1Succs.contains(s2))
      return true;
    for (Enumeration se=s1Succs.elements(); se.hasMoreElements(); )
      if (before((Step)se.nextElement(), s2, okSteps))
	return true;
    okSteps.addElement(s1);
    return false;
  }
  Vector getBefore(Step s2) {
    Vector res = new Vector();
    for (Enumeration s1e=theOrd.keys(); s1e.hasMoreElements(); ) {
      Step s1 = (Step)s1e.nextElement();
      if (before(s1, s2, new Vector()))
	res.addElement(s1);
    }
    return res;
  }
    
  public String toString() {
    String res = "[";
    for (Enumeration s1e=theOrd.keys(); s1e.hasMoreElements(); ) {
      Step s1 = (Step)s1e.nextElement();
      for (Enumeration s2e=((Vector)theOrd.get(s1)).elements(); 
          s2e.hasMoreElements(); )
	res += " S" + s1.stepNr + "<S" + ((Step)s2e.nextElement()).stepNr;
    }
    return res + ']';
  }

  Hashtable theOrd;
}

class CausalLink {
  CausalLink(Step s1, Literal ef, Literal pr, Step s2) {
    theProd = s1; theEffect = ef; thePrec = pr; theCons = s2;
  }
  public String toString() {
    return "S" + theProd.stepNr + "--" + thePrec + "->S" + theCons.stepNr;
  }
  Step theProd, theCons;
  Literal theEffect, thePrec;
}
