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

// -*-Mode: Java-*- 
// $Id: CausalStructure.java,v 1.7 2003/03/06 00:40:49 moltmans Exp $
// CausalStructure.java -- 
// Author: Michael Oltmans <moltmans@ai.mit.edu> 
// Created: <Wed May 17 18:22:33 2000> 
// Time-stamp: <2000-07-14 20:20:09 moltmans> 
package rec.annotate.rules;

import java.util.*;

import rec.util.*;
import rec.sys.*;
import rec.simulate.widgets.*;
import rec.tms.*;
import rec.annotate.*;
import rec.annotate.facts.*;

/**
 **/
public class CausalStructure
{
  private Notebook m_notes;
  private JTRE m_jtre;
  private List m_roots;
  private Map m_eventMap;

  public CausalStructure( Notebook notes, JTRE jtre )
  {
    m_notes = notes;
    m_jtre = jtre;
    update();
  }

  /**
   * @return list of canonical events that are the roots of causal
   *         chains.
   **/
  public List getChains()
  {
    return m_roots;
  }

  public void update() 
  {
    makeCanonicalEvents();
    makeCausalOrdering();
    makeSubEvents();
  }

  /**
   * Set up a map from an event to a set of equivalent events.  We
   * construct it by maping each event to a set containing itself and
   * then merging the sets from equivalent events.  When they are
   * merged, one set is replaced by a pointer to the equivalent event.
   * To find all events equivalent to a given event, recursively look
   * up the events until a set is found, this will be the set of
   * events.
   *
   * construct the map from events to their canonical collection
   **/
  private void makeCanonicalEvents()
  {
    Set events = m_notes.getEvents();
    List sameEvts = m_jtre.fetchInFacts( Type.SAME_EVENT );

    m_eventMap = new HashMap();

    // initialize the map with a CanonicalEvent for each event
    Iterator it = events.iterator();
    while( it.hasNext() ) {
      Event evt = (Event)it.next();
      CanonicalEvent canonical = new CanonicalEvent();
      canonical.addEvent( evt );
      m_eventMap.put( evt, canonical );
    }

    // merge the canonical event sets for equivalent events
    it = sameEvts.iterator();
    while( it.hasNext() ) {
      SameEvent same = (SameEvent)it.next();
      // get the two sets
      CanonicalEvent set1 = getCanonical( same.getEvent1() );
      CanonicalEvent set2 = getCanonical( same.getEvent2() );
      // If the sets are the same then there is nothing to do
      // but if they are different then they should be merged
      if( set1 != set2 ) {
	set1.merge( set2 );
	// leave a pointer to the merged set
	m_eventMap.put( same.getEvent2(), same.getEvent1() );
      }
    }

//      // fill in pointers to the canonical events now that we are done
//      // merging
//      it = m_eventMap.keySet().iterator();
//      while( it.hasNext() ) {
//        Event event = it.next();
//        CanonicalEvent canonical = getCanonical( event );
//        m_eventMap.put( event, canonical );
//      }
  }

  /** follow pointers till we get to the canonical set **/
  public CanonicalEvent getCanonical( Object o ) 
  {
    Object obj = m_eventMap.get( o );
    while( obj instanceof Event ) {
      obj = m_eventMap.get( obj );
    }
    return (CanonicalEvent)obj;
  }

  /**
   * @return A sorted set of events, sorted according to the order
   * they were described in.
   **/
  public SortedSet getCanonicalEvents() 
  {
    TreeSet set = new TreeSet( new IntervalComparator() );

    Object obj;
    Iterator it = m_eventMap.values().iterator();
    
    while( it.hasNext() ) {
      obj = it.next();
      if( obj instanceof CanonicalEvent ) {
	set.add( obj );
      }
    }
    return set;
  }
  
  /**
   * Fill in the cause/effect fields of all the canonical events.
   * First, fill them in from explicit causal facts in the DB.  Then
   * connect events that are described temporally adjacent.
   *
   * Set m_roots to a list of events which are the roots of the causal
   * structure.
   **/
  private void makeCausalOrdering()
  {
    // build the cause/effect fields for each event that is explicitly
    // mentioned in a causal fact
    makeExplicitCausalChains();
    // list of roots of the causal structure
    m_roots = new LinkedList();
    SortedSet events = getCanonicalEvents();
    
    Iterator it = events.iterator();
    while( it.hasNext() ) {
      CanonicalEvent evt = (CanonicalEvent)it.next();
      if( evt.getCauses().isEmpty() ) {
	m_roots.add( evt );
      }
//        while( evt.getEffects().isEmpty() && it.hasNext() ) {
//  	CanonicalEvent evt2 = (CanonicalEvent)it.next();
//  	evt.addEffect( evt2 );
//  	evt2.addCause( evt );
//  	evt = evt2;
//        }
    }
  }
  
  /**
   * Build the cause/effect fields for each event that is explicitly
   * mentioned in a causal fact.
   **/
  private void makeExplicitCausalChains()
  {
    List causeList = m_jtre.fetchInFacts( Type.CAUSES );
    causeList.addAll( m_jtre.fetchInFacts( Type.LEADS_TO ) );
    Iterator causes = causeList.iterator();
    while( causes.hasNext() ) {
      LeadsTo causesFact = (LeadsTo)causes.next();
      CanonicalEvent cause = getCanonical( causesFact.getCause() );
      CanonicalEvent effect = getCanonical( causesFact.getEffect() );
      cause.addEffect( effect );
      effect.addCause( cause );
    }
  }

  private void makeSubEvents()
  {
    List subList = m_jtre.fetchInFacts( Type.SUB_EVENT );

    Iterator subs = subList.iterator();
    while( subs.hasNext() ) {
      SubEvent subEvent = (SubEvent)subs.next();
      CanonicalEvent parent = getCanonical( subEvent.getEvent() );
      List children = getCanonicals( subEvent.getSubEvents() );
      parent.addSubEvents( children );

      Iterator kids = children.iterator();
      while( kids.hasNext() ) {
	CanonicalEvent kid = (CanonicalEvent)kids.next();
	kid.addParentEvent( parent );
      }
    }
  }

  /**
   * Convert list of events to list of CanonicalEvents, eliminating
   * duplicates.
   **/
  private List getCanonicals( List events )
  {
    List list = new ArrayList( events.size() );
    Iterator it = events.iterator();
    while( it.hasNext() ) {
      Event event = (Event)it.next();
      list.add( getCanonical( event ) );
    }
    return list;
  }
  
      
    

//        HashSet chains = new HashSet();
//        ListIterator causeIndex = findEvent( chains, cause );
//        ListIterator effectIndex = findEvent( chains, effect );

//        if( causeIndex == null ) {
//  	// if neither event has been placed start a new chain
//  	if( effectIndex == null ) {
//  	  CausalChain chain = new CausalChain();
//  	  chain.add( cause );
//  	  chain.add( effect );
//  	  chains.add( chain );
//  	}
//  	// make sure there are no other causes of this event
//  	else if( effectIndex.hasPrevious() ) {
//  	  // (a leads to b and c), (b leads to c)
//  	  // then a leads to b leads to c

//  	  DebugWindow.addLine( "CausalDebug",
//  			       "Cannot have 2 causes: " + cause +
//  			       " and " + effectIndex.previous() );
//  	}
//  	// add the cause at the beginning of the chain
//  	else {
//  	  effectIndex.add( cause );
//  	}
//        }
//        else if( effectIndex == null ) {
//  	//advance the iterator
//  	causeIndex.next();
//  	if( causeIndex.hasNext() ) {
//  	  DebugWindow.addLine( "CausalBug",
//  			       "Cannot have 2 effects: " + effect +
//  			       " and " + causeIndex.next() );
//  	}
	
//  	causeIndex.add( effect );
//        }
//        else if( effectIndex.hasPrevious() ) {
//  	CanonicalEvent e1 = (CanonicalEvent)effectIndex.previous();
//  	CanonicalEvent e2 = (CanonicalEvent)causeIndex.next();
//  	Assert.check( e1.equals( e2 ) && e1.equals( cause ),
//  		      "Causal conflict:\n  " +
//  		      cause + " causes " + effect + " and\n  " +
//  		      e1 + " causes " + effect + " and\n  " +
//  		      cause + " causes " + e2 );
//        }
//      }
    
//      return chains;
//    }

//    private static ListIterator findEvent( HashSet chains,
//  					 CanonicalEvent evt )
//    {
//      Iterator it = chains.iterator();
//      while( it.hasNext() ) {
//        List list = (List)it.next();
//        int index = list.indexOf( evt );
//        if( index >= 0 ) {
//  	return list.listIterator( index );
//        }
//      }
//      return null;
//    }

}
