// -*- mode: Java; file-coding-system: *euc-japan*; -*-
// $Id: LineParser.java,v 1.6 1999/12/29 21:55:53 yamauchi Exp $
// Copyright (C) 1999 MAEDA Atusi

/**
 * 設定ファイルを読み込むためのパーザクラス.
 * @author MAEDA Atusi
 */
package jp.ac.tohoku.is.archi.mpi2.util;

import java.io.LineNumberReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;

// import java.util.HashMap;
// import java.util.HashSet;
// import java.util.Map;
// import java.util.Set;
// import java.util.NoSuchElementException;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.HashMap;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.HashSet;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.Map;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.Set;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.NoSuchElementException;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.Str;



import java.util.StringTokenizer;
import java.util.StringTokenizer;

public class LineParser {
  public abstract static class Element {
    private String identifier;
    private Object defaultValue = null;
    protected Element(String identifier, Object defaultValue) {
      this.identifier = identifier;
      this.defaultValue = defaultValue;
    }
    protected Element(String identifier) {
      this(identifier, null);
    }
    public abstract Object parse(StringTokenizer args)
	 throws IllegalElementException;
    public String getIdentifier() {
      return identifier;
    }
    public abstract String getKeyword();
    public Object getDefaultValue() {
      return defaultValue;
    }
  }
  /**
   * LineParserフォーマットファイル中で行の文法に誤りがあることを表す例外.
   *
   */
  public static class IllegalElementException extends RuntimeException {
    public IllegalElementException() {
      super();
    }
    public IllegalElementException(String message) {
      super(message);
    }
  }
  /**
   * LineParserフォーマットファイル中で必須Elementが現われなかったことを表す例外.
   */
  public static class MissingElementException extends RuntimeException {
    public MissingElementException() {
      super();
    }
    public MissingElementException(String message) {
      super(message);
    }
  }
  public static class Name extends Element {
    public Name(String identifier, String defaultValue) {
      super(identifier, defaultValue);
    }
    public Name(String identifier) {
      super(identifier);
    }
    public Object parse(StringTokenizer args)
	 throws IllegalElementException
    {
      return args.nextToken();
    }
    public String getKeyword() {
      return "setname";
    }
  }
  public static class Number extends Element {
    private int length;
    public Number(String identifier, int length, double[] defaultValue) {
      super(identifier, defaultValue);
      this.length = length;
    }
    public Number(String identifier, int length) {
      this(identifier, length, null);
    }
    public Object parse(StringTokenizer args)
	 throws IllegalElementException
    {
      double[] v = new double[length];
      for (int i = 0; i < length; i++) {
	  // v[i] = Double.parseDouble(args.nextToken());
	  v[i] = Str.toDouble(args.nextToken());
      }
      return v;
    }
    public String getKeyword() {
      return "setnum";
    }
  }
  public static class Type extends Element {
    Set choices;
    public Type(String identifier, String[] choices, String defaultValue) {
      super(identifier, defaultValue);
      this.choices = new HashSet((int)(choices.length * 1.5));
      for (int i = 0; i < choices.length; i++) {
	this.choices.add(choices[i]);
      }
    }
    public Type(String identifier, String[] choices) {
      this(identifier, choices, null);
    }
    public Object parse(StringTokenizer args)
	 throws IllegalElementException
    {
      String arg = args.nextToken();
      if (!choices.contains(arg)) {
	throw new IllegalElementException("Value for " + getIdentifier() +
					  " must be one of " + choices);
      }
      return arg;
    }
    public String getKeyword() {
      return "settype";
    }
  }

  private Element[] elements;
  private Map keywords;

  /**
   * パーズすべきElementの配列を与えてLineParserを生成する.
   * @see Element
   * @param elements パーズすべきキーワード定義の配列
   */
  public LineParser(Element[] elements) {
    this.elements = elements;
    keywords = new HashMap();
    for (int i = 0; i < elements.length; i++) {
      String keyword = elements[i].getKeyword();
      if (!keywords.containsKey(keyword)) {
	keywords.put(keyword, new HashMap());
      }
      Map e = (Map)keywords.get(keyword);
      e.put(elements[i].getIdentifier(), elements[i]);
    }
  }
  /**
   * LineParser形式の入力ストリームを解析し，解析結果のMapを返す.
   * @param input LineParser形式の入力ストリーム
   * @return キーワードから値へのMap
   */
  public Map parseAll(InputStream input)
       throws IOException, IllegalElementException, MissingElementException
  {
    String line;
    LineNumberReader in = new LineNumberReader(new InputStreamReader(input));
    Map value = new HashMap();
    Set seen = new HashSet();
    for (int i = 0; i < elements.length; i++) {
      if (elements[i].getDefaultValue() != null) {
	value.put(elements[i].getIdentifier(), elements[i].getDefaultValue());
	seen.add(elements[i].getIdentifier());
      }
    }
    while ((line = in.readLine()) != null) {
      if (line.startsWith("#") || line.startsWith("//")) {
	// Comment.
	continue;
      }
      StringTokenizer st = new StringTokenizer(line);
      if (! st.hasMoreTokens()) {
	// Blank line.
	continue;
      }

      String keyword = st.nextToken();
      Map m = (Map)keywords.get(keyword);
      if (m == null) {
	throw new IllegalElementException("Line " + in.getLineNumber() +
					  ": Unknown keyword: " + keyword
					  + "\n" + line);
      }

      String identifier = st.nextToken();
      Element e = (Element)m.get(identifier);
      if (e == null) {
	throw new IllegalElementException("Line " + in.getLineNumber() +
					  ": Unknown identifier: " + identifier
					  + "\n" + line);
      }

      if (! st.hasMoreTokens() || !"=".equals(st.nextToken())) {
	throw new IllegalElementException("Line " + in.getLineNumber() +
					  ": Syntax error: `=' exptected after " + 
					  keyword + " " + identifier +
					  "\n" + line);
      }

      seen.add(identifier);
      try {
	value.put(identifier, e.parse(st));
      } catch (NoSuchElementException ex) {
	throw new IllegalElementException("Line " + in.getLineNumber() +
					  ": Arguments missing.\n" + line);
      } catch (RuntimeException ex) {
	throw new IllegalElementException("Line " + in.getLineNumber() +
					  ":" + line +
					  "\noriginal exception was :" + ex);
      }
    }
    for (int i = 0; i < elements.length; i++) {
      if (!seen.contains(elements[i].getIdentifier())) {
	throw new MissingElementException(elements[i].getIdentifier() +
					  " not set");
      }
    }
    return value;
  }
}
