/* Copyright (c) 2008 Charles Rich and Worcester Polytechnic Institute.
 * All Rights Reserved.  Use is subject to license terms.  See the file
 * "license.terms" for information on usage and redistribution of this
 * file and for a DISCLAIMER OF ALL WARRANTIES.
 */
package edu.wpi.cetask;

import java.util.*;

import javax.script.*;

/**
 * Representation for instances of a decomposition class.
 */
public class Decomposition extends Instance {
   
   Decomposition (DecompositionClass type, Task goal) { 
      super(type, goal.engine);
      if ( goal == null ) 
         throw new IllegalArgumentException("Goal of decomposition cannot be null");
      this.goal = goal;
      bindings.put("$this", goal.bindings.get("$this"));
   }
   
   @Override
   public DecompositionClass getType() { 
      return (DecompositionClass) super.getType(); 
   }
   
   private final Task goal;
   public Task getGoal () { return goal; }
   
   private boolean failed;
   void setFailed (boolean failed) { this.failed = failed; }
   
   //TODO may be better to store plans here (and make subtype Plan.Step)
   //     to avoid needing to call Plan#childWithGoal
   
   private final Map<String,Step> steps = new HashMap<String,Step>();
   
   void putStep (String name, Step step) { 
      steps.put(name, step); 
      bindings.put('$'+name, step.bindings.get("$this"));
   }
   
   public Step getStep (String name) { return steps.get(name); }
   
   public Collection<Step> getSteps () { return steps.values(); }
   
   boolean stepsModified = true; // to reduce calls to updateBindings
   
   // per-decomposition flag used to prevent recursive calls to updateBindings
   // NOTE: this only works if there is only one thread running!
   private boolean updatingBindings;
   
   private void updateBindings (boolean modified) {
      if ( !failed && 
            (modified || stepsModified || goal.isModified()) && !updatingBindings ) 
         try {
            updatingBindings = true;
            getType().updateBindings(this);
            stepsModified = false;
            goal.setModified(false);
         } finally { updatingBindings = false; }
   }
   
   public class Step extends Task {
      
      private final String name; 
      
      public String getName () { return name; }
      
      Step (TaskClass type, String name) { 
         super(type, type.engine);
         this.name = name;
      }

      public Decomposition getDecomposition () { return Decomposition.this; }
      
      @Override
      public Object eval (String expression, String where) {
         updateBindings(false);
         return super.eval(expression, where);
      }
      
      @Override
      protected Object eval (String script, Bindings extra, String where) {
         updateBindings(false);
         return super.eval(script, extra, where);
      }
  
      @Override
      protected Boolean evalCondition (CompiledScript compiled, Bindings extra, 
                                       String where) {
         updateBindings(false);
         return super.evalCondition(compiled, extra, where);
      }
      
      // need these also since SCRIPTABLE optimization does not call eval 
    
      @Override
      public Object getSlotValue (String name) {
         updateBindings(false);
         return super.getSlotValue(name);
      }
      
      @Override
      public boolean isDefinedSlot (String name) {
         updateBindings(false);
         return super.isDefinedSlot(name);
      }
      
      @Override
      public boolean copySlotValue (Task from, String fromSlot, String thisSlot, 
                                    boolean onlyDefined) {
         if ( from instanceof Step ) ((Step) from).getDecomposition().updateBindings(false);
         boolean overwrite = super.copySlotValue(from, fromSlot, thisSlot, onlyDefined);
         updateBindings(true);
         return overwrite;
      }
      
      // only setting slot values sets modified bit; evaluation of conditions, etc.
      // is specified not to have side effects

      @Override
      public void setSlotValue (String name, Object value) {
         super.setSlotValue(name, value);
         updateBindings(true);
      }
      
      @Override
      public void setSlotValueScript (String name, String expression, String where) { 
         super.setSlotValueScript(name, expression, where);
         updateBindings(true);
      }
      
      @Override
      void setSlotValueScript (String name, String expression, String where,
            Bindings extra) {
         super.setSlotValueScript(name, expression, where, extra);
         updateBindings(true);
      }
      
      @Override
      void setSlotValueScript (String name, CompiledScript compiled, String where,
            Bindings extra) {
         super.setSlotValueScript(name, compiled, where, extra);
         updateBindings(true);
      }
      
      @Override
      public void deleteSlotValue (String name) {
         super.deleteSlotValue(name);
         updateBindings(true);
      }
   }
}

