/*
 * Automata.java  
 *
 * Copyright 2001
 * College of Computer Science
 * Northeastern University
 * Boston, MA  02115
 *
 * This software may be used for educational purposes as long as
 * this copyright notice is retained intact at the top of all files.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either in whole
 * or in part without explicit permission.
 *
 * Contact information:
 *   Richard Rasala    rasala@ccs.neu.edu
 *   Viera Proulx      vkp@ccs.neu.edu
 *   Jeff Raab         jmr@ccs.neu.edu
 *   Jennifer McDonald jenimac@ccs.neu.edu
 * 
 * Telephone:          617-373-2462
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

import edu.neu.ccs.*;
import edu.neu.ccs.codec.*;
import java.lang.*;
import java.util.*;
import java.awt.*;
import java.text.ParseException;

// Automata holds all states, arrows and transitions
public class Automata implements Stringable{

	// Hashtable of Hashtables: arrows is hashed 
	// on arrow's source, inner table is hashed
	// on arrow's target. Arrows are located in 
	// the innter tables
	
	private XHashtable stateData = null;
	private XHashtable automataData = null;
	
	private static String startState = "";
	
	// constructor
	public Automata() {
	
		stateData = new XHashtable();
		automataData = new XHashtable();
	}
	
	public Automata(String data)
		throws ParseException {
		fromStringData(data);
	}
	
	public void fromStringData(String data) throws ParseException{
	
		try {
		String[] terms = CodecUtilities.decode(data);
	
		if(terms.length > 0) 
			stateData = new XHashtable(terms[0]);
				
		
		if(terms.length > 1) 
			automataData = new XHashtable(terms[1]);
	
			if(terms.length > 2) {
			startState = terms[2];
		
			// notify the state that it is the start state
			getStartState().setStart();
			}
		}
		catch(Exception e3) {System.out.println("start state: " + e3);}
	
	}
		
	public String toStringData() {
		
	 	return CodecUtilities.encode(
            new String[] {stateData.toStringData(), 
            			  automataData.toStringData(),
            			  startState});  
 	}
	
	public void clear() {
		stateData.clear();
		automataData.clear();
		startState = "";
	}
	

	public State getStartState() {
		return (State) stateData.get(new XString(startState));
	}
	
	// adds a State to the stateData hashtable
	// if the State already exists, it returns false
	public boolean addState(State element) {
	
		XString key = new XString(element.getName());
	
		if (stateData.isEmpty()) {
			startState = key.getValue();
			element.setStart();
		}
		
		if (!(stateData.containsKey(key))) {
			stateData.put(key, element);
			
			
			automataData.put(key, new XHashtable());
			
			
			return true;
		}
		
		return false;	
	}
	
	public State getState(String name) {
		return (State)stateData.get(new XString(name));
	}	
	
	public State getState(XString name) {
		return (State) stateData.get(name);
	}
	
	public Enumeration getStates() {
		return stateData.elements();
	}
	
	public int getNumberOfStates() {
		return stateData.size();
	}
	
	public State getStateContaining(Point p) {

		Enumeration e = stateData.elements();
		
		while(e.hasMoreElements()){
		
			State s = (State) e.nextElement();
			
			if(s.contains(p))
				return s;
		}
		return null;		
	}

	
	public void addTransition(State source, 
							  State target,
							  Transition t) {
		
		XString sourceKey = new XString(source.getName());
		XString targetKey = new XString(target.getName());						  
	
		XHashtable innerTable = 
			(XHashtable) automataData.get(sourceKey);
		
		if (innerTable == null) 	
			innerTable = new XHashtable();
			
		XVector transitions = 
			(XVector) innerTable.get(targetKey);
		
		if (transitions == null)
			transitions = new XVector();
			
		transitions.add(t);
		
		// put transitions in inner table
		innerTable.put(targetKey, transitions);

		// put inner table in main table
		//automataData.put(sourceKey, innerTable);							  
							  
	}
	
	public XHashtable getReachableFrom(State source) {
	
		XString key = new XString(source.getName());
		
		return (XHashtable) automataData.get(key);
	}


	public XVector getTransitionsFrom(State source) {
	
		XString key = new XString(source.getName());
		
		XVector v = new XVector();
		
		XHashtable innerTable = 
			(XHashtable) automataData.get(key);
		
		if(innerTable == null)
			return v;
		
		Enumeration elements = innerTable.elements();
		
		for(int i = 0; i < innerTable.size(); i++) 
			v.add((XVector) elements.nextElement());
	
		return v;
	}
	
	public XVector getTransitionsBetween(State source,
										 State target) {
										 
		XString sourceKey = new XString(source.getName());
		XString targetKey = new XString(target.getName());					
		
		XVector v = new XVector();
		
//		System.out.println("AutomataData containsKey? " + automataData.containsKey(sourceKey));
		
//		System.out.println("going to find innertable, automataData is " + automataData.size());
		
		XHashtable innerTable = 
			(XHashtable) automataData.get(sourceKey);
			
		
		
		if(innerTable == null) {
//			System.out.println("inner table == null");
			return v;
		}
			
//		System.out.println("Found " + innerTable.size() + " states from source");
		
		return (XVector) innerTable.get(targetKey);
	
	}
	
	public boolean isEmpty() {
		return (stateData.size() == 0);
	}
	
	// draw arrows and transitions first 
	// then states. Otherwise, arrow lines will
	// overlap states
	public void draw(Graphics2D g) {
	
		try {
	
		int numSources = automataData.size();
		
		Enumeration sources = automataData.keys();
		Enumeration tables = automataData.elements();
		
		 // draw arrow between each source and
		 // all corresponding targets
		 // draw all transitions for each arrow
		 for (int i = 0; i < numSources; i++) {
		 	State source = 
		 		(State) stateData.get((XString) sources.nextElement());
		 	
		 	drawDetails(g, source, (XHashtable) tables.nextElement());
		 }	
		 
		 }
		 catch(Exception e) {System.out.println("problem drawing: " + e); }
	}
	
	
	public void drawDetails(Graphics2D g, State source, 
							XHashtable table) {
		
		int numTargets = table.size();
		
		Enumeration targets = table.keys();
		Enumeration vectors = table.elements();
		
		Arrow a = new Arrow();
		
		for(int i = 0; i < numTargets; i++)	{
			
			State target = 
				(State) stateData.get((XString) targets.nextElement());
				
			XVector transitions = 
				(XVector) vectors.nextElement();	
			
			a.set(source, target, transitions);
			a.draw(g, null);
			a.drawTransitions(g, null);
			
			target.draw(g, null);
		}	
		
		source.draw(g, null);				
	}
}
