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

// -*-Mode: Java-*- 
// $Id: Tag.java,v 1.5 2003/03/06 00:40:40 moltmans Exp $
// Tag.java -- 
// Author: Michael Oltmans <moltmans@ai.mit.edu> 
// Created: <Wed Mar  1 23:38:55 2000> 
// Time-stamp: <2000-07-23 18:07:58 moltmans> 
package rec.speech;

import java.util.*;
import javax.speech.recognition.*;
import java.io.*;
import rec.util.*;

/**
 *
 */
public class Tag extends Object {
  /**
   * This is for debugging so we can check that requested tags are
   * valid in some grammar.
   **/
  private static HashSet m_allTags = new HashSet();

  private HashSet m_tags;
  private String m_text;
  private LinkedList m_subTags;
  private long m_start;
  private long m_end;

  public Tag( Tag tag ) 
  {
    m_tags = tag.m_tags;
    m_text = tag.m_text;
    m_subTags = new LinkedList( tag.m_subTags );
    m_start = tag.m_start;
    m_end = tag.m_end;
  }

  public Tag( String tag, String text, long start, long end ) 
  {
    m_tags = new HashSet();
    m_tags.add( tag );
    m_text = text;
    m_subTags = new LinkedList();
    m_start = start;
    m_end = end;
  }

  public Tag( String tag, String text, LinkedList subTags,
	      long start, long end )
  {
    m_tags = new HashSet();
    m_tags.add( tag );
    m_text = text;
    m_subTags = new LinkedList();
    m_start = start;
    m_end = end;
    
    addSubTags( subTags );
  }
  
  public void addTag( String tag ) 
  {
    m_tags.add( tag );
  }

  public void addTags( Collection tags ) 
  {
    m_tags.addAll( tags );
  }
  
  public void addSubTag( Tag tag ) 
  {
    if( tag.getText().equals( getText() ) ) {
      addTags( tag.getTags() );
      addSubTags( tag.getSubTags() );
    }
    else {
      m_subTags.addLast( tag );
    }
  }

  public void addSubTags( LinkedList subTags ) 
  {
    Iterator subs = subTags.iterator();
    while ( subs.hasNext() ) {
      Tag sub = (Tag)subs.next();
      addSubTag( sub );
    }
  }

  /**
   * @return true if this tag is an instance of tag.
   *
   * contrast this with hasTag which checks if tag is contained
   * anywhere in the tag tree.
   **/
  public boolean isTag( String tag ) 
  {
    return m_tags.contains( tag );
  }

  /**
   * @return true if this tag contains tag anywhere in its tree
   *
   * contrast this with isTag which only checks this tag and not the
   * sub tags.
   **/
  public boolean hasTag( String tag ) 
  {
    Assert.check( getAllTagsFromAllGrammars().contains( tag ),
		  "No Grammars return tag: " + tag );
    
    // check if we have it
    if ( m_tags.contains( tag ) )
      return true;

    // check if the subtags have it
    Iterator subs = m_subTags.iterator();
    while ( subs.hasNext() ) {
      Tag sub = (Tag)subs.next();
      if ( sub.hasTag( tag ) )
	return true;
    }

    // nobody has it, so we've had it: return false
    return false;
  }

  public long getStartTime()
  {
    return m_start;
  }

  public long getEndTime()
  {
    return m_end;
  }

  public String getText() 
  {
    return m_text;
  }

  public String getText( String tag ) 
  {
    return getText( tag, true);
  }
  
  public String getText( String tag, boolean unique ) 
  {
    Tag t = getTag( tag, unique );
    if ( t == null )
      return null;

    return t.getText();    
  }
  
  public Tag getTag( String tag ) 
  {
    return getTag( tag, true );
  }

  public Tag getTag( String tag, boolean unique ) 
  {
    Tag found = null;

    if ( m_tags.contains( tag ) )
      found = this;
    
    Iterator subs = m_subTags.iterator();
    while ( subs.hasNext() ) {
      Tag sub = (Tag)subs.next();
      Tag subFound = sub.getTag( tag, unique );
      if ( subFound != null ) {
	Assert.check( (found == null) || !unique,
		      "Requesting tag that has multiple bindings" );
	found = subFound;
      }
    }

    // make sure there wasn't a typo
    if ( found == null ) {
      Assert.check( getAllTagsFromAllGrammars().contains( tag ),
		    "No Grammars return tag: " + tag );
    }
    
    // just one found so return it
    return found;
  }

  /** Collection of strings of tags for the top level **/
  public Collection getTags() 
  {
    return m_tags;
  }

  /**
   * @return Collection of all the tags at all levels.  If a tag is
   * listed multiple times it will only be included once here.
   **/
  public Set getAllTags()
  {
    HashSet tags = new HashSet( m_tags );
    Iterator subs = m_subTags.iterator();
    
    while ( subs.hasNext() ) {
      Tag sub = (Tag)subs.next();
      tags.addAll( sub.getAllTags() );
    }
    return tags;
  }

  /**
   * @return The list of sub tags
   **/
  public LinkedList getSubTags() 
  {
    return m_subTags;
  }

//    /**
//     * Since the structure is often known anyway just get the nth one
//     **/
//    public Tag getSubTag( int n ) 
//    {
//      return (Tag) m_subTags.get( n );
//    }

  /**
   * @return The direct subtag whose tag is equal to tag
   **/
  public Tag getSubTag( String tag ) 
  {
    Iterator subs = m_subTags.iterator();
    while ( subs.hasNext() ) {
      Tag sub = (Tag)subs.next();
      if ( sub.m_tags.contains( tag ) ) {
	return sub;
      }
    }
    Assert.check( getAllTagsFromAllGrammars().contains( tag ),
		  "No Grammars return tag: " + tag );
    return null;
  }

  public String toString() 
  {
    return toString("");
  }
  
  public String toString(String indent) 
  {
    StringBuffer result = new StringBuffer();
    result.append( indent + "TAG: " + m_tags + "\n");
//      Iterator tags = m_tags.iterator();
//      while ( tags.hasNext() ) {
//        String tag = (String)tags.next();
//        result.append( " " + tag );
//      }
//      result.append( "\n" );
    
    indent += "  ";
    result.append( indent + "Text: " + m_text + "\n" );
    result.append( indent + "Time: (" + m_start + ", " +
		   m_end + ")\n" );

    Iterator subs = m_subTags.iterator();
    while ( subs.hasNext() ) {
      Tag sub = (Tag)subs.next();
      result.append( sub.toString( indent ) );
    }
    return result.toString();
  }
  

  public static Set getAllTagsFromAllGrammars() 
  {
    return m_allTags;
  }    

  public static void gatherAllTagsFromAllGrammars() 
  {
    RuleGrammar[] grammars = RecListen.getListen().m_recognizer.listRuleGrammars();

    for ( int i=0; i<grammars.length; i++ ) {
      String[] rules = grammars[i].listRuleNames();
      for ( int j=0; j<rules.length; j++ ) {
	String ruleText = grammars[i].getRule(rules[j]).toString();
	extractTagsFromString( ruleText );
      }
    }
  }
  
  
  private static void extractTagsFromString( String text ) 
  {
    StringReader reader = new StringReader( text );
    StreamTokenizer parser = new StreamTokenizer( reader );
    
    parser.slashSlashComments( true );
    parser.slashStarComments( true );
    parser.wordChars( '_', '_' );
    parser.wordChars( '-', '-' );

    int token;

    try {
      
      while ( (token = parser.nextToken()) != parser.TT_EOF ) {
	if ( token == '{' ) {
	  String tag = "";
	  while ( (token = parser.nextToken()) != '}' ) {
	    Assert.check( parser.ttype == parser.TT_WORD,
			  "Bad parse of grammar file in RecGrammar.getAllTags" );
	    tag += parser.sval;
	  }
	  m_allTags.add( tag );
	}
      }
    }
    catch ( IOException ioe ) {
    }
    
  }

}

