/*
 *  Picsparser.java
 *
 *  Copyright 1997 Massachusetts Institute of Technology.
 *  All Rights Reserved.
 *
 *  Author: Ora Lassila
 *
 *  $Id: PICSParser.java,v 1.1 1997/04/15 20:39:27 yhchu 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.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.EXP_ABBR, PICS.EXP);
    this.mappers.put(PICS.FOR, m = new StringToURL());
    this.mappers.put(PICS.FULL, m);
    this.mappers.put(PICS.FULL_ABBR, PICS.FULL);
    this.mappers.put(PICS.GEN, new SymbolToBoolean());
    this.mappers.put(PICS.GEN_ABBR, PICS.GEN);
    this.mappers.put(PICS.MD5_ABBR, PICS.MD5);
    this.stringParser = new StringParser();
  }

  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()));
  }

  // ( (...)
  //   (version service *data)
  //   (...))
  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, PICS.LABELS_ABBR, mappers);
    while (true) {
      PICSLabel label =
        new PICSLabel(version, service,
                      makeOptions(new PropertySet(common), data,
                                  PICS.RATINGS, PICS.RATINGS_ABBR, 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, Symbol alt_end,
                                    Dictionary mappers)
    throws PICSParserException
  {
    Object next;
    while (data.hasMoreElements()
           && ((next = data.nextElement()) != end) && (next != alt_end)) {
//      System.out.println(next.toString()); // DEBUG
      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
  {
    try {
      PICSParser p = new PICSParser();
      Object result = p.parse(new FileInputStream(args[0]));
      SimpleSExprStream.printExpr(result, System.out);
      System.out.println();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    System.in.read(); // DEBUG
  }
}

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;
    }
  }

}


