// (c) MIT 2003.  All rights reserved.

// -*-Mode: Java-*- 
// $Id: JTRE.java,v 1.7 2005/02/17 21:40:55 moltmans Exp $
// JTRE.java -- 
// Author: Michael Oltmans <moltmans@ai.mit.edu> 
// Created: <Sun Apr 30 01:56:09 2000> 
// Time-stamp: <2005-02-16 13:59:43 moltmans> 
package rec.tms;

import java.util.*;
import rec.util.*;

public class JTRE
{
  private String m_title;
  /** Map type strings to DBClasses **/
  private Hashtable m_classTable = new Hashtable();
  private JTMS m_jtms;
  /** Count the number of rules run so far. **/
  private int m_rulesRun;
  /** The rule queue **/
  private LinkedList m_ruleQueue;
  private LinkedList m_userRules;

  //  private boolean m_debugging = true;
  private boolean m_debugging = false;
  

  public JTRE( String title ) 
  {
    m_title = title;
    m_classTable = new Hashtable();
    m_jtms = new JTMS( "JTMS of " + title );
    Thunk enqueueThunk = new Thunk() {
	public void run( Object obj ) 
	{
	  RuleApplication ra = (RuleApplication)obj;
	  enqueueRule( ra );
	}
      };
    m_jtms.setEnqueueProc( enqueueThunk );
    m_rulesRun = 0;
    m_userRules = new LinkedList();
    m_ruleQueue = new LinkedList();
  }

  /**
   * Assert the fact into the JTRE.
   * @param data  The fact to assert.
   * @param justs List of Facts that justify the given fact.
   **/
  public void myassert( Fact fact, Object informant,
		      LinkedList justs )
  {
    Datum datum = referent( fact, true );
    Node node = datum.getNode();

    if( justs == null )
      justs = new LinkedList();

    debugging( "    Asserting " + fact + ":" );

    // look up the datums for each antecedent fact
    LinkedList datums = new LinkedList();
    Iterator it = justs.iterator();
    while( it.hasNext() ) {
      Fact f = (Fact)it.next();
      datums.addLast( referent( f, true ).getNode() );
      debugging( "      via: " + f );
    }
    
    // Create justification link between fact and antecendents
    node.justify( informant, datums );
  }

  public void assume( Fact fact, Object reason )
  {
    Datum datum = referent( fact, true );
    Node node = datum.getNode();

    if( !datum.isAssumption() ) {
      datum.setAssumption( reason );
      debugging( "    Assuming " + fact + " via " + reason + "." );
      node.assume();
    }
    else if( !reason.equals( datum.getAssumption() ) ) {
      Assert.error( "Fact " + datum.show() + " assumed because of " +
		    datum.getAssumption()  + " assumed again because of " +
		    reason );
    }
    //    return datum;
  }

  /**
   * Retract an assumption from JTRE
   **/
  public void retract( Fact fact, Object just )
  {
    boolean debug = true;
    
    Datum datum = referent( fact, true );
    Node node = datum.getNode();

    if( !node.isAssumption() ) {
      if( debug ) {
	debugging( datum.show() + " isn't an assumption." );
      }
    }
    else if( !node.isIn() ) {
      if( debug ) {
	debugging( "The asumption " + fact + " is not currently in" );
      }
    }
    else if( just.equals( datum.getAssumption() ) ) {
      debugging( "    Retracting " + fact + " via " + just + "." );
      datum.setAssumption( null );
      node.retractAssumption();
    }
    else if( debug ) {
      debugging( just + " not source of assumption for " + fact );
    }

    //    return node;
  }

  /** Make fact into a contradiction. **/
  public void contradiction( Fact fact )
  {
    Datum datum = referent( fact, true );
    Node node = datum.getNode();

    node.makeContradiction();
  }


  JTMS getJTMS()
  {
    return m_jtms;
  }

  public String getTitle() 
  {
    return m_title;
  }

  ////////////////////////////////////////////////////////////////////
  // Rule handling

  /** Try all rules that are relevant to the given datum. **/
  void tryRules( Datum datum ) 
  {
    Iterator it = datum.getDBClass().getRules().iterator();

    while( it.hasNext() ) {
      InternalRule rule = (InternalRule)it.next();
      tryRule( rule, datum );
    }
  }

  /**
   * Try to match rule on datum, if it suceeds then queue up the rule
   * so its body will be run.
   **/
  private boolean tryRule( InternalRule rule, Datum datum )
  {
    if( rule.match( datum ) ) {
      enqueueRule( rule, datum );
      return true;
    }
    return false;
  }

  /**
   * Run the rules that have matched.  They may not fire but they are
   * all run until there are no more rules to run.
   **/
  public void runRules() 
  {
    int counter = 0;
    while( !m_ruleQueue.isEmpty() ) {
      RuleApplication ra = (RuleApplication)m_ruleQueue.removeFirst();
      ra.apply();
      counter++;
    }
    debugging( "    Ran " + counter + " rules." );
    m_rulesRun += counter;
  }

  /** Run rule on datum in the next round **/
  void enqueueRule( InternalRule rule, Datum datum ) 
  {
    enqueueRule( new RuleApplication( rule, datum ) );
  }

  /** Run rule on datum in the next round **/
  void enqueueRule( RuleApplication ra )
  {
    m_ruleQueue.addFirst( ra );
  }
  

  /** @return true if there are rules waiting to run **/
  public boolean rulesWaiting()
  {
    return !m_ruleQueue.isEmpty();
  }

  ////////////////////////////////////////////////////////////////////
  // Finding existing datum objects for Facts

  private Datum referent( Fact fact ) 
  {
    return referent( fact, false );
  }

  /**
   * @return Datum object that corresponds to the given Fact.
   * @param isVirtual if true create a new Datum object if needed.
   **/
  private Datum referent( Fact fact, boolean isVirtual )
  {
    if( isVirtual ) {
      return addFact( fact );
    }
    else {
      return referent1( fact );
    }
  }
  
  
  private Datum referent1( Fact fact )
  {
    DBClass dbclass = getDBClass( fact.getFactType() );
    Iterator it = dbclass.getFacts().iterator();
    while( it.hasNext() ) {
      Datum datum = (Datum)it.next();
      if( datum.getFact().sameFact( fact ) ) {
	return datum;
      }
    }
    return null;
  }

  DBClass getDBClass( String name )
  {
    DBClass dbclass = (DBClass)m_classTable.get( name );
    if( dbclass == null ) {      
      dbclass = new DBClass( name );
      m_classTable.put( name, dbclass );
    }
    return dbclass;
  }

  ////////////////////////////////////////////////////////////////////
  // Adding to the JTRE
  
  /**
   * Insert a single rule into a dbclass and run it on existing facts.
   **/
  public void insertRule( Rule rule )
  {
    m_userRules.add( rule );
    InternalRule ir = new InternalRule( this, rule, new LinkedList(),
					rule.getTriggers() );
    insertRule( ir );
  }
  
  public void insertRule( InternalRule rule )
  {
    // add it to each class that it triggers off of
    Iterator dbclasses = rule.getDBClasses().iterator();
    while( dbclasses.hasNext() ) {
      DBClass dbclass = (DBClass)dbclasses.next();
      dbclass.addRule( rule );

      Iterator it = dbclass.getFacts().iterator();
      while( it.hasNext() ) {
	Datum fact = (Datum)it.next();
	tryRule( rule, fact );
      }
    }
  }

  Datum addFact( Fact fact ) 
  {
    Datum datum = referent1( fact );

    if( datum != null ) {
      return datum;
    }
    
    datum = new Datum( this, fact );
    // add the fact to the appropriate dbclass
    datum.getDBClass().addFact( datum );
    tryRules( datum );
    return datum;
  }

  ////////////////////////////////////////////////////////////////////
  // Query the DB about status of facts and their support:

  public boolean isIn( Fact fact ) 
  {
    Datum datum = referent( fact );
    if( datum != null ) {
      return datum.getNode().isIn();
    }
    return false;
  }

  public boolean isOut(Fact fact ) 
  {
    Datum datum = referent( fact );
    if( datum != null ) {
      return datum.getNode().isOut();
    }
    return false;
  }

  /**
   * @param factType The type of the facts to retrieve
   * @return List of Datums that are of the given type
   **/
  public List fetchDatums( String factType ) 
  {
    return getDBClass( factType ).getFacts();
  }

  /**
   * @param factType The type of the facts to retrieve
   * @return List of Facts that are of the given type
   **/
  public List fetchFacts( String factType ) 
  {
    return Datum.datumsToFacts( getDBClass( factType ).getFacts() );
  }

  /**
   * @param factType The type of the facts to retrieve
   * @return List of Facts that are of the given type and that are
   *         currently believed.
   **/
  public List fetchInFacts( String factType ) 
  {
    return Datum.datumsToFacts( getDBClass( factType ).getInFacts() );
  }

  ////////////////////////////////////////////////////////////////////
  // Debugging and printing:

  void debugging( String note ) 
  {
    if( m_debugging ) {
      System.out.println( note );
    }
  }

  public String factsToString() 
  {
    StringBuffer result = new StringBuffer();
    result.append( "Facts for " + this + ":\n" );
    int count = 1;
    
    Iterator it = m_classTable.values().iterator();
    while( it.hasNext() ) {
      DBClass dbclass = (DBClass)it.next();
      Iterator facts = dbclass.getFacts().iterator();
      while( facts.hasNext() ) {
	Datum d = (Datum)facts.next();
	if( d.getNode().isIn() )
	  result.append( "   IN: " );
	else
	  result.append( "  OUT: " );
	result.append( (count++) + ": " + d.getFact() + "\n");
      }
    }
    return result.toString();
  }

  /** @return a list of datum objects.  **/
  public LinkedList getAllDatums() 
  {
    LinkedList list = new LinkedList();

    Iterator it = m_classTable.values().iterator();
    while( it.hasNext() ) {
      DBClass dbclass = (DBClass)it.next();
      list.addAll( dbclass.getFacts() );
    }
    return list;
  }

  public String rulesToString() 
  {
    StringBuffer result = new StringBuffer();
    result.append( "Rules for " + this + ":" );
    int count = 1;
    
    Iterator it = m_userRules.iterator();
    while( it.hasNext() ) {
      Rule r = (Rule)it.next();
      result.append( "\n   " + r );
    }
    return result.toString();
  }

  public String toString() 
  {
    return "<JTRE: " + m_title + ">";
  }
}
