content: /*
   * Copyright (C) 2010-2014 Andreas Maier
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
   */
  package edu.stanford.rsl.conrad.geometry.splines;
  
  import java.io.BufferedWriter;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Iterator;
  
  import com.jogamp.opencl.CLContext;
  import com.jogamp.opencl.CLDevice;
  
  import edu.stanford.rsl.conrad.geometry.AbstractCurve;
  import edu.stanford.rsl.conrad.geometry.AbstractShape;
  import edu.stanford.rsl.conrad.geometry.AbstractSurface;
  import edu.stanford.rsl.conrad.geometry.General;
  import edu.stanford.rsl.conrad.geometry.motion.MotionField;
  import edu.stanford.rsl.conrad.geometry.motion.OpenCLParzenWindowMotionField;
  import edu.stanford.rsl.conrad.geometry.motion.ParzenWindowMotionField;
  import edu.stanford.rsl.conrad.geometry.shapes.compound.CompoundShape;
  import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND;
  import edu.stanford.rsl.conrad.geometry.shapes.simple.Triangle;
  import edu.stanford.rsl.conrad.geometry.transforms.Transform;
  import edu.stanford.rsl.conrad.numerics.SimpleVector;
  import edu.stanford.rsl.conrad.opencl.OpenCLUtil;
  import edu.stanford.rsl.conrad.utils.Configuration;
  import edu.stanford.rsl.conrad.utils.RegKeys;
  import edu.stanford.rsl.conrad.utils.TessellationUtil;
  
  public class TimeVariantSurfaceBSpline extends AbstractSurface {
  
  	/**
  	 * 
  	 */
  	private static int tesselationCount = 0;
  	private static int tesselationCount1 = 0;
  	protected ArrayList<SurfaceBSpline> timeVariantShapes;
  	private String title;
  	private static final long serialVersionUID = 6323388850451960550L;
  	protected BSpline timeSpline;
  	protected int tPoints;
  	protected boolean clockwise;
  
  	public boolean isClockwise(){
  		return clockwise;
  	}
  
  	public TimeVariantSurfaceBSpline(ArrayList<SurfaceBSpline> splines){
  		timeVariantShapes = splines;
  		init();
  	}
  
  	public TimeVariantSurfaceBSpline(SurfaceBSpline timeInvariant, MotionField motion, int timePoints, boolean addInitial){
  		timeVariantShapes = new ArrayList<SurfaceBSpline>();
  		ArrayList<PointND> initialPoints = timeInvariant.getControlPoints();
  		// add initial state.
  		if (addInitial) {
  			timeVariantShapes.add(new SurfaceUniformCubicBSpline(initialPoints, timeInvariant.getUKnots(), timeInvariant.getVKnots()));
  		}
  		// Time information comes from the time variant spline(s)
  		tPoints = timePoints;
  		ArrayList<ArrayList<PointND>> newPoints = new ArrayList<ArrayList<PointND>>();
  		if (motion instanceof ParzenWindowMotionField) {
  			for (int t = 1; t < tPoints; t++){
  				PointND [] pointsArray = new PointND[initialPoints.size()];
  				initialPoints.toArray(pointsArray);
  				ArrayList<PointND> pts = null;
  				if (OpenCLUtil.isOpenCLConfigured()){
  					try {
  						CLContext context = OpenCLUtil.getStaticContext();
  						CLDevice device = context.getMaxFlopsDevice();
  						OpenCLParzenWindowMotionField openCLMotion = new OpenCLParzenWindowMotionField((ParzenWindowMotionField)motion, context, device);
  						pts = openCLMotion.getPositions(0, ((double)t)/(tPoints-1), pointsArray);
  					} catch (IOException e) {
  						// Program not found.
  						e.printStackTrace();
  					}
  				} else {
  					pts = motion.getPositions(0, ((double)t)/(tPoints-1), pointsArray);
  				}
  				newPoints.add(pts);
  			}
  		} else {
  			double [] times = new double [tPoints-1];
  			for (int t = 1; t < tPoints; t++){
  				newPoints.add(new ArrayList<PointND>());
  				times[t-1] = ((double)t)/(tPoints-1);
  			}
  			for (int i = 0; i < initialPoints.size(); i++){
  				ArrayList<PointND> pts = motion.getPositions(initialPoints.get(i), 0, times);
  				for (int t = 1; t < tPoints; t++){
  					newPoints.get(t-1).add(pts.get(t-1));
  				}	
  			}
  		}
  		for (int t = 1; t < tPoints; t++) {
  			timeVariantShapes.add(new SurfaceUniformCubicBSpline(newPoints.get(t-1), timeInvariant.getUKnots(), timeInvariant.getVKnots()));
  		}
  		// Set properties for timeVariantShapes
  		for (int t = 0; t < timeVariantShapes.size(); t++) {
  			timeVariantShapes.get(t).setTitle(timeInvariant.getTitle());
  			timeVariantShapes.get(t).setClockwise(timeInvariant.isClockwise());
  		}
  		init();
  	}
  	
  	public TimeVariantSurfaceBSpline(TimeVariantSurfaceBSpline tvs){
  		super(tvs);
  		title = (tvs.title!=null) ? new String(tvs.title) : null;
  		tPoints = tvs.tPoints;
  		clockwise = tvs.clockwise;
  		
  		if (tvs.timeVariantShapes != null){
  			Iterator<SurfaceBSpline> it = tvs.timeVariantShapes.iterator();
  			timeVariantShapes = new ArrayList<SurfaceBSpline>();
  			while (it.hasNext()) {
  				SurfaceBSpline spl = it.next();
  				timeVariantShapes.add((spl!=null) ? (SurfaceBSpline) spl.clone() : null);
  			}
  		}
  		else{
  			timeVariantShapes = null;
  		}
  		
  		timeSpline = (tvs.timeSpline!=null) ? (BSpline)tvs.timeSpline.clone() : null;
  	}
  
  	private void init(){
  		max = new PointND(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE);
  		min = new PointND(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
  		for (AbstractShape spline: timeVariantShapes) {
  			max.updateIfHigher(spline.getMax());
  			min.updateIfLower(spline.getMin());
  		}
  		tPoints = timeVariantShapes.size();
  		setTitle(timeVariantShapes.get(0).getTitle());
  		createTimeSpline();
  		clockwise = timeVariantShapes.get(0).clockwise; // assumes all time steps have same orientation
  	}
  
  	private void createTimeSpline(){
  		SimpleVector knotVector = new SimpleVector(4+timeVariantShapes.size());
  		ArrayList<PointND> controlPoints = new ArrayList<PointND>();
  		int count = 0;
  		for (count = 0; count < 4; count++){
  			knotVector.setElementValue(count, 0);
  			controlPoints.add(new PointND(0, 0, 0));
  		}
  		for (count = 4; count < timeVariantShapes.size(); count++){
  			knotVector.setElementValue(count, ((count - 3.0) / (timeVariantShapes.size() - 3.0)));
  			controlPoints.add(new PointND(1, 1, 1));
  		}
  		for (count = timeVariantShapes.size(); count < timeVariantShapes.size() + 4; count++){
  			knotVector.setElementValue(count, 1.0);
  		}
  		timeSpline = new UniformCubicBSpline(controlPoints, knotVector);
  	}
  	
  	public int getNumberOfTimePoints(){
  		return tPoints;
  	}
  
  	public ArrayList<PointND> getControlPoints(int time){
  		return timeVariantShapes.get(time).getControlPoints();
  	}
  
  	@Override
  	public void applyTransform(Transform t) {
  		for (AbstractShape spline: timeVariantShapes){
  			spline.applyTransform(t);
  		}
  	}
  
  	@Override
  	public int getDimension() {
  		return timeVariantShapes.get(0).getDimension();
  	}
  
  	@Override
  	public PointND evaluate(PointND u) {
  		return evaluate(u.get(0), u.get(1), u.get(2));
  	}
  
  	public PointND evaluate(double u, double v, double t){
  		double [] p = new double [timeVariantShapes.get(0).getControlPoints().get(0).getDimension()];
  
  		double internal = ((t * timeSpline.getKnots().length) -3.0);
  		int numPts = timeSpline.getControlPoints().size();
  		double step = internal- Math.floor(internal);
  
  		//System.out.println(u + " " + internal);
  		double [] weights = UniformCubicBSpline.getWeights(step);
  		for (int i=0;i<4;i++){
  			double [] loc = (internal+i < 0)? ((SurfaceBSpline)timeVariantShapes.get(0)).evaluate(u, v).getCoordinates(): (internal+i>=numPts)? ((SurfaceBSpline)timeVariantShapes.get(numPts-1)).evaluate(u, v).getCoordinates() : ((SurfaceBSpline)timeVariantShapes.get((int) (internal+i))).evaluate(u, v).getCoordinates();
  			for (int j = 0; j < loc.length; j++){
  				p[j] += (loc[j] * weights[i]);
  				//p[j] = loc[j];
  			}
  		}
  		return new PointND(p);
  	}
  
  	@Deprecated
  	public PointND evaluateFull(double u, double v, double t){
  		SimpleVector point = new SimpleVector(getDimension()); 
  		for (int i = 0; i < tPoints; i++){
  			//System.out.println("Upoints: " + uPoints + " " + i);
  			double weight = timeSpline.getWeight(t, i);
  			point.add(((SurfaceBSpline)timeVariantShapes.get(i)).evaluate(u, v).getAbstractVector().multipliedBy(weight));
  		}
  		PointND revan = new PointND(point);
  		return revan;
  	}
  
  	public PointND[] getRasterPoints(double samplingU, double samplingV, double time){
  		PointND [] pts = new PointND[((int)samplingU) * ((int)samplingV)];
  		for(double i =0 ; i < samplingU; i++){
  			for (double j = 0; j < samplingV; j++){
  				PointND p = evaluate(i/samplingU, j/ samplingV, time);
  				pts[(int)(i*samplingV+j)] = p;
  			}
  		}
  		return pts;
  	}
  
  	public AbstractShape tessellateMesh(double samplingU, double samplingV, double time){
  		PointND [] pts = getRasterPoints(samplingU, samplingV, time);
  		boolean write = false;
  		BufferedWriter bwpoint = null;
  		String filename = Configuration.getGlobalConfiguration().getRegistry().get(RegKeys.XCAT_WRITE_MESH);
  		if (filename != null) {
  			String substring = Configuration.getGlobalConfiguration().getRegistry().get(RegKeys.XCAT_WRITE_MESH_SURFACE_SUBSTRING);
  			if (substring != null){
  				write = getTitle().contains(substring);
  			} 
  			if (write){
  				String splineName = getTitle().replace('*', '-');
  				try {
  					//bwpoint = new BufferedWriter(new FileWriter(filename + "_" + splineName +  "_" + time + ".points.txt"));
  					bwpoint = new BufferedWriter(new FileWriter(filename + "_" + splineName +  "_" + tesselationCount + ".points.txt"));
  					tesselationCount++;
  					bwpoint.write(splineName+"\n");
  				} catch (IOException e) {
  					// TODO Auto-generated catch block
  					e.printStackTrace();
  					throw new RuntimeException(e);
  				}
  			}
  		}
  		CompoundShape superShape = new CompoundShape();
  		ArrayList<PointND> lastSlice = new ArrayList<PointND>();
  		ArrayList<PointND> firstSlice = new ArrayList<PointND>();
  		for(double i =1 ; i < samplingU; i++){
  			CompoundShape mesh =  new CompoundShape();
  			double lasti = i - 1;
  			if (lasti < 0) lasti = samplingU - 1;
  			for (double j = 0; j < samplingV; j++){
  				double lastj = j - 1;
  				if (lastj < 0) lastj = samplingV - 1;
  				if (i == samplingU-1) lastSlice.add(pts[(int)(i*samplingV+j)]);
  				if (i == 1) firstSlice.add(pts[(int)((i-1)*samplingV+j)]);
  				try{
  					Triangle t1 = new Triangle(pts[(int)(lasti*samplingV+lastj)] ,
  							pts[(int)(i*samplingV+lastj)] ,
  							pts[(int)(lasti*samplingV+j)]);
  					t1.setName(i+"x"+j+"u");
  					if (write){
  						PointND pt = pts[(int)(lasti*samplingV+lastj)];
  						bwpoint.write(lasti+"x"+lastj+"\t"+i+"x"+j+"u\t"+pt.get(0)+"\t"+pt.get(1)+"\t"+pt.get(2)+"\n");
  						pt = pts[(int)(i*samplingV+lastj)];
  						bwpoint.write(i+"x"+lastj+"\t"+i+"x"+j+"u\t"+pt.get(0)+"\t"+pt.get(1)+"\t"+pt.get(2)+"\n");
  						pt = pts[(int)(lasti*samplingV+j)];
  						bwpoint.write(lasti+"x"+j+"\t"+i+"x"+j+"u\t"+pt.get(0)+"\t"+pt.get(1)+"\t"+pt.get(2)+"\n");
  					}
  					mesh.add(t1);
  				} catch (Exception e){
  					if (!e.getLocalizedMessage().contains("direction vector")){
  						System.out.println(e.getLocalizedMessage() +" "+ i + " " + j + " " + lasti + " " + lastj);
  					}
  				}
  				try{
  
  					Triangle t2 = new Triangle(pts[(int)(i*samplingV+lastj)] ,
  							pts[(int)(lasti*samplingV+j)] ,
  							pts[(int)(i*samplingV+j)]);
  					t2.setName(i+"x"+j+"l");
  					if (write){
  						PointND pt = pts[(int)(i*samplingV+lastj)];
  						bwpoint.write(i+"x"+lastj+"\t"+i+"x"+j+"l\t"+pt.get(0)+"\t"+pt.get(1)+"\t"+pt.get(2)+"\n");
  						pt = pts[(int)(lasti*samplingV+j)];
  						bwpoint.write(lasti+"x"+j+"\t"+i+"x"+j+"l\t"+pt.get(0)+"\t"+pt.get(1)+"\t"+pt.get(2)+"\n");
  						pt = pts[(int)(i*samplingV+j)];
  						bwpoint.write(i+"x"+j+"\t"+i+"x"+j+"l\t"+pt.get(0)+"\t"+pt.get(1)+"\t"+pt.get(2)+"\n");
  					}
  					mesh.add(t2);
  				} catch (Exception e){
  					if (!e.getLocalizedMessage().contains("direction vector")){
  						System.out.println(e.getLocalizedMessage() +" "+ i + " " + j + " " + lasti + " " + lastj);
  					}
  				}
  			}
  			superShape.add(mesh);
  		}
  		superShape.addAll(General.createTrianglesFromPlanarPointSet(lastSlice, "firstSlice", bwpoint));
  		superShape.addAll(General.createTrianglesFromPlanarPointSet(firstSlice, "lastSlice", bwpoint));
  		if (write){
  			try {
  				bwpoint.flush();
  				bwpoint.close();
  			} catch (IOException e) {
  				// TODO Auto-generated catch block
  				e.printStackTrace();
  			}
  		}
  		//System.out.println("Triangles: " + superShape + " " + superShape.getInternalDimension() + " " + superShape.size());
  		superShape.setName(getTitle());
  		if (write){
  			try {
  				String splineName = getTitle().replace('*', '-');
  				//BufferedWriter bw = new BufferedWriter(new FileWriter(filename + "_" + splineName +  "_" + time + ".txt"));
  				BufferedWriter bw = new BufferedWriter(new FileWriter(filename + "_" + splineName +  "_" + tesselationCount1 + ".txt"));
  				tesselationCount1++;
  				bw.write(splineName+"\n");
  				for (int i = 0; i < superShape.getInternalDimension(); i++){
  					AbstractShape subShape = superShape.get(i);
  					if (subShape instanceof CompoundShape) {
  						CompoundShape cmpShape = (CompoundShape) subShape;
  						for (int j=0;j <cmpShape.size(); j++){
  							Triangle tri = (Triangle) cmpShape.get(j);
  							bw.write(tri.toString()+"\n");
  						}
  					} else {
  						bw.write(subShape.toString()+"\n");
  					}
  				}
  				bw.close();
  			} catch (IOException e) {
  				// TODO Auto-generated catch block
  				e.printStackTrace();
  			}
  		}
  		return superShape;
  	}
  
  	@Override
  	public int getInternalDimension() {
  		return 3;
  	}
  
  	@Override
  	public PointND[] getRasterPoints(int number) {
  		// TODO Auto-generated method stub
  		return null;
  	}
  
  	public PointND[] getRasterPoints(int number, double time){
  		int root  = (int) Math.sqrt(number);
  		PointND [] points = new PointND[root*root];
  		for (double u =0; u < root; u++){
  			for (double v = 0; v < root; v++){
  				points[(int)((v*root)+u)] = evaluate(u/root, v/root, time);
  			}
  		}
  		return points;
  	}
  
  	@Override
  	public ArrayList<PointND> intersect(AbstractCurve other) {
  		throw new RuntimeException("Not yet implemented");
  	}
  
  	/**
  	 * @param title the title to set
  	 */
  	public void setTitle(String title) {
  		this.title = title;
  	}
  
  	/**
  	 * @return the title
  	 */
  	public String getTitle() {
  		return title;
  	}
  
  	@Override
  	public boolean isBounded() {
  		return true;
  	}
  
  	/**
  	 * Returns a binary representation of a time variant surface spline:
  	 * <pre>
  	 * type
  	 * total size in float values
  	 * # of surface splines
  	 * ID
  	 * timeSpline
  	 * surfaceSplines
  	 * </pre>
  	 * @return the binary representation for use with OpenCL
  	 */
  	public float[] getBinaryRepresentation() {
  		float type = BSpline.SPLINE3DTO3D;
  		float tPoints = this.tPoints;
  		float ID = BSpline.getID(title);
  		float [] timeSpline = this.timeSpline.getBinaryRepresentation();
  		float [][] surfaceSplines = new float [this.tPoints][];
  		int timeVariantsLength = 0;
  		for (int i = 0; i < this.tPoints; i++){
  			surfaceSplines[i] = this.timeVariantShapes.get(i).getBinaryRepresentation();
  			timeVariantsLength += surfaceSplines[i].length;
  		}
  		int totalsize = 4 + timeSpline.length + timeVariantsLength;
  		float [] binary = new float [totalsize];
  		binary [0] = type;
  		binary [1] = totalsize;
  		binary [2] = ID;
  		binary [3] = tPoints;
  		int index = 4;
  		for (int i = 0; i < timeSpline.length; i++){
  			binary[index] = timeSpline[i];
  			index++;
  		}
  		for (int i=0; i<surfaceSplines.length; i++){
  			for (int j = 0; j< surfaceSplines[i].length; j++){
  				binary[index] = surfaceSplines[i][j];
  				index ++;
  			}
  		}
  		return binary;
  	}
  
  	public ArrayList<SurfaceBSpline> getSplines() {
  		return timeVariantShapes;
  	}
  
  	@Override
  	public PointND evaluate(double u, double v) {
  		return evaluate(u, v, 0);
  	}
  
  	@Override
  	public AbstractShape tessellate(double accuracy) {
  		return tessellateMesh(TessellationUtil.getSamplingU(this), TessellationUtil.getSamplingV(this), 0);
  	}
  
  	@Override
  	public AbstractShape clone() {
  		return new TimeVariantSurfaceBSpline(this);
  	}
  }
  