/*
 *  PICSParser.java
 *
 *  Copyright 1997 Massachusetts Institute of Technology.
 *  All Rights Reserved.
 *
 *  Author: Ora Lassila
 *
 *  $Id: PICSParser.java,v 1.1 1997/05/30 13:23:32 bmahe Exp $
 */

package w3c.www.pics;

import java.util.Date;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Vector;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.StringBufferInputStream;
import java.io.IOException;
import java.io.EOFException;
import java.net.URL;
import java.net.MalformedURLException;
import w3c.tools.sexpr.SExprParser;
import w3c.tools.sexpr.SExprStream;
import w3c.tools.sexpr.SimpleSExprStream;
import w3c.tools.sexpr.SExprParserException;
import w3c.tools.sexpr.Symbol;

public class PICSParser {

  private Dictionary mappers;
  private SExprParser stringParser;

  public PICSParser()
  {
    this.mappers = new Hashtable();
    Mapper m;
    this.mappers.put(PICS.ON, m = new StringToDate());
    this.mappers.put(PICS.EXP, m);
    this.mappers.put(PICS.FOR, m = new StringToURL());
    this.mappers.put(PICS.FULL, m);
    this.mappers.put(PICS.GEN, new SymbolToBoolean());
    this.stringParser = new StringParser();
  }

  public Object parse(String input)
        throws IOException, SExprParserException
  {
    return this.parse(new StringBufferInputStream(input));
  }

  public Object parse(InputStream input)
    throws IOException, SExprParserException
  {
    SExprStream stream = new SimpleSExprStream(input);
    stream.setListsAsVectors(true);
    stream.setSymbols((Hashtable)(PICS.getSymbols().clone()));
    stream.addParser('"', stringParser);
    return
      makeLabels(new PushbackEnumeration(((Vector)stream.parse()).elements()));
  }

  protected Object makeLabels(PushbackEnumeration data)
    throws PICSParserException
  {
    Vector labels = new Vector();
    Symbol version = (Symbol)data.nextElement();
    try {
      URL service = new URL((String)data.nextElement());
      while (true) {
        labels.addElement(makeLabelsForService(version, service, data));
        if (!data.hasMoreElements())
          return vectorOr1(labels);
        else if (data.peekElement() instanceof String)
          service = new URL((String)data.nextElement());
      }
    }
    catch (MalformedURLException e) {
      throw new PICSParserException("Unable to parse service URL", e);
    }
  }

  protected Object makeLabelsForService(Symbol version,
                                        URL service,
                                        PushbackEnumeration data)
    throws PICSParserException
  {
    Vector labels = new Vector();
    PropertySet common =
      makeOptions(new PropertySet(null), data, PICS.LABELS, mappers);
    while (true) {
      PICSLabel label =
        new PICSLabel(version, service,
                      makeOptions(new PropertySet(common),
                                  data, PICS.RATINGS, mappers));
      label.addRatings((Vector)data.nextElement());
      labels.addElement(label);
      if (!data.hasMoreElements() || data.peekElement() instanceof String)
        return vectorOr1(labels);
    }
  }

  protected PropertySet makeOptions(PropertySet options, Enumeration data,
                                    Symbol end, Dictionary mappers)
    throws PICSParserException
  {
    Object next;
    while (data.hasMoreElements() && (next = data.nextElement()) != end) {
      if (next == PICS.EXTENSION) {
        Vector ext = (Vector)data.nextElement();
        try {
          URL key = new URL((String)ext.elementAt(1)); // format verification
          options.put(key.toExternalForm(), new UserOptionValue(ext));
        }
        catch (MalformedURLException e) {
          throw new PICSParserException("Unable to parse extension URL "
                                        + ext.elementAt(1), e);
        }
      }
      else options.putWithMap(next, data.nextElement(), mappers);
    }
    return options;
  }

  private Object vectorOr1(Vector vector)
  {
    return (vector.size() == 1) ? vector.elementAt(0): vector;
  }

  public static void main(String args[])
    throws IOException, SExprParserException
  {
    PICSParser p = new PICSParser();
    //    Object result = p.parse(new FileInputStream("test.pics"));
    //    SimpleSExprStream.printExpr(result, System.out);
    //    System.out.println();
    //    PICSLabel label = (PICSLabel) result;
    String pics = new String("(PICS-1.1 \"http://www.rsac.org/ratingsv01.html\" l gen true comment \"RSACi North America Server\" by \"philipd@w3.org\" for \"http://www.w3.org\" on \"1997.04.18T12:04-0500\" exp \"1997.07.01T08:15-0500\" r (n 1 s 2 v 3 l 4))");
    System.out.println("got :: \n"+getAlternativePICS(pics));
  }

  private static String getAlternativePICS(String picslabel) 
    throws IOException, SExprParserException
  {
    PICSParser p = new PICSParser();
    Object result = p.parse(picslabel);
    PICSLabel label = (PICSLabel) result;
    StringBuffer buffer = new StringBuffer();

    Dictionary D = label.getOptions();
    Enumeration e = D.keys();
    String var = null;
    while (e.hasMoreElements()) {
	Object key = e.nextElement();
	var = ((Symbol)key).toString();
	buffer.append(var+"=");
	if (key.equals(PICS.GEN))
	  buffer.append(D.get(key)+"\n");
	else
	  buffer.append("\""+D.get(key)+"\"\n");
    }
    buffer.append("ratings=(");
    D = label.getRatings();
    e = D.keys();
    try {
      while (e.hasMoreElements()) {
	Object key = e.nextElement();
	buffer.append(" "+key+" "+
		      ((RatingValue)D.get(key)).getValue());
      }
      buffer.append(")");
      return new String(buffer);
    } catch (Exception ex) {
      return null;
    }
  }

}

class StringParser implements SExprParser {

  public Object parse(char first, SExprStream stream)
    throws SExprParserException, IOException
  {
    int code;
    StringBuffer buffer = stream.getScratchBuffer();
    while (true) {
      switch (code = stream.read()) {
        case (int)'"':
          return new String(buffer);
        case -1:
          throw new EOFException();
        case (int)'%':
          int d = readDigit(16, stream);
          buffer.append((char)(d * 16 + readDigit(16, stream)));
          break;
        default:
          buffer.append((char)code);
          break;
      }
    }
  }

  private int readDigit(int radix, SExprStream stream)
    throws SExprParserException, IOException
  {
    int digit = stream.read();
    if (digit == -1)
      throw new EOFException();
    else {
      int value = Character.digit((char)digit, radix);
      if (value == -1)
        throw new SExprParserException("Invalid radix-" + radix
                                       + " digit " + (char)digit);
      else
        return value;
    }
  }

}
