/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Version : 07/04/2016 : SCA : V2.2 (PSWS-H2020)
 *  Add comets
 * Version : 17/10/2018 : SCA : V2.2.5
 *  Redmine #6119 
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.timetable;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

import fr.tlse.irap.pt.client.controller.common.Formatter;
import fr.tlse.irap.pt.client.controller.common.SpaceElementCollection;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.spaceElement.Comet;
import fr.tlse.irap.spaceElement.Planet;
import fr.tlse.irap.spaceElement.Probe;
import fr.tlse.irap.spaceElement.Sun;
import fr.tlse.irap.tool.sep.Sep;
import fr.tlse.irap.tool.sep.SepEnd;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.paint.Color;
import javafx.util.Callback;

public class TimeTableSepController extends TimeTableController {

	protected TableColumn<SepProperty, String> targetCol;
	protected TableColumn<SepProperty, String> tEndCol;
	protected TableColumn<SepProperty, String> dtMinLCol;
	protected TableColumn<SepProperty, String> dtMaxLCol;
	protected TableColumn<SepProperty, String> deltaLongCol;
	protected TableColumn<SepProperty, String> spiralLengthCol;
	protected TableColumn<SepProperty, String> spreadCol;
	protected TableColumn<SepProperty, String> longSourceCol;
	protected TableColumn<SepProperty, String> particleCol;
	protected TableColumn<SepProperty, String> energyCol;

	Boolean currentIsInsideSpiral = false;

	private SepTimeTable timeTable = new SepTimeTable();
	private SepTimeTable timeTableRef = new SepTimeTable();
	private Sep currentModel;

	private Thread fillTimeThread;
	private boolean isCancelledThread;
	private boolean isComputed; 

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public TimeTableSepController() {
		super();
		buildColumns();
			
		final TableColumn[] columns = {targetCol, tEndCol, dtMinLCol, dtMaxLCol, deltaLongCol, spiralLengthCol, spreadCol, longSourceCol, particleCol, energyCol};
		tableViewRoot.getColumns().setAll(columns);
		
		// Disable the columns re-ordering
		tableViewRoot.getColumns().addListener(new ListChangeListener() {
			public boolean suspended;

			@Override
			public void onChanged(Change change) {
				change.next();
				if (change.wasReplaced() && !suspended) {
					this.suspended = true;
					tableViewRoot.getColumns().setAll(columns);
					this.suspended = false;
				}
			}
		});
		
		// Sets the value of the property items with the timetable model
		((TableView<SepProperty>)tableViewRoot).setItems(timeTable.getProperties());

	}

	/**
	 * Build table table columns
	 */
	private void buildColumns() {

		// target column
		targetCol = new TableColumn<SepProperty, String>(timeTable.TARGET);
		targetCol.setMinWidth(WM_COL);
		targetCol.setSortable(false);
		targetCol.setResizable(true);
		// Custom how to show data for all cells in this column.
		targetCol.setCellFactory(getCustomCellFactory());
		// Specify how to populate all cells in this column.
		targetCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					currentIsInsideSpiral = param.getValue().getIsInsideSpiral();
					return new SimpleStringProperty(param.getValue().getTarget());
				} else {
					return new SimpleStringProperty("<no name>");
				}
			}
		});

		
		// endTime column
		tEndCol = new TableColumn<SepProperty, String>();
		tEndCol.setText(timeTable.TIME_END);
		tEndCol.setMinWidth(WL_COL);
		tEndCol.setSortable(false);
		tEndCol.setResizable(true);
		tEndCol.setCellFactory(getCustomCellFactory());
		tEndCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getEndTime());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});

		// deltaTimeMinL column
		dtMinLCol = new TableColumn<SepProperty, String>();
		dtMinLCol.setText(timeTable.DELTA_TMINL);
		dtMinLCol.setMinWidth(WS_COL);
		dtMinLCol.setSortable(false);
		dtMinLCol.setResizable(true);
		dtMinLCol.setCellFactory(getCustomCellFactory());
		dtMinLCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getDeltaTimeMinL());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});

		// deltaTimeMaxL column
		dtMaxLCol = new TableColumn<SepProperty, String>();
		dtMaxLCol.setText(timeTable.DELTA_TMAXL);
		dtMaxLCol.setMinWidth(WS_COL);
		dtMaxLCol.setSortable(false);
		dtMaxLCol.setResizable(true);
		dtMaxLCol.setCellFactory(getCustomCellFactory());
		dtMaxLCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getDeltaTimeMaxL());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});

		// deltaLong Start/End Column
		deltaLongCol = new TableColumn<SepProperty, String>();
		deltaLongCol.setText(timeTable.DELTA_LONG_2L);
		deltaLongCol.setMinWidth(WS_COL);
		deltaLongCol.setSortable(false);
		deltaLongCol.setResizable(true);
		deltaLongCol.setCellFactory(getCustomCellFactory());
		deltaLongCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getDeltaLong());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});
		
		// spiral Length column
		spiralLengthCol = new TableColumn<SepProperty, String>();
		spiralLengthCol.setText(timeTable.SPIRAL_LENGTH);
		spiralLengthCol.setMinWidth(WS_COL);
		spiralLengthCol.setSortable(false);
		spiralLengthCol.setResizable(true);
		spiralLengthCol.setCellFactory(getCustomCellFactory());
		spiralLengthCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getSpiralLength());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});

		// spreadSpiral Column
		spreadCol = new TableColumn<SepProperty, String>();
		spreadCol.setText(timeTable.SPREAD);
		spreadCol.setMinWidth(WS_COL);
		spreadCol.setSortable(false);
		spreadCol.setResizable(true);
		spreadCol.setCellFactory(getCustomCellFactory());
		spreadCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getSpread());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});

		// longSourceCol Column
		longSourceCol = new TableColumn<SepProperty, String>();
		longSourceCol.setText(timeTable.LONG_SOURCE);
		longSourceCol.setMinWidth(WS_COL);
		longSourceCol.setSortable(false);
		longSourceCol.setResizable(true);
		longSourceCol.setCellFactory(getCustomCellFactory());
		longSourceCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getLongSource());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});

		// particleCol Column
		particleCol = new TableColumn<SepProperty, String>();
		particleCol.setText(timeTable.PARTICLE);
		particleCol.setMinWidth(WS_COL);
		particleCol.setSortable(false);
		particleCol.setResizable(true);
		particleCol.setCellFactory(getCustomCellFactory());
		particleCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getParticle());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});

		// energyCol Column
		energyCol = new TableColumn<SepProperty, String>();
		energyCol.setText(timeTable.ENERGY);
		energyCol.setMinWidth(WS_COL);
		energyCol.setSortable(false);
		energyCol.setResizable(true);
		energyCol.setCellFactory(getCustomCellFactory());
		energyCol.setCellValueFactory(new Callback<CellDataFeatures<SepProperty, String>, ObservableValue<String>>() {
			@Override
			public ObservableValue<String> call(CellDataFeatures<SepProperty, String> param) {
				if (param.getValue() != null) {
					return new SimpleStringProperty(param.getValue().getEnergy());
				} else {
					return new SimpleStringProperty("<null>");
				}
			}
		});
	}

	/**
	 * Custom how to show data for all cells in this column.
	 */
	private Callback<TableColumn<SepProperty, String>, TableCell<SepProperty, String>> getCustomCellFactory() {
		return new Callback<TableColumn<SepProperty, String>, TableCell<SepProperty, String>>() {
			@Override
			public TableCell<SepProperty, String> call(TableColumn<SepProperty, String> param) {
				TableCell<SepProperty, String> cell = new TableCell<SepProperty, String>() {

					@Override
					public void updateItem(final String item, boolean empty) {
						setText(item);
						if (!empty) {
							setTextFill((currentIsInsideSpiral ? Color.RED : Color.BLACK));
						}
					}
				};
				return cell;
			}
		};
	}

    /**
     * Update current timetable with radial model 
     */
	public void updateView(Sep sep) {
		this.currentModel = sep;
		isComputed = false;
	}

    /**
     * Update current timetable view 
     */
	public void updateView() {
		if (!isComputed) {
			fillTimeTable();
			isComputed = true;
		}
	}

	/**
	 * Clear current timetable 
	 */
	public void resetView() {
		if (currentModel != null) {
			timeTable.clearProperties();
			timeTableRef.clearProperties();
		}
	}

	/**
	 * Fill timetable process launched in a dedicated thread
	 */
	private void fillTimeTable() {
		Task<Object> worker = fillTimeTableWorker();

		worker.stateProperty().addListener(new ChangeListener<Worker.State>() {
			@Override
			public void changed(ObservableValue<? extends Worker.State> observableValue, Worker.State oldState, Worker.State newState) {
				if (newState == Worker.State.SUCCEEDED || newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
					endCursor();
				}
			}
		});

		if (fillTimeThread != null && fillTimeThread.isAlive()) {
			// Cancel the current thread before launch a new thread 
			isCancelledThread = true;
		}
		fillTimeThread = new Thread(worker);
		fillTimeThread.start();
		waitCursor();
	}

	/**
	 * Fill timetable worker 
	 */
	private Task<Object> fillTimeTableWorker() {
		return new Task<Object>() {
			@Override
			protected Object call() throws Exception {
				synchronized (timeTable) {
					isCancelledThread = false;
					// Initialize the timetable content with a row of text units
					initTimeTable();
					
					if (SpaceElementCollection.getSpaceElementWrapper() != null) {
						log.info("Compute propagation for SUN");
						try {
							SepEnd sepEnd = ServiceFactory.getToolService().computeSep(currentModel, new Sun());
							fillTimeTable(getSepProperty(sepEnd));
							if (isCancelledThread) {
								log.debug("Exit current interrupted thread");
								return true;
							}
					    } catch (Exception e) {
					    	log.debug("Sun has not been computed");
					    }
						log.info("Compute propagation for probes");
						fillTimeTable(new SepProperty("Probes"));
						//for (Probe probe : SpaceElementCollection.getProbes()) {
						for (Probe probe : SpaceElementCollection.getSpaceElementWrapper().getProbes()) {
							if (probe.isDefined()) {
								try {
									SepEnd sepEnd = ServiceFactory.getToolService().computeSep(currentModel, probe);
									fillTimeTable(getSepProperty(sepEnd));
									if (isCancelledThread) {
										log.debug("Exit current interrupted thread");
										return true;
									}
							    } catch (Exception e) {
							    	log.debug("Probe has not been computed : " + probe);
							    }
							}
						}
						log.info("Compute propagation for planets");
						fillTimeTable(new SepProperty("Planets"));
						for (Planet planet : SpaceElementCollection.getPlanets()) {
							try {
								SepEnd sepEnd = ServiceFactory.getToolService().computeSep(currentModel, planet);
								fillTimeTable(getSepProperty(sepEnd));
								if (isCancelledThread) {
									log.debug("Exit current interrupted thread");
									return true;
								}
						    } catch (Exception e) {
						    	log.debug("Probe has not been computed : " + planet);
						    }
						}
						log.info("Compute propagation for comets");
						fillTimeTable(new SepProperty("Comets"));
						for (Comet comet : SpaceElementCollection.getComets()) {
							try {
								SepEnd sepEnd = ServiceFactory.getToolService().computeSep(currentModel, comet);
								fillTimeTable(getSepProperty(sepEnd));
								if (isCancelledThread) {
									log.debug("Exit current interrupted thread");
									return true;
								}
						    } catch (Exception e) {
						    	log.debug("Comet has not been computed : " + comet.getName());
						    }
						}
					}
					return true;
				}
		    }
		};
	}

	private void initTimeTable() {
		SepProperty property = new SepProperty("");

		// Set an empty property to create a row with text unit
		property.setTarget(null);
		property.setEndTime(null);
		property.setDeltaTimeMinL(UNIT_TIME_MIN);
		property.setDeltaTimeMaxL(UNIT_TIME_MIN);
		property.setDeltaLong(UNIT_LONG);
		property.setSpiralLength(UNIT_DIST);
		property.setSpread(UNIT_LONG);
		property.setLongSource(UNIT_LONG);
		property.setEnergy(UNIT_ENERGY);

		// Add current property item to the timetable model
		timeTable.clearProperties();
		timeTable.getProperties().add(property);

		// Add current property item to the timetable model ref
		timeTableRef.clearProperties();
		timeTableRef.getProperties().add(property);

	}

	/**
	 * Get a new SepProperty filled with a endModel
	 * @param endModel
	 * @return
	 */
	private SepProperty getSepProperty(SepEnd endModel)  {
    	final SepProperty property = new SepProperty(endModel.getEndSource().getLongName());
        
		property.setEndTime(Formatter.toSdf(endModel.getEndTime()));
		property.setDeltaTimeMinL(Formatter.toDecimal(endModel.getDtMinL()));
		property.setDeltaTimeMaxL(Formatter.toDecimal(endModel.getDtMaxL()));
		property.setDeltaLong(Formatter.toDecimal(endModel.getLongSep()));
		property.setSpiralLength(Formatter.toDecimal(endModel.getLength()));
		property.setSpread(Formatter.toDecimal(currentModel.getSpreadCIR()));
		property.setLongSource(Formatter.toDecimal(currentModel.getLongSourceT()));
		property.setParticle(currentModel.getParticle().toString());
		property.setEnergy(Formatter.toDecimal4D(currentModel.getParticleEnergy()));
		
		property.setIsInsideSpiral(endModel.isImpacted());
		
		return property;
	}

	/**
	 * Add a new row in TimeTable from a SepProperty
	 * If the current thread cannot fill TimeTable, the process is dedicated to the main JFX thread
	 * with a lock on TimeTable resource  
	 * @param property
	 * @throws Exception
	 */
	private void fillTimeTable(final SepProperty property) throws Exception {
	    try {
	    	// Add current property item to the timetable model
	    	timeTable.getProperties().add(property);
    		timeTableRef.getProperties().add(property);
	    	
        } catch (Exception e) {
	        e.printStackTrace();
	        log.info("Try to update timetable model in the JFX thread");
		    try {
	    		// Update timetable model in the JFX thread
	    		Platform.runLater(new Runnable() {
	    			@Override public void run() {
	    				if (!isCancelledThread) {
	    					// Add current property item to the timetable model
	    					timeTable.getProperties().add(property);
	    					timeTableRef.getProperties().add(property);
	    				}
	  	    	  	}
	  			});
	        } catch (Exception e2) {
		        e2.printStackTrace();
		        throw e2;
	        }
	    }
	}

	/**
	 * Show all target rows 
	 */
	@Override
	protected void filterAll() {
		if (!timeTableRef.isEmpty()) {
			timeTable.clearProperties();
			for (SepProperty property : timeTableRef.getProperties()) {
				timeTable.addProperty(property);
			}
		}
	}

	/**
	 * Show only impacted target rows 
	 */
	@Override
	protected void filterImpacted() {
		if (!timeTableRef.isEmpty()) {
			timeTable.clearProperties();
			for (SepProperty prop : timeTableRef.getProperties()) {
				if (prop.getEndTime() == null || prop.getEndTime().isEmpty() || prop.getIsInsideSpiral()) {
					timeTable.addProperty(prop);
				}
			}
		}
	}

	/**
	 * Export table of arrival times format xml (VOTABLE)
	 */
	@Override
	protected File tableToXml() throws IOException {

		File fileTT = File.createTempFile("tmpSepTable", ".xml");
		log.info("Temporary export file is " + fileTT.getPath());
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileTT), "UTF8"));
		// Header
		bw.write(XML_HDR);

		// Table declaration - begin
		bw.write("<RESOURCE name=\"Table of arrival times\">\n");
		bw.write("<TABLE>\n");
		bw.write("<DESCRIPTION>" + EXPORT_HDR + "\n\n" + getHeaderExport(currentModel) + "</DESCRIPTION>\n");
		// Table fields description
		bw.write("<FIELD name=\"TARGET\" datatype=\"char\" arraysize=\"10\"/>\n");
		bw.write("<FIELD name=\"t'\" datatype=\"char\" xtype=\"dateTime\" ucd=\"time.epoch\" arraysize=\"*\"/>\n");
		bw.write("<FIELD name=\"t'min(dl)\" unit=\"hrs\" datatype=\"float\" />\n");
		bw.write("<FIELD name=\"t'max(dl)\" unit=\"hrs\" datatype=\"float\" />\n");
		bw.write("<FIELD name=\"PhiEnd(t')-PhiStart(t)\" unit=\"deg\" datatype=\"float\" />\n");
		bw.write("<FIELD name=\"Length\" unit=\"AU\" datatype=\"float\" />\n");
		bw.write("<FIELD name=\"dPhiSpiral\" unit=\"°\" datatype=\"float\" />\n");
		bw.write("<FIELD name=\"PhiSpiral (source)\" unit=\"°\" datatype=\"float\" />\n");
		bw.write("<FIELD name=\"Particle\" unit=\"\" datatype=\"char\" />\n");
		bw.write("<FIELD name=\"Energy\" unit=\"MeV\" datatype=\"float\" />\n");
		bw.write("<FIELD name=\"inside Spiral\" datatype=\"boolean\" />\n");

		// Table data
		bw.write("<DATA><TABLEDATA>\n");
		for (int i = 1; i < timeTable.getProperties().size(); i++) {
			SepProperty prop = timeTable.getProperties().get(i);
			bw.write("<TR> ");
			bw.write("<TD>" + prop.getTarget() + "</TD> ");
			if (prop.getEndTime() != null) {
				bw.write("<TD>" + prop.getEndTime() + "</TD> ");
				bw.write("<TD>" + prop.getDeltaTimeMinL() + "</TD> ");
				bw.write("<TD>" + prop.getDeltaTimeMaxL() + "</TD> ");
				bw.write("<TD>" + prop.getDeltaLong() + "</TD> ");
				bw.write("<TD>" + prop.getSpiralLength() + "</TD> ");
				bw.write("<TD>" + prop.getSpread() + "</TD> ");
				bw.write("<TD>" + prop.getLongSource() + "</TD> ");
				bw.write("<TD>" + prop.getParticle() + "</TD> ");
				bw.write("<TD>" + prop.getEnergy() + "</TD> ");
				bw.write("<TD>" + prop.getIsInsideSpiral() + "</TD> ");
			}
			bw.write("</TR>\n");
		}
		bw.write("</TABLEDATA></DATA>\n");

		// Table declaration - end
		bw.write("</TABLE>\n");
		bw.write("</RESOURCE>\n");

		// Trailer
		bw.write("</VOTABLE>\n");
		bw.close();

		return fileTT;
	}

	/*
	 * Export table of arrival times format text
	 */
	@Override
	protected File tableToAscii() throws IOException {

		File fileTT = File.createTempFile("tmpSepTable", ".txt");
		log.info("Temporary export file is " + fileTT.getPath());
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileTT), "UTF8"));
		// Header
		bw.write(EXPORT_HDR); 
		bw.newLine(); bw.newLine();
		bw.write(getHeaderExport(currentModel)); 
		bw.newLine(); bw.newLine();
		// Table fields description
		bw.write(timeTable.TARGET + "\t\t\t\t\t" + 
				timeTable.TIME_END + "\t\t" + 
				timeTable.DELTA_TMINL + " " + 
				timeTable.DELTA_TMAXL + "   " + 
				timeTable.DELTA_LONG + "   " + 
				timeTable.SPIRAL_LENGTH + "     " + 
				timeTable.SPREAD + " " + 
				timeTable.LONG_SOURCE + "  " + 
				timeTable.PARTICLE + "   " + 
				timeTable.ENERGY + " " + 
				timeTable.IS_INSIDE);
		bw.newLine();
		bw.write("\t\t\t\t\t\t\t " + 
				UNIT_TIME_MIN + "     " + 
				UNIT_TIME_MIN + "\t     " + 
				UNIT_LONG + "\t    " + 
				UNIT_DIST + "\t" + 
				UNIT_LONG + "\t  " + 
				UNIT_LONG + "\t\t       " + 
				UNIT_ENERGY);
		bw.newLine();
		// Table data
		for (int i = 1; i < timeTable.getProperties().size(); i++) {
			SepProperty prop = (SepProperty) timeTable.getProperties().get(i);
			bw.write(prop.getTarget() + "\t");
			if (prop.getEndTime() != null) {
				if (prop.getTarget().length() < 8) {
					bw.write("\t");
				}
				if (prop.getTarget().length() < 16) {
					bw.write("\t");
				}
				if (prop.getTarget().length() < 24) {
					bw.write("\t");
				}
				bw.write(prop.getEndTime() + "\t  ");
				bw.write(prop.getDeltaTimeMinL() + " \t    ");
				bw.write(prop.getDeltaTimeMaxL() + "\t");
				if (prop.getDeltaTimeMaxL().length() < 8) {
					bw.write("\t");
				}
				bw.write("    ");
				bw.write(prop.getDeltaLong() + "    \t    ");
				bw.write(prop.getSpiralLength() + "     \t");
				bw.write(prop.getSpread() + "    ");
				bw.write(prop.getLongSource() + "\t    ");
				bw.write(prop.getParticle() + "\t");
				bw.write(prop.getEnergy() + "\t  ");
				bw.write(prop.getIsInsideSpiral() + "");
			}
			bw.newLine();
		}
		bw.close();
		
		return fileTT;
	}

	private String getHeaderExport(Sep sep) {
		String header = "Source=" + sep.getStartElement().getLongName()
				+ ", t=" + Formatter.toSdf(sep.getStartTime())
				+ " (hrs), speed=" + Formatter.toDecimal(sep.getSpeedSolarWind()) 
				+ " (km/s), error=" + Formatter.toDecimal(sep.getSpeedError()) + " (km/s)";
		return header;
	}
}
