content: /*
   * Copyright (C) 2010-2014 Mathias Unberath
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
  */
  package edu.stanford.rsl.conrad.io;
  
  import java.io.BufferedReader;
  import java.io.FileNotFoundException;
  import java.io.FileReader;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.io.UnsupportedEncodingException;
  import java.util.ArrayList;
  import java.util.StringTokenizer;
  
  import edu.stanford.rsl.conrad.geometry.shapes.mesh.Mesh;
  import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND;
  import edu.stanford.rsl.conrad.numerics.SimpleMatrix;
  
  
  /**
   * Class to write legacy .vtk polydata meshes to a file. 
   * Only triangular meshes are supported at the moment.
   * Extended to meshes featuring deformation vectors at each mesh vertex.
   * FIXME Parser adds additional decimal values not present in the input file.
   * @author Mathias Unberath, Tobias Geimer, Bastian Bier
   */
  public class VTKMeshIO {
  	
  	/**
  	 * The filename of the output file.
  	 */
  	public String filename;
  	
  	/**
  	 * The mesh file to be written to <filename>.
  	 */
  	public Mesh mesh;
  	
  	//==========================================================================================
  	// METHODS
  	//==========================================================================================\
  	
  	/**
  	 * Class to write legacy .vtk polydata files.
  	 */
  	public VTKMeshIO(){
  		
  	}
  	
  	/**
  	 * Constructs a writer object and sets the output filename.
  	 * @param filename The output file.
  	 */
  	public VTKMeshIO(String filename){
  		this.filename = filename;
  	}
  	
  	/**
  	 * Constructs a writer object and sets the output filename and mesh to be written.
  	 * @param filename The output file.
  	 * @param mesh	The mesh to be written.
  	 */
  	public VTKMeshIO(String filename, Mesh mesh){
  		this.filename = filename;
  		this.mesh = mesh;
  	}
  
  	/**
  	 * Sets the output filename.
  	 * @param filename The output file.
  	 */
  	public void setFilename(String filename){
  		this.filename = filename;
  	}
  	
  	/**
  	 * Sets the mesh to be written.
  	 * @param filename The mesh to be written.
  	 */
  	public void setMesh(Mesh mesh){
  		this.mesh = mesh;
  	}
  	
  	/**
  	 * Getter for the mesh.
  	 * @return The mesh.
  	 */
  	public Mesh getMesh(){
  		return this.mesh;
  	}
  	
  	/**
  	 * Write the mesh to a file.
  	 */
  	public void write(){
  		assert(filename != null) : new Exception("Filename has not been set.");
  		assert(mesh != null) : new Exception("Mesh has not been set.");
  		
  		System.out.println("Writing file: " + filename);
  		
  		SimpleMatrix points = mesh.getPoints();
  		SimpleMatrix triangles = mesh.getConnectivity();
  		SimpleMatrix deformations = mesh.getDeformation();
  		
  		if(points == null){
  			points = new SimpleMatrix(0,0);
  		}
  		
  		if(triangles == null){
  			triangles = new SimpleMatrix(0,0);
  		}
  		
  		try {
  			PrintWriter writer = new PrintWriter(filename,"UTF-8");
  			//write header information
  			writer.println("# vtk DataFile Version 3.0");
  			writer.println("vtk output");
  			writer.println("ASCII");
  			writer.println("DATASET POLYDATA");
  			// write number of points and then each point in one line
  			writer.println("POINTS "+points.getRows()+" float");
  			for(int i = 0; i < points.getRows(); i++){
  				writer.println(points.getElement(i, 0)+" "+points.getElement(i, 1)+" "+points.getElement(i, 2));
  			}
  			
  			// write number of triangles, number of total entries and then each triangle in one line
  			writer.println("POLYGONS "+triangles.getRows()+" "+4*triangles.getRows());
  			for(int i = 0; i < triangles.getRows(); i++){
  				writer.println("3 " + Integer.toString((int)triangles.getElement(i, 0)) + " " 
  									+ Integer.toString((int)triangles.getElement(i, 1)) + " " 
  									+ Integer.toString((int)triangles.getElement(i, 2)));
  			}
  			writer.println();
  			
  			// write deformations
  			if(deformations != null) {
  				writer.println("POINT_DATA " + points.getRows());
  				writer.println("FIELD FieldData 6");
  				writer.println("phiGlyph " + deformations.getCols() + " " + deformations.getRows() + " float");
  				for(int i = 0; i < deformations.getRows(); i++){
  					writer.println(	deformations.getElement(i, 0) + " " 
  									+ deformations.getElement(i, 1) + " " 
  									+ deformations.getElement(i, 2));
  				}
  				writer.println();
  			}
  	
  			writer.close();
  			
  		} catch (FileNotFoundException e) {
  			// TODO Auto-generated catch block
  			e.printStackTrace();
  		} catch (UnsupportedEncodingException e) {
  			// TODO Auto-generated catch block
  			e.printStackTrace();
  		}	
  	}
  	
  	/**
  	 * Method to read a triangular mesh in legacy .vtk polydata format. 
  	 * @throws IOException if .vtk format does not match expected format
  	 */
  	public void read() throws IOException{
  		
  		ArrayList<PointND> points = new ArrayList<PointND>();
  		ArrayList<PointND> triangles = new ArrayList<PointND>();
  		ArrayList<PointND> deformations = new ArrayList<PointND>();
  		
  		FileReader fr = new FileReader(filename);
  		BufferedReader br = new BufferedReader(fr);
  
  		// read and discard header information
  		br.readLine();
  		br.readLine();
  		br.readLine();
  		br.readLine();
  
  		String line = br.readLine();
  		StringTokenizer tok = new StringTokenizer(line);
  		String t = tok.nextToken(); // skip "points"
  		t = tok.nextToken();
  		int numPoints = Integer.parseInt(t);
  				
  		// read points
  		// the logic here allows more than one single point per line
  		for (int i = 0; i < numPoints;){
  			line = br.readLine();
  			tok = new StringTokenizer(line);
  			int nrPts = tok.countTokens();
  			nrPts /= 3;
  			for (int j = 0; j < nrPts; j++){
  				PointND p = new PointND(Float.parseFloat(tok.nextToken()), Float.parseFloat(tok.nextToken()), Float.parseFloat(tok.nextToken()));
  				points.add(p);
  			}
  			i += nrPts;
  		}
  		// read connectivity information
  		// assumes triangle mesh, hence first number in connectivity information needs to be 3
  		// logic allows more than one triangle per line
  		line = br.readLine();
  		if(line.isEmpty()){
  			line = br.readLine();
  		}
  		tok = new StringTokenizer(line);
  		tok.nextToken(); // skip "polygons"
  		int numTri =  Integer.parseInt(tok.nextToken());
  		for(int i = 0; i < numTri;){
  			line = br.readLine();
  			tok = new StringTokenizer(line);
  			int nTri = tok.countTokens();
  			nTri /= 4;
  			for(int j = 0; j < nTri; j++){
  				t = tok.nextToken();
  				if(Integer.parseInt(t) != 3){
  					br.close();
  					fr.close();
  					throw new IOException("VTK-Polydata file: Format not yet supported.");
  				}else{
  					PointND triangle = new PointND(Integer.parseInt(tok.nextToken()),Integer.parseInt(tok.nextToken()),Integer.parseInt(tok.nextToken()));
  					triangles.add(triangle);
  				}
  			}
  			i += nTri;
  		}
  		
  		// read deformation vectors
  		boolean hasDeform = true;
  		
  		line = br.readLine();
  		if(line!=null && line.isEmpty()){
  			line = br.readLine();
  		}
  		// not every mesh features a deformation field
  		// check whether the file contains any more information
  		if(line == null) {
  			hasDeform = false; 
  		} else {
  			tok = new StringTokenizer(line);
  			tok.nextToken(); // skip "Point_Data"
  			int numVec =  Integer.parseInt(tok.nextToken());
  			line = br.readLine(); // skip "Field FieldData 6"
  			line = br.readLine(); // skip "Phi_Glyph numComp numTuples dataType"
  			for(int i = 0; i < numVec;){
  				line = br.readLine();
  				tok = new StringTokenizer(line);
  				int nVec = tok.countTokens();
  				nVec /= 3;
  				for(int j = 0; j < nVec; j++){
  					PointND vector = new PointND(Float.parseFloat(tok.nextToken()),Float.parseFloat(tok.nextToken()),Float.parseFloat(tok.nextToken()));
  					deformations.add(vector);
  				}
  				i += nVec;
  			}
  		}		
  		br.close();	
  		fr.close();
  		
  		this.mesh = new Mesh();
  		this.mesh.numPoints = numPoints;
  		this.mesh.setPoints(toSimpleMatrix(points));
  		this.mesh.dimension = this.mesh.getPoints().getCols();
  		this.mesh.numConnections = numTri;
  		if(hasDeform) this.mesh.setDeformation(toSimpleMatrix(deformations));
  		if(triangles.size()!=0) this.mesh.setConnectivity(toSimpleMatrix(triangles));
  	}
  	
  	/**
  	 * Converts the ArrayList into a SimpleMatrix structure.
  	 * @param list The ArrayList to be converted.
  	 * @return	The SimpleMatrix containing the ArrayList's entries.
  	 */
  	private SimpleMatrix toSimpleMatrix(ArrayList<PointND> list){
  		int rows = list.size();
  		int cols = list.get(0).getDimension();
  		SimpleMatrix pts = new SimpleMatrix(rows,cols);
  		
  		for(int i = 0; i < rows; i++){
  			PointND point = list.get(i);
  			for(int j = 0; j < cols; j++){
  				pts.setElementValue(i, j, point.get(j));
  			}
  		}
  		return pts;
  	}
  }
  