// NOTE: YC-A: Yang-hua's addition
//       YC-D: Yang-hua's deletion
//       YC-M: Yang-hua's modification

package w3c.pics.parser;

import java.util.*;
import java.io.StringBufferInputStream;

// YC-A
import w3c.www.dsig.*;
import java.security.*;
import java.security.interfaces.*;


/**
 * A class that represents a single PICS label.
 **/

// YC-M: public class Label implements java.io.Serializable, Cloneable
public class Label implements java.io.Serializable, Cloneable, 
  DSigLabelInterface
{ 
  String serviceURL;
  Hashtable options = new Hashtable();
  Hashtable ratings = new Hashtable();
  Hashtable mextensions = new Hashtable();
  Hashtable oextensions = new Hashtable();

  private ResInfoExtension resinfo;    // YC-A
  private SigBlockExtension sigblock;  // YC-A
  private static final boolean DEBUG_PICS=false;  // YC-A
  private static final boolean DEBUG_DSIG=false;  // YC-A

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

  public Label() { }

  /**
   * Returns the requested one of the Label's options.
   * @param optionName The option to return.
   * @return A String, the value of the option <em>optionName</em>
   **/

  public String getOption(String optionName)
  {
    return (String)options.get(optionName);
  }

  /**
   * Changes the value of one of the Label's options.
   * @param optionName The option to change.
   * @param value The new value.
   **/

  public void setOption(String optionName, String value)
  {
    options.put(optionName, value);
  }

  /**
   * Adds a mandatory extension to this Label.
   * @param exName The name of the extension.
   * @param data The extension data.
   **/

  public void addMExtension(String exName, String data) {
    mextensions.put(exName, data);
  }

  /**
   * Adds an optional extension to this Label.
   * @param exName The name of the extension.
   * @param data The extension data.
   **/

  public void addOExtension(String exName, String data) {
    oextensions.put(exName, data);
  }

  /**
   * Returns the requested one of the Label's mandatory extensions.
   * @param exName The extension to return.
   * @return A String, the data of the mandatory extension <em>exName</em>
   **/
 
  public String getMExtension(String exName) {
    return (String)mextensions.get(exName);
  }

  /**
   * Returns the requested one of the Label's optional extensions.
   * @param exName The extension to return.
   * @return A String, the data of the optional extension <em>exName</em>
   **/

  public String getOExtension(String exName) {
    return (String)oextensions.get(exName);
  }

  /**
   * Returns the Service URL associated with this Label.
   * @return A String containing the Service URL.
   **/

  public String getService()
  {
    return serviceURL;
  }

  /**
   * Changes the Service URL associated with this Label.
   * @param serURL The new Service URL.
   **/

  public void setService(String serURL)
  {
    serviceURL = serURL;
  }

  /**
   * Returns all of the ratings in a Label.
   * @return A Hashtable mapping keys to Vectors of Rating objects.
   **/

  public Hashtable getRatings()
  {
    return ratings;
  }

  /**
   * Changes the rating(s) for a particular category.
   * @param name The name of the Category to change.
   * @param rating A Vector containing one or more Rating objects.
   **/

  public void setRating(String name, Vector rating)
  {
    ratings.put(name,rating);
  }

  /**
   * Returns the ratings for a particular category in this Label.
   * @param categoryName The category whose ratings are to be returned.
   * @return A Vector, containing the Rating objects for the category 
   * <em>categoryName</em>.
   **/

  public Vector getCategory(String categoryName)
  {
    return (Vector)ratings.get(categoryName);
  }

  /**
   * Returns a String containing the PICS 1.1 representation of this Label.
   * @return A String with a valid PICS 1.1 label representation of this 
   * Label object.
   **/

  public String toString()
  {
    String retval = new String("");
    retval+="(PICS-1.1 \"";
    retval+=serviceURL;
    retval+="\"\n";
    retval+="\tlabels \n";
    Enumeration oList = options.keys();
    while (oList.hasMoreElements()) {
      String oKey = oList.nextElement().toString();
      String oVal = options.get(oKey).toString();
      if (oKey.equals("generic"))
	retval+="\t\t"+oKey+" "+oVal+"\n";
      else
	retval+="\t\t"+oKey+" \""+oVal+"\"\n";
    }
    Enumeration mandList = mextensions.keys();
    while (mandList.hasMoreElements()) {
      String mandKey = mandList.nextElement().toString();
      String mandVal = mextensions.get(mandKey).toString();
      retval+="\t\textension (mandatory \""+mandKey+"\" "+mandVal+")\n";
    }
    Enumeration optList = oextensions.keys();
    while (optList.hasMoreElements()) {
      String optKey = optList.nextElement().toString();
      String optVal = oextensions.get(optKey).toString();
      retval+="\t\textension (optional \""+optKey+"\" "+optVal+")\n";
    }
    retval+="\t\tratings (";
    Enumeration rList = ratings.keys();
    while (rList.hasMoreElements()) {
      String catname = rList.nextElement().toString();
      Vector rvector = (Vector)ratings.get(catname);
      retval+=catname+" ";
      Enumeration vlist = rvector.elements();
      if (rvector.size()>1) 
	retval+="(";
      while (vlist.hasMoreElements()) {
	String singlerating = vlist.nextElement().toString();
	retval+=singlerating+" ";
      }
      if (rvector.size()>1)
	retval+=") ";
    }
    retval+="))\n";
    return retval;
  }

  private String sortExtensions() {
    String retval = new String("");
    String[] exnames = new String[mextensions.size()+oextensions.size()];
    Enumeration optList = oextensions.keys();
    Enumeration mandList = mextensions.keys();
    int i = 0;
    while (optList.hasMoreElements()) {
      exnames[i]=optList.nextElement().toString();
      i++;
    }
    while (mandList.hasMoreElements()) {
      exnames[i]=mandList.nextElement().toString();
      i++;
    }
    for (int j = exnames.length-1; j>-1; j--) {
      for (int k = 0; k<j; k++) {
	if (exnames[k].compareTo(exnames[k+1])>0) {
	  String temp = exnames[k];
	  exnames[k]=exnames[k+1];
	  exnames[k+1]=temp;
	}
      }
    }
    for (int l = 0; l<exnames.length; l++) {
      if ((oextensions.get(exnames[l]))!=null) {
	retval += "extension ( optional \""+exnames[l]+"\" "+
	  oextensions.get(exnames[l])+") ";
      }
      else {
	retval += "extension ( mandatory \""+exnames[l]+"\" "+
	  mextensions.get(exnames[l])+") ";
      }
    }
    return retval;
  }

  // YC-A
  // Methods below this line are additions

  /**
   * Returns a String containing the canonical PICS 1.1 representation of 
   * this Label, suitable for signing.
   * @return A String with a valid canonicalized PICS 1.1 label 
   * representation of this Label object.
   **/
  public String toDSigString() {
    String retval = new String("");
    retval+="( pics-1.1 \"";
    retval+=serviceURL;
    retval+="\" l ";
    String[] optionnames = new String[options.size()+1];
    Enumeration olist = options.keys();
    int i = 0;
    optionnames[i] = new String("extension");
    i = 1;
    while (olist.hasMoreElements()) {
      optionnames[i]=(String)olist.nextElement();
      i++;
    }
    // optionnames[i]= new String("extension");
    // sort array 
    for (int j = optionnames.length-1; j>-1; j--) {
      for (int k = 0; k<j; k++) {
	// System.out.println("j"+j+" k"+k);
	// System.out.println("k: "+optionnames[k]+" k+1: "+optionnames[k+1]);
	if (optionnames[k].compareTo(optionnames[k+1])>0) {
	  String temp = optionnames[k];
	  optionnames[k]=optionnames[k+1];
	  optionnames[k+1]=temp;
	  /* System.out.println("Switched "+optionnames[k+1]+" with "+
			     optionnames[k]); */
	}
      }
    }
    for (int l = 0; l<optionnames.length; l++) {
      if (!optionnames[l].equals("extension")) {
	if (optionnames[l].equals("generic")) {
	  retval+=optionnames[l]+" "+options.get(optionnames[l]).toString()
	    +" ";
	}
	else {
	  retval+=optionnames[l]+" \""+options.get(optionnames[l]).toString()
	    +"\" ";
	}
      }
      else {
	if ((oextensions.size()!=0) || (mextensions.size()!=0)) {
	  retval += sortExtensions();
	}
      }
    }
    retval+="r ( ";
    Enumeration rlist = ratings.keys();
    String[] ratlist = new String[ratings.size()];
    int j = 0;
    while (rlist.hasMoreElements()) {
      ratlist[j] = (String)rlist.nextElement();
      // System.out.println("ratlist"+j+": "+ratlist[j]);
      j++;
    }
    for (int k = ratlist.length-1; k>-1; k--) {
      for (int m = 0; m<k; m++) {
	if (ratlist[m].compareTo(ratlist[m+1])>0) {
	  String temp = ratlist[m];
	  ratlist[m]=ratlist[m+1];
	  ratlist[m+1]=temp;
	}
      }
    }
    for (int n = 0; n<ratlist.length; n++) {
      String catname = ratlist[n];
      Vector rvector = (Vector)ratings.get(catname);
      retval+=catname+" ";
      Enumeration vlist = rvector.elements();
      if (rvector.size()>1)
	retval+="( ";
      while (vlist.hasMoreElements()) {
	String singlerating = ((Rating)vlist.nextElement()).toDsigString();
	retval+=singlerating+" ";
      }
      if (rvector.size()>1) 
	retval+=") ";
    }
    retval+=") )";
    return retval;
  }

  void setRatings(Hashtable ratings)
  {
    this.ratings = ratings;
  }  

  /**
   * Parse the two DSig extension fields (resinfo and sigblock) out
   * of the <code>Label</code> object.  It is necessary to call this
   * method after the <code>Label</code> object is created by the
   * PICS parser and before any signing or verification takes place.
   * @exception ParseException if the DSig extensions cannot be parsed 
   * correctly.
   * @exception DSigException if the DSig extensions cannot be parsed 
   * correctly.
   */
  public void initDSig() throws ParseException, DSigException
  {
      resinfo = new ResInfoExtension();
      sigblock = new SigBlockExtension();
      Vector v = this.getParsedResInfo();
      if (v != null) resinfo.parse(v);
      v = this.getParsedSigBlock();
      if (v != null) sigblock.parse(v);
  }

  /**
   * The change made in ResInfo and SigBlock extension is not automatic;
   * calling this method updates the original PICS1.1 Label and reflect
   * the changes in resinfo and sigblock.
   */
  public void updateDSig()
  {
    this.addOExtension(resinfo.EXTENSION_ID, resinfo.toString());
    this.addOExtension(sigblock.EXTENSION_ID, sigblock.toString());
  }
 
  /**
   * Get the DSig Resource Information (resinfo).
   */
  public ResInfoExtension getResInfo()
  {
    return this.resinfo;
  }

  /**
   * Set the DSig Resource Information (resinfo).
   */
  public void setResInfo(ResInfoExtension resinfo)
  {
    this.resinfo = resinfo;
  }

  /**
   * Get the DSig Signature Block (sigblock)
   */
  public SigBlockExtension getSigBlock()
  {
    return this.sigblock;
  }

  /**
   * Set the DSig Signature Block (sigblock)
   */
  public void setSigBlock(SigBlockExtension sigblock)
  {
    this.sigblock = sigblock;
  }

  /**
   * Sign the label using the appropriate private key class 
   * recognized by the signature suite. Signing automatically
   * sets the ByName property to "[anonymous]", that should be
   * set appropriately after signing. If this signature suite
   * object already contains a signature, it will be replaced by
   * the new one.
   */
  public void sign(SignatureSuite sigsuite, PrivateKey privkey)
  {
    sigsuite.sign(this, privkey);
    sigblock.addSigSuite(sigsuite);
  }

  /**
   * Verify the complete Signature Label. This will verify all
   * message digests present in the resinfo and all signatures
   * present in the sigblock. Therefore, this is a computationally
   * rather expensive operation that is often not required. Still,
   * this function is provided to allow verification with just one
   * function call. For other methods to verify the label see
   * the documentation.
   */
  public Trivalue verifyAll()
  {
    Trivalue result = Trivalue.newTrue();
    result = result.and(resinfo.verifyAll(this));     // verify all hashes
    if (DEBUG_DSIG) System.out.println("Label: ResInfo:" + result.toString());
    result = result.and(sigblock.verifyAll(this));    // verify all signatures
    return result;
  }

  /**
   * Verify the label using the user's default policy. Currently this
   * is defined to be the same as verifyAll(), but this will change.
   */
  public Trivalue verify()
  {
    return verifyAll();
  }

  /**
   * Return the URL specified in the 'for' option of this label
   * or null if none is present.
   */
  public String getFor()
  {
    return this.getOption("for");
  }

  private static int NONE = 0;
  private static int INCLUDE = 1;
  private static int EXCLUDE = 2;


  /**
   * Returns the canonicallized form of the DSig label with fields
   * selectively included or excluded according the sigdata.  Please refer
   * to the DSig Label specification for more detail.
   * @exception DSigException if the format of the sigdata is incorrect.
   */
  public String toExcludedDSigString(Hashtable sigdata) throws DSigException
  {
    /* first, look at the Sigdata passed in, and find the include/exclude
       clauses, if any. */
    int equivmode = NONE;
    Vector optionList = new Vector();
    Vector ratingsList = new Vector();
    Vector extensionList = new Vector();
    if (sigdata.get("include")!=null) {
      if (equivmode==NONE) {
	equivmode=INCLUDE;
      }
      else {
	// error
	throw new DSigException();
      }
    }
    if (sigdata.get("exclude")!=null) {
      if (equivmode==NONE) {
	equivmode=EXCLUDE;
      }
      else {
	// error
	throw new DSigException();
      }
    }
    if (equivmode!=NONE) {
      // build lists
      String fieldlist = null;
      if (equivmode==INCLUDE) {
	fieldlist = (String)sigdata.get("include");
      }
      else {
	fieldlist = (String)sigdata.get("exclude");
      }
      int firstquote = fieldlist.indexOf("\"");
      while (firstquote!=-1) {
	int secondquote = fieldlist.indexOf("\"", firstquote+1);
	String listname = fieldlist.substring(firstquote+1, secondquote);
	int thirdquote = fieldlist.indexOf("\"", secondquote+1);
	if (listname.equals("options")) {
	  StringTokenizer onames = 
	    new StringTokenizer(fieldlist.substring(secondquote+1), " ()");
	  if (thirdquote==-1) {
	    while (onames.hasMoreTokens()) {
	      optionList.addElement(onames.nextToken());
	    }
	  }
	  else {
	    while (onames.hasMoreTokens()) {
	      String nextTok = onames.nextToken();
	      if (nextTok.indexOf("\"")!=-1) {
		break;
	      }
	      else {
		optionList.addElement(nextTok);
	      }
	    }
	  }
	}
	else if (listname.equals("ratings")) {
	  StringTokenizer rnames = 
	    new StringTokenizer(fieldlist.substring(secondquote+1), " ()");
	  if (thirdquote==-1) {
	    while (rnames.hasMoreTokens()) {
	      ratingsList.addElement(rnames.nextToken());
	    }
	  }
	  else {
	    while (rnames.hasMoreTokens()) {
	      String nextTok = rnames.nextToken();
	      if (nextTok.indexOf("\"")!=-1) {
		break;
	      }
	      else {
		ratingsList.addElement(nextTok);
	      }
	    }
	  }
	}
	else if (listname.equals("extensions")) {
	  StringTokenizer exnames = 
	    new StringTokenizer(fieldlist.substring(secondquote+1), " ()\"");
	  while (exnames.hasMoreTokens()) {
	    extensionList.addElement(exnames.nextToken());
	  }
	}
	if (thirdquote==-1)
	  break;
	fieldlist = fieldlist.substring(thirdquote);
	firstquote = fieldlist.indexOf("\"");
      }
    }
    // save of copy of this label as origLabel.
    Label origLabel = null;
    try {
      origLabel = (Label)this.clone();
    }
    catch (CloneNotSupportedException ex) {
      System.out.println("Label.sign() CloneNotSupportException");
    }
    // debugging info
    if (DEBUG_PICS) {
      System.out.println("optionList: "+optionList);
      System.out.println("ratingsList: "+ratingsList);
      System.out.println("extensionList: "+extensionList);
    }
    // create Dsig equivalent label.
    if (equivmode!=NONE) {
      if (optionList.size()!=0) {
	if (equivmode==INCLUDE) {
	  Hashtable oldoptions = (Hashtable)options.clone();
	  options.clear();
	  Enumeration optList = optionList.elements();
	  while (optList.hasMoreElements()) {
	    Enumeration oldoptlist = oldoptions.keys();
	    String theopt = (String)optList.nextElement();
	    while (oldoptlist.hasMoreElements()) {
	      String oldopt = (String)oldoptlist.nextElement();
	      if (theopt.equals(oldopt)) {
		options.put(oldopt, oldoptions.get(oldopt));
		break;
	      }
	    }
	  }
	} 
	else {
	  Enumeration optList = optionList.elements();
	  while (optList.hasMoreElements()) {
	    String optName = (String)optList.nextElement();
	    options.remove(optName);
	  }
	}
      }
      if (ratingsList.size()!=0) {
	if (equivmode==INCLUDE) {
	  Hashtable oldratings = (Hashtable)ratings.clone();
	  ratings.clear();
	  Enumeration ratList = ratingsList.elements();
	  while (ratList.hasMoreElements()) {
	    Enumeration oldratlist = oldratings.keys();
	    String therat = (String)ratList.nextElement();
	    while (oldratlist.hasMoreElements()) {
	      String oldrat = (String)oldratlist.nextElement();
	      if (therat.equals(oldrat)) {
		ratings.put(oldrat, oldratings.get(oldrat));
		break;
	      }
	    }
	  }
	} 
	else {
	  Enumeration ratList = ratingsList.elements();
	  while (ratList.hasMoreElements()) {
	    String ratName = (String)ratList.nextElement();
	    ratings.remove(ratName);
	  }
	}
      }
      if (extensionList.size()!=0) {
	if (equivmode==INCLUDE) {
	  Hashtable oldmexts = (Hashtable)mextensions.clone();
	  Hashtable oldoexts = (Hashtable)oextensions.clone();
	  mextensions.clear();
	  oextensions.clear();
	  Enumeration extList = extensionList.elements();
	  while (extList.hasMoreElements()) {
	    Enumeration oldmlist = oldmexts.keys();
	    Enumeration oldolist = oldoexts.keys();
	    String theext = (String)extList.nextElement();
	    boolean wasm = false;
	    while (oldmlist.hasMoreElements()) {
	      String oldm = (String)oldmlist.nextElement();
	      if (theext.equals(oldm)) {
		mextensions.put(oldm, oldmexts.get(oldm));
		wasm = true;
		break;
	      }
	    }
	    if (wasm) 
	      break;
	    while (oldolist.hasMoreElements()) {
	      String oldo = (String)oldolist.nextElement();
	      if (theext.equals(oldo)) {
		oextensions.put(oldo, oldoexts.get(oldo));
		break;
	      }
	    }
	  }
	} 
	else {
	  Enumeration extList = extensionList.elements();
	  while (extList.hasMoreElements()) {
	    String extName = (String)extList.nextElement();
	    mextensions.remove(extName);
	    oextensions.remove(extName);
	  }
	}
      }
    }
    // yc: ALWAYS remove SigBlock extension
    mextensions.remove(SigBlockExtension.EXTENSION_ID);  
    oextensions.remove(SigBlockExtension.EXTENSION_ID);  
    return origLabel.toDSigString();
  }
  /**
   * Returns a Vectorized version of the DSig SigBlock extension present 
   * in this label.  Returns null if the SigBlock extension is not present.
   * @return A Vector
   * @exception ParseException If the SigBlock did not parse.
   **/

  private Vector getParsedSigBlock() throws ParseException {
    Object ex = oextensions.get(SigBlockExtension.EXTENSION_ID);
    if (ex==null)
      return null;
    else {
      String exVal = (String)ex;
      StringBufferInputStream parserinput = new StringBufferInputStream(exVal);
      if (Globals.firstdsig) {
	Globals.dsigparser = new DSigParser(parserinput);
	Globals.firstdsig = false;
      }
      else {
	Globals.dsigparser.ReInit(parserinput);
      }
      Vector output = new Vector();
      Globals.dsigparser.sigExt(output);
      return output;
    }
  }

  /**
   * Returns a Vectorized version of the Resinfo extension present in this 
   * label.  Returns null if the Resinfo extension is not present.
   * @return A Vector
   * @exception ParseException If the Resinfo did not parse.
   **/

  private Vector getParsedResInfo() throws ParseException {
    Object ex = oextensions.get(ResInfoExtension.EXTENSION_ID);
    if (ex==null)
      return null;
    else {
      String exVal = (String)ex;
      StringBufferInputStream parserinput = new StringBufferInputStream(exVal);
      if (Globals.firstdsig) {
	Globals.dsigparser = new DSigParser(parserinput);
	Globals.firstdsig = false;
      }
      else {
	Globals.dsigparser.ReInit(parserinput);
      }
      Vector output = new Vector();
      Globals.dsigparser.resInfo(output);
      return output;
    }
  }
  
}
