package w3c.pics.parser;

import java.util.*;
import java.io.*;
import java.net.*;

/**
 * A class that represents a PICSRules 1.1 Profile.
 **/

public class Profile implements java.io.Serializable, Cloneable
{
  String version, name, description, sourceURL, tool, author, 
  lastModified;
  Vector policyClauses = new Vector();
  Vector optExt = new Vector();
  Vector reqExt = new Vector();
  Vector extClauses = new Vector();
  Vector nameExt = null;
  Vector sourceExt = null;
  Vector labels = new Vector();

  /* a Hashtable mapping service names to Hashtables mapping attribute names 
    to attribute values */
  Hashtable serviceInformation = new Hashtable();

  /**
   * Creates a new, empty Profile.
   **/

  public Profile() {}

  /**
   * Returns the version of PICSRules that this profile was created for.
   * @return A String containing the version information.
   **/

  public String getVersion()
  {
    return version;
  }

  /**
   * Returns the name of this Profile.
   * @return A String containing the name of this Profile.
   **/

  public String getRuleName()
  {
    return name;
  }

  /**
   * Returns the description of this Profile.
   * @return A String containing the description of this Profile.
   **/

  public String getDescription()
  {
    return description;
  }

  /**
   * Returns the SourceURL of this Profile.
   * @return A String containing the SourceURL of this Profile.
   **/

  public String getSourceURL()
  {
    return sourceURL;
  }

  /**
   * Returns the value of the CreationTool attribute of this Profile.
   * @return A String containing the value of the CreationTool attribute.
   **/

  public String getCreationTool()
  {
    return tool;
  }

  /**
   * Returns the author of this Profile.
   * @return A String containing the author of the Profile.
   **/

  public String getAuthor()
  {
    return author;
  }

  /**
   * Returns the last modified date of this Profile.
   * @return A String containing the last modified date of this Profile.
   **/

  public String getLastModified()
  {
    return lastModified;
  }

  /**
   * Returns a single ServiceInfo clause from this Profile.
   * @param serviceName The name of the ServiceInfo clause to return.
   * @return A Hashtable containing the information for the ServiceInfo 
   * clause with name <em>serviceName</em>
   **/

  public Hashtable getServiceInfo(String serviceName)
  {
    return (Hashtable)serviceInformation.get(serviceName);
  }

  /**
   * Searches for the service name, given the Shortname of the service, 
   * as it appears in a Policy clause, for example.
   * @param shortName The shortname to lookup.
   * @result A String containing the actual service name.
   **/

  public String getFullServiceName(String shortName)
  {
    Enumeration serviceHashtableEnum = serviceInformation.elements();
    while (serviceHashtableEnum.hasMoreElements())
      {
	Hashtable h = (Hashtable)serviceHashtableEnum.nextElement();
	String sn = (String)h.get("shortname");
	if (sn != null)
	  if (sn.equals(shortName))
	    return (String)h.get("name");
      }
    return null;
  }

  /**
   * Returns an Enumeration containing all of the names of the ServiceInfo 
   * clauses.
   * @return An Enumeration.
   **/

  public Enumeration getServiceNames() 
  {
    return serviceInformation.keys();
  }

  /**
   * Returns a Vector containing all of the information contained in the 
   * Policy clauses.
   * @return A Vector
   **/

  public Vector getPolicies()
  {
    return policyClauses;
  }

  /**
   * Returns a Vector containing String representations of all the optional 
   * extensions defined in this Profile.
   * @return A Vector.
   **/
  
  public Vector getOptExt() {
    return optExt;
  }

  /**
   * Returns a Vector containing String representations of all the required
   * extensions defined in this Profile.
   * @return A Vector.
   **/

  public Vector getReqExt() {
    return reqExt;
  }

  /** 
   * Matches URLs and results up one by one and nicely prints the
   * results to System.out.
   * @param URLs The URLs to be printed.
   * @param results A Vector containing the results for each URL.
   **/

  public void printResults(Vector URLs, Vector results){
    Enumeration URLStrings = URLs.elements();
    Enumeration enumResults = results.elements();
    int i = 0;
    while (URLStrings.hasMoreElements()){
      i++;
      String url = (String) URLStrings.nextElement();
      EvalResult result = (EvalResult) enumResults.nextElement();
      System.out.print(i + ". " + url + "\n\t");
      if (result.getResult()){
	System.out.print("Yes: ");
      }
      else{
	System.out.print("No: ");
      }
      System.out.println(result.getExplanation());
    }
  }

  /**
   * checkAll returns a Vector of EvalResults, one for each of the URLs.
   * @param URLs A Vector containing the URLs to evaluate.
   * @return A Vector of EvalResults.
   **/

  public Vector checkAll(Vector URLs){
    Vector results = new Vector();
    String url;
    Enumeration URLStrings = URLs.elements();
    while (URLStrings.hasMoreElements()){
      url = (String) URLStrings.nextElement();
      if (Globals.debugmode) System.out.println("URL = "+ url);
      results.addElement(evalURL(url));
    }
    return results;
  }

  /**
   * Evaluates whether a single url is allowed or not; first fetches
   * any labels from label bureaus that the profile specifies to
   * fetch. 
   * @param url The URL to evaluate.
   * @return An EvalResult.
   **/

  public EvalResult evalURL(String url) {
    
    getLabels(url);
    return isValidWithReason(url, labels, Globals.debugmode);
  }


  /**
   * For each service, get embedded labels and label bureau labels.
   * @param url The URL to be evaluated.
   **/

  public void getLabels(String url){
    labels = new Vector();
    Enumeration serviceNames = getServiceNames();
    while (serviceNames.hasMoreElements()){
      /* serviceInfo attributes are bureauURL, UseEmbedded, etc. */
      Hashtable serviceInfo =
	getServiceInfo((String) serviceNames.nextElement());
      if (Globals.debugmode) 
	System.out.println("getLabels for service: "+serviceInfo.toString());
      if (serviceInfo.containsKey("UseEmbedded") && 
	  ((String) serviceInfo.get("UseEmbedded")).equalsIgnoreCase("N")){
	/* don't use embedded, for now; should realy be a callback to
	/* some embedded label provider method */
	if (Globals.debugmode) 
	  System.out.println("Dont use embedded labels");
      }
      else{
	/* get embedded labels */
	if (Globals.debugmode) 
	  System.out.println("Use embedded labels");
      }
      if (serviceInfo.containsKey("bureauURL") ){      
	/* 1. cons up the URL string with bureauURL + "?" + "u=" +
           <URL with %2f for / and %3a for :> */
	String query = ((String) serviceInfo.get("bureauURL")) + "?" + "u=" + 
	  url;
	if (Globals.debugmode) 
	  System.out.println("query string is " + query);
	
	/* 2. make request to the label bureau (just do a get on the
           URL string) */
	try{
	  URL queryObj = new URL(query);
	  
	  DataInputStream labelin = 
	    new DataInputStream( queryObj.openStream() ); 
	  /*	  if (Globals.debugmode){
	    String line = labelin.readLine(); 
	    while (line != null){
	      System.out.println(line);
	      line = labelin.readLine();
	    }
	  } */
	  /* 3. add response labels to the list-- [when do you check
             for errors?] */
	  
	  if (Globals.firstlabel) {
	    Globals.lparser = new LabelParser(labelin);
	    Globals.firstlabel = false;
	  }
	  else
	    Globals.lparser.ReInit(labelin);
	  
	  Globals.lparser.labellist(labels);
	  if (Globals.debugmode){
	    Enumeration labelenum = labels.elements();
	    while (labelenum.hasMoreElements()) {
	      System.out.println(labelenum.nextElement().toString());
	    }
	  }
	}
	catch (MalformedURLException ex) {
 	  System.out.println("URL malformed");
	  if (ex.getMessage()!=null)
	    System.out.println(ex.getMessage());
	}
	catch (IOException ex) {
	  System.out.println("Fetching error");
	  if (ex.getMessage()!=null)
	    System.out.println(ex.getMessage());
	}
	catch (ParseException ex) {
	  System.out.println("Parsing Error");
	  if (ex.getMessage()!=null)
	    System.out.println(ex.getMessage());
	}
      }
    }
  }

  /**
   * Evaluates a label against this Profile.
   * @param URL The URL that the label rates.
   * @param label The label to evaluate against.
   * @return A boolean, indicating if the label was passed or blocked by this 
   * profile.
   **/
  
  public boolean isValid(String URL, Label label) {
    return isValid(URL, label, false);
  }

  /**
   * Evaluates a group of labels against this Profile.
   * @param URL The URL that the labels rate.
   * @param labellist A Vector containing the labels to evaluate against.
   * @return A boolean, indicating if this group of labels was passed or 
   * blocked by this profile.
   **/

  public boolean isValid(String URL, Vector labellist) {
    return isValid(URL, labellist, false);
  }
  
  /**
   * Evaluates a label against this Profile.
   * @param URL The URL that the label rates.
   * @param label The label to evaluate against.
   * @param debugmode A boolean indicating if debugging output should be 
   * printed or not.
   * @return A boolean, indicating if the label was passed or blocked by this 
   * profile.
   **/

  public boolean isValid(String URL, Label label, boolean debugmode) {
    Vector labellist = new Vector();
    labellist.addElement(label);
    return isValid(URL, labellist, debugmode);
  }

  /**
   * Evaluates a group of labels against this Profile.
   * @param URL The URL that the labels rate.
   * @param labellist A Vector containing the labels to evaluate against.
   * @param debugmode A boolean indicating if debugging output should be 
   * printed or not.
   * @return A boolean, indicating if this group of labels was passed or 
   * blocked by this profile.
   **/

  public boolean isValid(String URL, Vector labellist, boolean debugmode)
  {
    boolean retval = false;
    if (policyClauses==null)
      return true;
    // System.out.println("Profile.isValid: #Policies: "+policyClauses.size());
    Enumeration policies = policyClauses.elements();
    while (policies.hasMoreElements()) {
      Policy currentPolicy = (Policy)policies.nextElement();
      retval = currentPolicy.isValid(URL, labellist, serviceInformation);
      // System.out.println("nodetype: "+currentPolicy.nodetype);
      switch (currentPolicy.nodetype) {
      case Policy.R_URL:
	if (retval) {
	  if (debugmode) {
	    System.out.print("R_URL:MATCH ");
	  }
	  System.out.println(getExp(currentPolicy));
	  return false;
	}
	if (debugmode)
	  System.out.println("R_URL:NO_MATCH");
	break;
      case Policy.A_URL:
	if (retval) {
	  if (debugmode) {
	    System.out.print("A_URL:MATCH ");
	  }
	  System.out.println(getExp(currentPolicy));	  
	  return true;
	}
	if (debugmode)
	  System.out.println("A_URL:NO_MATCH");
	break;
      case Policy.R_UN:
	if (!retval) {
	  if (debugmode) {
	    System.out.print("R_UN:MATCH ");
	  }
	  System.out.println(getExp(currentPolicy));
	  return false;
	}
	if (debugmode)
	  System.out.println("R_UN:NO_MATCH");
	break;
      case Policy.A_UN:
	if (!retval) {
	  if (debugmode) {
	    System.out.print("A_UN:MATCH ");
	  }
	  System.out.println(getExp(currentPolicy));
	  return true;
	}
	if (debugmode)
	  System.out.println("A_UN:NO_MATCH");
	break;
      case Policy.R_IF:
	if (retval) {
	  if (debugmode) {
	    System.out.print("R_IF:MATCH ");
	  }
	  System.out.println(getExp(currentPolicy));
	  return false;
	}
	if (debugmode)
	  System.out.println("R_IF:NO_MATCH");
	break;
      case Policy.A_IF:
	if (retval) {
	  if (debugmode) {
	    System.out.print("A_IF:MATCH ");
	  }
	  System.out.println(getExp(currentPolicy));
	  return true;
	}
	if (debugmode)
	  System.out.println("A_IF:NO_MATCH");
	break;
      }
    }
    return true;
  }

  private String getExp(Policy p) {
    if (p.getExplanation()!=null) 
      return p.getExplanation();
    else
      return "";
  }



  /**
   * Evaluates a group of labels against this Profile.
   * @param URL The URL that the labels rate.
   * @param labellist A Vector containing the labels to evaluate against.
   * @param debugmode A boolean indicating if debugging output should be 
   * printed or not.
   * @return A Vector containing a Boolean and a String.  The Boolean 
   * indicates whether or not this group of labels was passed or 
   * blocked by this profile.  The String contains the reason why, taken 
   * from the Explanation sub-clause of the Policy clause that caused the 
   * passing/blocking.
   **/

  public EvalResult isValidWithReason(String URL, Vector labellist, 
				      boolean debugmode) {
    boolean retval = false;
    if (policyClauses==null) {
      return new EvalResult(true, 
			    "No policy clauses, so everything is acceptable");
    }
    // System.out.println("Profile.isValid: #Policies: "+policyClauses.size());
    Enumeration policies = policyClauses.elements();
    while (policies.hasMoreElements()) {
      Policy currentPolicy = (Policy)policies.nextElement();
      retval = currentPolicy.isValid(URL, labellist, serviceInformation);
      // System.out.println("nodetype: "+currentPolicy.nodetype);
      switch (currentPolicy.nodetype) {
      case Policy.R_URL:
	if (retval) {
	  if (debugmode) {
	    System.out.print("R_URL:MATCH ");
	    System.out.println(getExp(currentPolicy));
	  }
	  return new EvalResult(false, getExp(currentPolicy));
	}
	if (debugmode)
	  System.out.println("R_URL:NO_MATCH");
	break;
      case Policy.A_URL:
	if (retval) {
	  if (debugmode) {
	    System.out.print("A_URL:MATCH ");
	    System.out.println(getExp(currentPolicy));	  
	  }
	  return new EvalResult(true, getExp(currentPolicy));
	}
	if (debugmode)
	  System.out.println("A_URL:NO_MATCH");
	break;
      case Policy.R_UN:
	if (!retval) {
	  if (debugmode) {
	    System.out.print("R_UN:MATCH ");
	    System.out.println(getExp(currentPolicy));
	  }
	  return new EvalResult(false, getExp(currentPolicy));
	}
	if (debugmode)
	  System.out.println("R_UN:NO_MATCH");
	break;
      case Policy.A_UN:
	if (!retval) {
	  if (debugmode) {
	    System.out.print("A_UN:MATCH ");
	    System.out.println(getExp(currentPolicy));
	  }
	  return new EvalResult(true, getExp(currentPolicy));
	}
	if (debugmode)
	  System.out.println("A_UN:NO_MATCH");
	break;
      case Policy.R_IF:
	if (retval) {
	  if (debugmode) {
	    System.out.print("R_IF:MATCH ");
	    System.out.println(getExp(currentPolicy));
	  }
	  return new EvalResult(false, getExp(currentPolicy));
	}
	if (debugmode)
	  System.out.println("R_IF:NO_MATCH");
	break;
      case Policy.A_IF:
	if (retval) {
	  if (debugmode) {
	    System.out.print("A_IF:MATCH ");
	    System.out.println(getExp(currentPolicy));
	  }
	  return new EvalResult(true, getExp(currentPolicy));
	}
	if (debugmode)
	  System.out.println("A_IF:NO_MATCH");
	break;
      }
    }
    return new EvalResult(true, 
			  "No policy clause satisfied, so URL is acceptable");
  }

  /**
   * Returns the PICSRules 1.1 representation of this Profile.
   * @return A String containing the PICSRules 1.1 representation of this 
   * Profile.
   **/

  public String toString()
  {
    String result = new String();
    result += "(PicsRule-" + version + " \n";
    result += "  (\n";
    if (name!=null) {
      if (checkString(name)==USE_DQUOTE)
	result += "  name (rulename \"" + name + "\"\n";
      else
	result += "  name (rulename '" + name + "' \n";
    }
    if (description!=null) {
      if (checkString(description)==USE_DQUOTE)
	result += "   description \""+ description + "\"\n";
      else
	result +="    description '" + description + "'\n";
    }
    if (nameExt!=null) {
      Enumeration nameExts = nameExt.elements();
      while (nameExts.hasMoreElements()) {
	result += "   "+nameExts.nextElement()+"\n";
      }
    }
    if (name!=null)
      result += "  )\n";
    if (sourceURL!=null) {
      if (checkString(sourceURL)==USE_DQUOTE)
	result += "  source (SourceURL \"" + sourceURL + "\"\n";
      else
	result += "  source (SourceURL '" + sourceURL + "'\n";
    }
    if (tool!=null) {
      if (checkString(tool)==USE_DQUOTE)
	result += "   CreationTool \"" + tool + "\"\n";
      else
	result += "   CreationTool '" + tool + "'\n";
    }
    if (author!=null) {
      if (checkString(author)==USE_DQUOTE)
	result += "   author \"" + author + "\"\n";
      else
	result +="    author '" + author + "'\n";
    }
    if (lastModified!=null) {
      if (checkString(lastModified)==USE_DQUOTE)
	result += "   LastModified \"" + lastModified + "\"\n";
      else
	result += "   LastModified '" + lastModified + "'\n";
    }
    if (sourceExt!=null) {
      Enumeration sourceExts = sourceExt.elements();
      while (sourceExts.hasMoreElements()) {
	result += "   "+sourceExts.nextElement()+"\n";
      }
    }
    if (sourceURL!=null)
      result += "  )\n";
    Enumeration serviceClauses = serviceInformation.keys();
    while (serviceClauses.hasMoreElements())
      {
	String name = (String)serviceClauses.nextElement();
	result += "  serviceinfo (\n";
	Hashtable attributeTable = (Hashtable)serviceInformation.get(name);
	Enumeration serviceAttributes = attributeTable.keys();
	while (serviceAttributes.hasMoreElements())
	  {
	    String attributeName = (String)serviceAttributes.nextElement();
	    if (attributeName.equals("extension")) {
	      Vector extAttrList = (Vector)attributeTable.get("extension");
	      Enumeration eAttrList = extAttrList.elements();
	      while (eAttrList.hasMoreElements()) {
		result +="   "+eAttrList.nextElement()+"\n";
	      }
	    }
	    else {
	      result += "   "+attributeName + " \"" + 
		(String)attributeTable.get(attributeName) + "\" \n";
	    }
	  }
	result += "  ) \n";
      }
    if (policyClauses.size()!=0) {
      Enumeration policies = policyClauses.elements();
      while (policies.hasMoreElements()) {
	result += "  "+policies.nextElement().toString();
      }
    }
    if (reqExt.size()!=0) {
      Enumeration reqElements = reqExt.elements();
      while (reqElements.hasMoreElements()) {
	result += "  reqextension ("+reqElements.nextElement()+")\n";
      }
    }
    if (optExt.size()!=0) {
      Enumeration optElements = optExt.elements();
      while (optElements.hasMoreElements()) {
	result += "  optextension ("+optElements.nextElement()+")\n";
      }
    }
    if (extClauses.size()!=0) {
      Enumeration extList = extClauses.elements();
      while (extList.hasMoreElements()) {
	result += "  "+extList.nextElement()+"\n";
      }
    }
    result += "  )\n";
    result += ")\n";
    return result;
  }

  void setVersion(String version)
  {
    this.version = version;
  }
  
  private static int USE_DQUOTE = 0;
  private static int USE_SQUOTE = 1;

  int checkString(String input) {
    if (input.indexOf("\"")!=-1)
      return USE_SQUOTE;
    else
      return USE_DQUOTE;
  }

  void setRuleName(String name)
  {
    this.name = name;
  }

  void setDescription(String description)
  {
    this.description = description;
  }

  void setSourceURL(String sourceURL)
  {
    this.sourceURL = sourceURL;
  }

  void setCreationTool(String tool)
  {
    this.tool = tool;
  }

  void setAuthor(String author)
  {
    this.author = author;
  }

  void setLastModified(String lastModified)
  {
    this.lastModified = lastModified;
  }

  void addServiceInfo(String name, Hashtable attributes)
  {
    serviceInformation.put(name, attributes);
  }

  void putPolicy(Policy policy)
  {
    policyClauses.addElement(policy);
  }

  void addReqExt(String ext) {
    reqExt.addElement(ext);
  }

  void addOptExt(String ext) {
    optExt.addElement(ext);
  }

  void addExtClause(String ext) {
    extClauses.addElement(ext);
  }

  void addNameExt(String ext) {
    if (nameExt==null) {
      nameExt = new Vector();
    }
    nameExt.addElement(ext);
  }

  void addSourceExt(String ext) {
    if (sourceExt==null) {
      sourceExt = new Vector();
    }
    sourceExt.addElement(ext);
  }

  /**
   * The Clone method for Profiles.
   * @return A duplicate of this Profile, as an Object.
   * @exception CloneNotSupportedException If the cloning fails.
   **/

  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }

}

