// coding system の指定は XEmacsのバージョンによっては harmful です．．
// $Id: MGFReader.java,v 1.10.4.1 2000/06/30 15:45:04 yamauchi Exp $
// Copyright (C) 1999 MAEDA Atusi

/**
 * MGF形式を読み込むReader.
 * @author MAEDA Atusi
 */

package jp.ac.tohoku.is.archi.mpi2.util;
import java.io.LineNumberReader;
import java.io.IOException;
import java.io.Reader;
// import java.util.HashMap;
// import java.util.Map;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.HashMap;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.Map;
import jp.ac.tohoku.is.archi.mpi2.jdk12src.Str;

import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import jp.ac.tohoku.is.archi.mpi2.obj.Material;
import jp.ac.tohoku.is.archi.mpi2.obj.ObjectPrimitive;
import jp.ac.tohoku.is.archi.mpi2.obj.TriangleObject;
import jp.ac.tohoku.is.archi.mpi2.obj.SphereObject;

public class MGFReader extends LineNumberReader {
  private static HashMap parsers = new HashMap();
  private static class MGFParser {
    final protected Vector3 parseVector3(StringTokenizer args) {
      // double x = Double.parseDouble(args.nextToken());
      // double y = Double.parseDouble(args.nextToken());
      // double z = Double.parseDouble(args.nextToken());
	double x = Str.toDouble(args.nextToken());
	double y = Str.toDouble(args.nextToken());
	double z = Str.toDouble(args.nextToken());
      return new Vector3(x, y, z);
    }
    final protected IntVec3 parseIntVec3(StringTokenizer args) {
      int x = Integer.parseInt(args.nextToken());
      int y = Integer.parseInt(args.nextToken());
      int z = Integer.parseInt(args.nextToken());
      return new IntVec3(x, y, z);
    }
    ObjectPrimitive parse(StringTokenizer line, Map materials, Material current) {
      // Returning null means this command does not produce any output.
      return null;
    }
  }
  static {
    parsers.put("rd",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    String name = args.nextToken();
		    Material m = (Material)materials.get(name);

		    Vector3 rgb = parseVector3(args);
		    m.setDiffRef(rgb);
		    return null;
		  }
		});
    parsers.put("td",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    String name = args.nextToken();
		    Material m = (Material)materials.get(name);
		    
		    Vector3 rgb = parseVector3(args);
		    m.setDiffTrans(rgb);
		    return null;
		  }
		});
    parsers.put("ed",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    String name = args.nextToken();
		    Material m = (Material)materials.get(name);
		    
		    Vector3 rgb = parseVector3(args);
		    m.setDiffEmit(rgb);
		    return null;
		  }
		});
    parsers.put("rs",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    String name = args.nextToken();
		    Material m = (Material)materials.get(name);
		    // double n = Double.parseDouble(args.nextToken());
		    double n = Str.toDouble(args.nextToken());
		    Vector3 rgb = parseVector3(args);
		    m.setSpecRefCosN(n);
		    m.setSpecRef(rgb);
		    return null;
		  }
		});
    parsers.put("ts",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    String name = args.nextToken();
		    Material m = (Material)materials.get(name);
		    // double tau = Double.parseDouble(args.nextToken());
		    double tau = Str.toDouble(args.nextToken());
		    Vector3 rgb = parseVector3(args);
		    m.setSpecTransCosN(tau);
		    m.setSpecTrans(rgb);
		    return null;
		  }
		});
    parsers.put("ir",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    String name = args.nextToken();
		    Material m = (Material)materials.get(name);
		    // double ir = Double.parseDouble(args.nextToken());
		    double ir = Str.toDouble(args.nextToken());
		    m.setIdxRefraction(ir);
		    return null;
		  }
		});
    parsers.put("fhtr",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    String name = args.nextToken();
		    Material m = (Material)materials.get(name);
		    // double rad = Double.parseDouble(args.nextToken());
		    double rad = Str.toDouble(args.nextToken());
		    m.setFrustumHalfTopRad(rad);
		    return null;
		  }
		});
    parsers.put("f",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    int objectID = Integer.parseInt(args.nextToken());
		    Vector3 v1 = parseVector3(args);
		    Vector3 v2 = parseVector3(args);
		    Vector3 v3 = parseVector3(args);
		    IntVec3 spos = parseIntVec3(args);

		    return new TriangleObject(objectID, current.getID(),
					      spos, v1, v2, v3,
					      Vector3.ZERO);
		  }
		});
    parsers.put("F",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		    int objectID = Integer.parseInt(args.nextToken());
		    Vector3 v1 = parseVector3(args);
		    Vector3 v2 = parseVector3(args);
		    Vector3 v3 = parseVector3(args);
		    IntVec3 spos = parseIntVec3(args);
		    Vector3 color = parseVector3(args);

		    return new TriangleObject(objectID, current.getID(),
					      spos, v1, v2, v3,
					      color);
		  }
		});
    parsers.put("sph",
		new MGFParser() {
		  public ObjectPrimitive parse(StringTokenizer args,
					       Map materials,
					       Material current) {
		      int objectID = Integer.parseInt(args.nextToken());
		      Vector3 centerpos = parseVector3(args);
		      // double radius = Double.parseDouble(args.nextToken());
		      double radius = Str.toDouble(args.nextToken());
		      IntVec3 spos  = parseIntVec3(args);
		      return new SphereObject(objectID, current.getID(), spos, 
					      centerpos, radius);
		  }
    });
  }

  public static class UnknownCommandException extends RuntimeException {
    UnknownCommandException() {
      super();
    }
    UnknownCommandException(String message) {
      super(message);
    }
  }
  public static class SyntaxException extends RuntimeException {
    SyntaxException() {
      super();
    }
    SyntaxException(String message) {
      super(message);
    }
  }

  // JDK 1.1.7 はコンストラクタで初期化しているこの final を怒る FIXME
  // final private Map materials;  
  private Map materials;
  Material current = null;
  /**
   * Readerと属性情報を保持するMapを与えて，MGF形式をパーズするReaderを
   * 生成する．MGFストリーム中にマテリアル名の参照が現れた時，与えたMapを
   * その名前で引いてMaterialオブジェクトを得る．Map中に無いマテリアル名が
   * 現れた時は新たなマテリアルの定義とみなし，Mapに登録する．
   * @see jp.ac.tohoku.is.archi.mpi2.obj.Material
   * @param in MGF形式のテキストストリーム
   * @param materials 物体属性情報を保持するMap
   */
  public MGFReader(Reader in, Map materials) {
    super(in);
    this.materials = materials;
  }
  /**
   * MGFReader中に保持しているマテリアル情報へのMapを返す．
   * @return マテリアル名からマテリアル情報へのMap
   */
  public Map getMaterials() {
    return materials;
  }
  /**
   * MGFストリームから物体幾何情報を1つ読み取って返す.
   * ストリーム中に現れたマテリアル情報は内部に保持しているMapに登録されて
   * いるマテリアル定義を更新するだけで出力に現れない． EOFならばnullを返す．
   * @return 次の物体幾何情報
   */
  public ObjectPrimitive readItem() throws IOException
  {
    String line = null;
    try {
      while ((line = readLine()) != null) {
	if (line.startsWith("#")) {
	  // Comment.
	  continue;
	}
	StringTokenizer st = new StringTokenizer(line);
	if (!st.hasMoreTokens()) {
	  // Empty line.
	  continue;
	}
	String command = st.nextToken();
	if (command.equals("m")) {
	  String name = st.nextToken();
	  current = (Material)materials.get(name);
	  if (current == null) {
	    current = new Material();
	    current.setName(name);
	    materials.put(name, current);
	  }
	} else {
	  MGFParser parser = (MGFParser)parsers.get(command);
	  if (parser == null) {
	    throw new UnknownCommandException(command);
	  }
	  ObjectPrimitive obj = parser.parse(st, materials, current);
	  if (obj != null) {
	    // オブジェクトを1つ読んだ．
	    return obj;
	  }
	}
      }
    } catch (NoSuchElementException e) {
      throw new SyntaxException("Line " + getLineNumber() +
				": too few number of args.\n" + line);
    } catch (NullPointerException e) {
      throw new SyntaxException("Line " + getLineNumber() +
				": Maybe wrong material name?\n" + line +
				"\n: Original exception was: " + e);
    } catch (RuntimeException e) {
      throw new RuntimeException("Line " + getLineNumber() +
				 ": (original exception " + e + ")\n" + line);
    }
    // EOF
    return null;
  }
}
