package JavaAgent.resource;

import java.util.Hashtable;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.io.StreamTokenizer;
import java.io.InputStream;

/**
 * Subclass of Language which defines a KQML message. All legal messages 
 * must have values for the performative, sender, receiver, language, Interpreter
 * and content fields.  Additional fields may be added freely.
 * Constructs a KQML message by parsing
 * an input String, according to the BNF syntax for KQML.
 * See the KQML specification for syntax and semantic details.<p>
 *
 * <hr>
 * Copyright (c) 1995, H. Robert Frost, Stanford University.
 * All rights reserved.<p>
 * Copyright (c) 1996, H. Robert Frost, Enterprise Integration Technologies,
 * Inc. All rights reserved.<p>
 *
 * RESTRICTED RIGHTS LEGEND: Use, duplication or disclosure by the 
 * Government is subject to restrictions as set forth in 
 * subparagraph(c)(1)(ii) of the Rights in Technical Data and Computer
 * Software clause at DFARS 252.227-7013 and in similar clauses in the
 * FAR and NASA FAR supplement.<p>
 * 
 * This software is bound by the terms and conditions listed in the
 * attached <a href="../LICENSE">LICENSE file</A>.
 * <hr>
 * @author <A HREF="http://cdr.stanford.edu/html/people/frost-bio.html"> Rob Frost</A>
 * @version 0.3 5/21/96 for Java(tm) Language Version 1.0.2
 */

public class KQMLmessage extends Language {
  
  protected Hashtable field_value_pairs;
    
  /**
   * Constructor for KQMLmessage, creates an empty message.
   */
  
  public KQMLmessage () {
    field_value_pairs = new Hashtable();
  }

  /**
   * Constructor for KQMLmessage, parses a string to create the message.
   * does not support the  #&ltdigit&gt&lhdigit&gt*"&ltascii&gt* construct.
   *
   * @param message The String containing the message.
   */

  public KQMLmessage(String message){
    this();
    parseString(message);
  }

  /**
   * Takes an empty KQMLmessage instance and populates it by parsing
   * an input string.
   *
   * @param message The String containing the message.
   */
  
  public void parseString(String message){
    
    if(message.length() > 0){
      
      /* create a StringTokenizer with the following delimiters:
	 new line, tab, carriage return, double quote, single quote, right and
	 left parenthesis, and comma. 
	 This tokenizer will return the delimiters as tokens */
      
      StringTokenizer st = new StringTokenizer(message,"\n\t\r\"\'()` #", true);

      /* first token will be a ( so skip it */      
      st.nextToken();
      
      try{
	/* second token is the performative */
	addFieldValuePair("performative",getNonDelimiter(st));
      } catch (Exception e){}
      
      /* parse the rest of the message */
      while(st.hasMoreTokens()){
	/* get the field */
	String token = getNonDelimiter(st);
	if(token != null){
	  String field = token.substring(1); /* remove : */
	  
	  /* get the value, <expression> */
	  String value = ParseExpression(st, 0, false, token);
	  
	  this.addFieldValuePair(field, value);
	}
      }
    }
  }



  /**
   * Returns the next expression from the StringTokenizer. Recursive.
   * @param st The current StringTokenizer.
   * @param num_paren Number of parenthetical nestings.
   * @param string Are we inside of a string?
   * @param last_token What was the last token?
   */
  
  protected String ParseExpression(StringTokenizer st, int num_paren, boolean string,
			 String last_token){

    if(st.hasMoreTokens()){
      
      if(string){ /* we are inside of a string, must wait for double quote */
	String token = st.nextToken();
	
	if(token.equals("\"")){ /* end of string */
	  if(num_paren == 0){ /* end of expression */
	    return token;
	  } else {
	    return( token.concat(ParseExpression(st,num_paren,false,token)));
	  }
	} else if(token.equals("\\")){ /* escape character */
	  token = token.concat(st.nextToken());/* automatically take next token */
	  return( token.concat(ParseExpression(st,num_paren,true,token)));
	} else {
	  return(token.concat(ParseExpression(st,num_paren,true,token)));
	}

      } else {
	
	String token = getNonWhiteSpace(st);
	
	if(token != null){
	  
	  if(token.equals("(")){ /* (<word> {<whitespace> <expression>}*) */

	    if(num_paren == 0){/* get the <word>*/
	      token = token.concat(st.nextToken());
	    }
	    num_paren++;
	    return( token.concat(ParseExpression(st,num_paren,false,token)));
	    
	  } else if(token.equals(")")){ /* close paren */
	    num_paren--;
	    if(num_paren == 0){
	      return token;
	    } else {
	      return( token.concat(ParseExpression(st,num_paren, false,token)));
	    }
	    
	  } else if(token.equals("\'")){ /* '<expression> */
	    
	    return( token.concat(ParseExpression(st,num_paren,false,token)));
	    
	  }  else if(token.equals("`")){ /* `<comma-expression> */
	    
	    return( token.concat(ParseExpression(st,num_paren,false,token)));
	    
	  } else if(token.equals("\"")){ /* "<stringchar>*" */
	    
	    return( token.concat(ParseExpression(st,num_paren,true,token)));
	    
	  } else if(token.equals("#")){ /* #<digit><digit>*"<ascii>* */

	    return(token.concat(LengthDelimitedString(st, num_paren)));
		      
	  } else { /* <word> */
	    if( num_paren == 0){
	      return token;
	    } else {
	      String return_value =
		token.concat(ParseExpression(st,num_paren,false,token));
	      if(isDelimiter(last_token) || isWhiteSpace(last_token)){
		return return_value;
	      } else {
		return (" " + return_value);
	      }
	    
	    }
	    
	  } 
	  
	} else {
	  return "";
	}
      }
    }
    
    return "";
  } 
  

  /**
   * Handles #<digit><digit>*"<ascii>* string constructs.
   */  

  protected String LengthDelimitedString(StringTokenizer st, int num_paren){
    String answer = "";

    /* get the length */
    String length_str = st.nextToken();
    Integer length = new Integer(length_str);
    /* add it on */
    answer = answer.concat(length_str);
    /* get the " */
    answer = answer.concat(st.nextToken());
    /* get each character in the string */
    for(int i = 0; i < length.intValue(); i++){
      answer = answer.concat(st.nextToken("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890()[]{}<>=+-*\\/&^~\'`\"_@#$%:;,.!?\t\n\r "));
    }
    if(num_paren == 0){
      return answer;
    } else {
      return(answer.concat(ParseExpression(st,num_paren,false,answer)));
    }
  }

  /**
   * Removes tokens from the StringTokenizer until the Tokenizer is empty or a 
   * a non-Delimiter token is found. If no non-Delimiter is found returns null.
   */
  
  protected String getNonDelimiter(StringTokenizer st){
    String token = st.nextToken("\n\t\r\"\'()` #");
    while(isDelimiter(token) && st.hasMoreTokens()){
      token = st.nextToken();
    }
    if(isDelimiter(token)){
      token = null;
    } 
    return token;
  }
  
  /**
   * Removes tokens from the StringTokenizer until the Tokenizer is empty or a 
   * a non-whitespace token is found. If no non-whitespace is found returns null.
   */
  
  protected String getNonWhiteSpace(StringTokenizer st){
    String token = st.nextToken("\n\t\r\"\'()` #");
    while(isWhiteSpace(token) && st.hasMoreTokens()){
      token = st.nextToken();
    }
    if(isWhiteSpace(token)){
      token = null;
    } 
    return token;
  }
  
  /**
   * Checks whether a single token is a delimiter.
   */
  
  protected boolean isDelimiter(String token){
    boolean answer = false;
    if(token.equals(" ") ||
       token.equals("\n") ||
       token.equals("\t") ||
       token.equals("\r") ||
       token.equals("\"") ||
       token.equals("\'") ||
       token.equals("`") ||
       token.equals(",") ||
       token.equals("(") ||
       token.equals(")") ||
       token.equals("#")){
      answer = true;
    }
    return answer;
  }
  
  /**
   * Checks whether a single token is whitespace.
   */
  
  protected boolean isWhiteSpace(String token){
    boolean answer = false;
    if(token.equals(" ") ||
       token.equals("\n") ||
       token.equals("\t") ||
       token.equals("\r")){
      answer = true;
    }
    return answer;
  }

  /** 
   * Return the fields in the message.  
   * @return An Enumeration of the message fields.
   */
  
  public Enumeration getFields() {
    return field_value_pairs.keys();
  }


  /**
   * Tests the validity of a KQML message. Does it have all of
   * the necessary fields? Can have more than the minimum set.
   * @param message The KQMLmessage to test.
   * @return Boolean answer, true if valid, otherwise false.
   */

  static public boolean testValidity(KQMLmessage message) {
    boolean validity = false;
    
    if( message.field_value_pairs.containsKey("performative") &&
	message.field_value_pairs.containsKey("sender") &&
	message.field_value_pairs.containsKey("receiver")) {
	// message.field_value_pairs.containsKey("language") &&
	// message.field_value_pairs.containsKey("ontology") &&
	// message.field_value_pairs.containsKey("content")) {
      validity = true;
    }

    return validity;
  }
  
  
 /**
   * Sets the value of the field "field".
   * @param field String key for the field.
   * @param value String value for the field.
   */
  
  public void addFieldValuePair(String field, String value) {
    field_value_pairs.put(field, value);
  }
  
  /**
   * Returns the value of the "field" field. If it does not exit, returns null.
   * @return String representing field value.
   */

  public String getValue(String field) {
    String value = null;
    if(field_value_pairs.containsKey(field)){
      value = (String)field_value_pairs.get(field);
    }
    return value;
  }


  /**
   * Returns the KQML message as a single human readable string.
   * @return Message in string format.
   */

  public String getReadString() {
    String answer = "";
    Enumeration fields = field_value_pairs.keys();

    while(fields.hasMoreElements()){
      String field = (String)fields.nextElement();
      answer = answer.concat(field + ": " + getValue(field) + "\n");
    }
    return answer;
  }

  /**
   * Returns the KQML message as a single machine readable string.
   * This is the format which is sent to other agents.
   * @return Message in string format.
   */

  public String getSendString () {
    String answer = "(" + getValue("performative");
    Enumeration fields = field_value_pairs.keys();

    while(fields.hasMoreElements()){
      String field = (String)fields.nextElement();
      if(!field.equals("performative")){
	answer = answer.concat(" :" + field + " " + getValue(field));
      }
    }
    answer = answer.concat(")");
    return answer;
  }
 
}

