/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.catalog;

import java.awt.Desktop;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.ResourceBundle;
import java.util.TimeZone;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.schudt.javafx.controls.calendar.DatePicker;
import fr.tlse.irap.pt.client.component.DialogComponent;
import fr.tlse.irap.pt.client.component.IDialogComponent;
import fr.tlse.irap.pt.client.controller.common.FilterKeyEvent;
import fr.tlse.irap.pt.client.controller.common.Formatter;
import fr.tlse.irap.pt.client.controller.common.Item;
import fr.tlse.irap.pt.client.controller.common.MapMenu;
import fr.tlse.irap.pt.client.controller.common.SpaceElementCollection;
import fr.tlse.irap.pt.client.controller.dialogbox.DialogBox;
import fr.tlse.irap.pt.client.controller.tools.common.DesktopBrowser;
import fr.tlse.irap.pt.client.controller.tools.externalLinks.ExternalLinks;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.pt.client.services.constant.CONFIG;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.tool.corotation.CirCorotation;
import fr.tlse.irap.tool.corotation.CirEnd;
import fr.tlse.irap.tool.corotation.CorotationCatalog;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Cursor;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.MenuButton;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;

public class CorotationCatalogComponent extends AnchorPane implements Initializable, IDialogComponent, ICatalog {

	private DialogComponent dialog;

	protected static final Logger log = LoggerFactory.getLogger(CorotationCatalogComponent.class);
	protected static final String ASCII_HDR1 	= ""
			+ "Computation done by CDPP/CNES Propagation Tool " + CONFIG.PROJECT_VERSION + " available at http://propagationtool.cdpp.eu";
	protected static final String ASCII_HDR2 	= ""
			+ "from HELCATS catalogues available at https://www.helcats-fp7.eu/catalogues/wp5_cat.html";
	protected static final String XML_HDR		= ""
			+ "version=\"1.3\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
			+ "xmlns=\"http://www.ivoa.net/xml/VOTable/v1.3\"\n"
			+ "xmlns:stc=\"http://www.ivoa.net/xml/STC/v1.30\"";

	@FXML protected GridPane gridPane;
	@FXML protected GridPane startTime;
	@FXML protected final DatePicker datePicker = new DatePicker("startTime");
    @FXML private TextField timeInterval;
    @FXML private ChoiceBox<String> unitTimeInt;
    @FXML private MenuButton targetMenuBtn;
    @FXML private TextField target;
    @FXML private ChoiceBox<String> observer;    
    @FXML private ChoiceBox<String> outputFormat;    
    @FXML private TextField spread;
    @FXML private Button okBtn;

	private static GregorianCalendar gc;
	private MapMenu targetMenu;
	private Date startDate;
	private int targetId;

    private CorotationCatalog catalog;

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		// init date picker
		datePicker.selectedDateProperty().set(new Date());
		addDatePicker();

		// init combo boxes
		ExternalLinks.initUnitTime(unitTimeInt);

		timeInterval.addEventFilter(KeyEvent.ANY, new FilterKeyEvent(
				timeInterval, 4, 1, 9999, Formatter.getFormatter(), new SimpleBooleanProperty(), timeInterval.focusedProperty()));

		spread.addEventFilter(KeyEvent.ANY, new FilterKeyEvent(
				spread, 22.5, 0, 180.0, Formatter.getFormatter(), new SimpleBooleanProperty(), spread.focusedProperty()));

		// Init button state
		okBtn.setDisable(true);
		okBtn.setOnMouseClicked(new EventHandler<Event>() {
			@Override
			public void handle(Event event) {
				getCatalog();
			}
		});

		// init targets context menu
		targetMenu = new MapMenu();
		loadMenu();
		// Listen for space elements list change
		SpaceElementCollection.getHasChanged().addListener(new ChangeListener<Boolean>() {
			@Override
			public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
				if (!newValue) {
					loadMenu();
				}
			}
		});

		EventHandler<MouseEvent> evtPlanet = new EventHandler<MouseEvent>() {
			@Override
			public void handle(MouseEvent event) {
				event.consume();
				if (event.getButton() == MouseButton.PRIMARY) {
					targetMenu.show(targetMenuBtn, event.getScreenX(), event.getScreenY());
					event.consume();
				}
				if (event.getButton() == MouseButton.SECONDARY) {
					targetMenu.hide();
					event.consume();
				}
			}
		}; 

		targetMenuBtn.getItems().clear();
		targetMenuBtn.setOnMouseClicked(evtPlanet);
		target.setOnMouseClicked(evtPlanet);

	}

	/**
	 * load context menu
	 */
	public void loadMenu() {
		List<Item> menus = new ArrayList<Item>();
		menus.add(CatalogItemBuilder.getAllItem(targetMenu.getMap(), this));
		menus.add(CatalogItemBuilder.getPlanetItem(targetMenu.getMap(), this));
		menus.add(CatalogItemBuilder.getProbeItem(targetMenu.getMap(), this));
		menus.add(CatalogItemBuilder.getCometItem(targetMenu.getMap(), this));

		target.setText("");
		targetMenu.getItems().clear();
		targetMenu.addAll(menus);
	}

    @FXML
    public void onClickOK() {
		getCatalog();
    }

	public void initComponentWith(Date startDate) {
		this.startDate = startDate;
		initComponent();
		dialog.show();
	}

	private void initComponent() {
		// set default values
		unitTimeInt.setValue(unitTimeInt.getItems().get(3));
		// init start date
		datePicker.selectedDateProperty().set(startDate);
		// init other fields
		observer.setValue(observer.getItems().get(0));
		outputFormat.setValue(outputFormat.getItems().get(0));
		target.setText("");
		// Init button state
		okBtn.setDisable(true);
		// load contextual menu
		//loadMenu();
		
	}

	@Override
	public void runChooseElement(SpaceElement element) {
		target.setText(element.getName());
		targetId = element.getId();
		okBtn.setDisable(false);
	}

	@Override
	public void runChooseAll() {
		target.setText("ALL");
		targetId = 0;
		okBtn.setDisable(false);
	}

	public void setDialog(DialogComponent dialog) {
		this.dialog = dialog;
	}

	public DialogComponent getDialog() {
		return dialog;
	}

	protected void addDatePicker() {
		ExternalLinks.setStyleDate(datePicker);
		startTime.add(datePicker, 0, 0);
	}

	private void getCatalog() {
		final Task<Object> getCatalogWorker = getCatalogWorker();

		getCatalogWorker.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) {
							// thread is done with success
							endCursor();
					    	if (catalog.getCatalog().size() > 0) {
					    		// Show Corot catalog in XML editor 
					    		System.out.println("Found catalog");
					    		openCorotationCatalog();
					    	}
							else {
								DialogBox.warn("No catalog found", dialog);
							}
							
						} else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
							// thread is failed or cancelled
							endCursor();
							if (getCatalogWorker.getException() != null) {
								DialogBox.error(" Error while getting catalog.\n"+ getCatalogWorker.getException().getMessage());
							}
							else {
								DialogBox.error(" Error while getting catalog.\n ");
							}
						}
					}
				});
		new Thread(getCatalogWorker).start();
		waitCursor();
	}

	private Task<Object> getCatalogWorker() {
		return new Task<Object>() {
			@Override
			protected Object call() throws Exception {
				startDate = datePicker.selectedDateProperty().get();
				Date t1 = new Date();
				t1.setTime(startDate.getTime() - (getInterval() * 1000));
				Date t2 = new Date();
				t2.setTime(startDate.getTime() + (getInterval() * 1000));
				catalog = ServiceFactory.getToolService().getCorotationCatalog(t1, t2, targetId, getObserver(), getSpread());
				return true;
			}
		};
	}

	/**
	 * get interval in seconds
	 * @return
	 */
	private long getInterval() {
		long ti = Formatter.toDouble(timeInterval.getText()).longValue();
		return ExternalLinks.computeTimeInSeconds(ti, unitTimeInt.getValue());
	}

	private double getSpread() {
		return Formatter.toDouble(spread.getText());
	}

	private String getObserver() {
		return observer.getValue();
	}
	
	private String getOutputFormat() {
		return outputFormat.getValue();
	}

	private void waitCursor() {
		this.setDisable(true);
		this.getScene().setCursor(Cursor.WAIT);
	}

	private void endCursor() {
		this.setDisable(false);
		this.getScene().setCursor(null);
	}

	/**
	 * Open CIR corotationcatalog to xml format (VOTABLE) and open it with the current editor
	 */
	private void openCorotationCatalog() {
		try {
			if (getOutputFormat().equals("VOTABLE")) {
				openFile(corotationToXml());
			}
			else {
				openFile(corotationToAscii());
			}
			
		} catch (IOException e) {
			e.printStackTrace();
			log.warn(e.getMessage());
			DialogBox.error(" Error while dowloading CIRs corotation catalog.\n");
		}
	}

	private void openFile(final File tmpFile) {
		final Task<Object> openFileWorker = openFileWorker(tmpFile);

		openFileWorker.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) {
							endCursor();
						} else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
							endCursor();
							DialogBox.error(" Error while opening corotation catalog of CIRs.\n");
							if (openFileWorker.getException() != null) {
								log.warn(openFileWorker.getException().getMessage());
							}
						}
					}
				});
		new Thread(openFileWorker).start();
		waitCursor();
	}

	private Task<Object> openFileWorker(final File file) {
		return new Task<Object>() {
			@Override
			protected Object call() throws Exception {
				try {
					Desktop.getDesktop().edit(file);
				} catch (Exception e) {
					DesktopBrowser.openURL("File://" + file.getPath());
				} 
				return true;
			}
		};
	}

	/**
	 * Download CIR corotation catalog to xml format (VOTABLE)
	 * @throws IOException 
	 */
	protected File corotationToXml() throws IOException {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");

		File file = File.createTempFile("tmpCorotCat", ".xml");
		log.info("Temporary export file is " + file.getPath());
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF8"));
		
		// header
		bw.write("<?xml version=\"1.0\"?>\n");
		bw.write("<VOTABLE " + XML_HDR + " >\n");
		bw.write("<RESOURCE name=\"Corotation catalog\">\n");
		bw.write("  <TABLE name=\"Corotation catalog\">\n");
		// CIR fields description
		bw.write("\t<FIELD name=\"cirID\" datatype=\"char\" arraysize=\"8\"></FIELD>\n");
		bw.write("\t<FIELD name=\"scInsitu\" datatype=\"char\" arraysize=\"8\"></FIELD>\n");
		bw.write("\t<FIELD name=\"startTime\" datatype=\"char\" xtype=\"dateTime\" ucd=\"time.epoch\" arraysize=\"*\"></FIELD>\n");
		bw.write("\t<FIELD name=\"vSolarWind\" datatype=\"float\" unit=\"km/s\"></FIELD>\n");
		bw.write("\t<FIELD name=\"spread\" datatype=\"float\" unit=\"deg\"></FIELD>\n");
		bw.write("\t<FIELD name=\"phiStart\" datatype=\"float\" unit=\"deg\"></FIELD>\n");
		bw.write("\t<FIELD name=\"target\" datatype=\"char\" arraysize=\"30\"></FIELD>\n");
		bw.write("\t<FIELD name=\"endTime\" datatype=\"char\" xtype=\"dateTime\" ucd=\"time.epoch\" arraysize=\"*\"></FIELD>\n");
		bw.write("\t<FIELD name=\"phiEnd(endTime)-phiStart(startTime)\" datatype=\"float\" unit=\"deg\"></FIELD>\n");
		bw.write("\t<FIELD name=\"r(End-Sun)\" datatype=\"float\" unit=\"AU\"></FIELD>\n");
		bw.write("\t<FIELD name=\"impacted\" datatype=\"boolean\"></FIELD>\n");
		
		bw.write("\t<DATA>\n");
		bw.write("\t  <TABLEDATA>\n");

		for (CirCorotation cirCorot : catalog.getCatalog()) {
			String cirId = "CIR_" + sdf.format(cirCorot.getStartTime().getTime());
			String startTime = Formatter.toSdf(cirCorot.getStartTime());
			String speed = Formatter.toDecimal(cirCorot.getSpeed());
			String extent = Formatter.toDecimal(getSpread());
			String phiStart = Formatter.toDecimal(cirCorot.getLongitude());
			for (CirEnd cirEnd : cirCorot.getCirEnds()) {
				// input CIR formatted parameters 
				bw.write("\t\t<TR><TD>" + cirId + "</TD><TD>" + getObserver() + "</TD><TD>" + startTime + "</TD>");
				bw.write("<TD>" + speed + "</TD><TD>" + extent + "</TD><TD>" + phiStart + "</TD>");
				// result data
				bw.write("<TD>" + cirEnd.getName() + "</TD>");
				bw.write("<TD>" + Formatter.toSdf(getCalendarFrom(cirEnd.getEndTime())) + "</TD>");
				bw.write("<TD>" + Formatter.toDecimal(cirEnd.getLongSep()) + "</TD>");
				bw.write("<TD>" + Formatter.toDecimal(cirEnd.getDistEnd()) + "</TD>");
				bw.write("<TD>" + cirEnd.isImpacted() + "</TD>");
				bw.write("</TR>\n");
			}
		}
		bw.write("\t  </TABLEDATA>\n");
		bw.write("\t</DATA>\n");
		bw.write("  </TABLE>\n");

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

		return file;
	}

//	protected File corotationToXmlOld() throws IOException {
//		SimpleDateFormat cdf = new SimpleDateFormat("yyyyMMddHHmm");
//
//		File file = File.createTempFile("tmpCorotCat", ".xml");
//		log.info("Temporary export file is " + file.getPath());
//		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF8"));
//		
//		// header
//		bw.write("<?xml version=\"1.0\"?>\n");
//		bw.write("<VOTABLE " + XML_HDR + " >\n");
//		bw.write("<RESOURCE name=\"Corotation catalog\">\n");
//		for (CirCorotation cirCorot : catalog.getCatalog()) {
//			// input CIR 
//			bw.write("  <TABLE name=\"CIR_" + cdf.format(cirCorot.getStartTime().getTime()) + "\">\n");
//			bw.write("\t<DESCRIPTION> " + getDescriptionCIR(cirCorot) + ", " + getXmlPropCIR(cirCorot)+ "</DESCRIPTION>\n");
//			// result data fields description
//			bw.write("\t<FIELD name=\"target\" datatype=\"char\" arraysize=\"30\"></FIELD>\n");
//			bw.write("\t<FIELD name=\"endTime\" datatype=\"char\" xtype=\"dateTime\" ucd=\"time.epoch\" arraysize=\"*\"></FIELD>\n");
//			bw.write("\t<FIELD name=\"phiEnd(endTime)-phiStart(startTime)\" datatype=\"float\" unit=\"deg\"></FIELD>\n");
//			bw.write("\t<FIELD name=\"r(End-Sun)\" datatype=\"float\" unit=\"AU\"></FIELD>\n");
//			bw.write("\t<FIELD name=\"impacted\" datatype=\"boolean\"></FIELD>\n");
//
//			// result data
//			bw.write("\t<DATA>\n");
//			bw.write("\t  <TABLEDATA>\n");
//			for (CirEnd cirEnd : cirCorot.getCirEnds()) {
//				bw.write("\t\t<TR><TD>" + cirEnd.getName() + "</TD> ");
//				bw.write("<TD>" + Formatter.toSdf(getCalendarFrom(cirEnd.getEndTime())) + "</TD> ");
//				bw.write("<TD>" + Formatter.toDecimal(cirEnd.getLongSep()) + "</TD> ");
//				bw.write("<TD>" + Formatter.toDecimal(cirEnd.getDistEnd()) + "</TD> ");
//				bw.write("<TD>" + cirEnd.isImpacted() + "</TD> ");
//				bw.write("</TR>\n");
//			}
//			bw.write("\t  </TABLEDATA>\n");
//			bw.write("\t</DATA>\n");
//			bw.write("  </TABLE>\n");
//		}
//		bw.write("</RESOURCE>\n");
//
//		// Trailer
//		bw.write("</VOTABLE>\n");
//		bw.close();
//
//		return file;
//	}

	/**
	 * Download CIR corotation catalog to ascii format
	 * @throws IOException 
	 */
	protected File corotationToAscii() throws IOException {
		SimpleDateFormat cdf = new SimpleDateFormat("yyyyMMddHHmm");

		File file = File.createTempFile("tmpCorotCat", ".txt");
		log.info("Temporary export file is " + file.getPath());
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF8"));
		// Header
		bw.write("#"); bw.newLine();
		bw.write("# Corotation Catalog"); 
		bw.newLine();
		bw.write("# " + ASCII_HDR1); bw.newLine();
		bw.write("# " + ASCII_HDR2); bw.newLine();
		bw.write("#"); 
		bw.newLine(); bw.newLine();
		for (CirCorotation cirCorot : catalog.getCatalog()) {
			// input CIR
			bw.write("CIR_" + cdf.format(cirCorot.getStartTime().getTime()) + " : "
//					+ getDescriptionCIR(cirCorot) + ", " + getAsciiPropCIR(cirCorot));
				+ "Source=" + cirCorot.getStartElement().getName()
				+ ", obs=" + getObserver()
				+ ", startTime=" + Formatter.toSdf(cirCorot.getStartTime())
				+ ", speed=" + Formatter.toDecimal(cirCorot.getSpeed()) + "(km/s)" 
				+ ", spread=" + Formatter.toDecimal(getSpread()) + "°"
				+ ", φ=" + Formatter.toDecimal(cirCorot.getLongitude()) + "°"); 

			
			
			bw.newLine();
			bw.newLine();
			// result data fields description
			bw.write("Target\t\t\t\t\tt'\t\tφEnd(t') - φStart(t)\trSun\tImpacted");  
			bw.newLine();
			bw.write("\t\t\t\t\t\t\t\t(°)\t\t(AU)");  
			bw.newLine();
			// result data
			boolean isComet = false;
			for (CirEnd cirEnd : cirCorot.getCirEnds()) {
				if (cirCorot.getCirEnds().size() > 1) {
					// All targets
					if (cirEnd.getId() == 199) {
						bw.write("Planets");
						bw.newLine();
						isComet = false;
					}
					else if (cirEnd.getId() == -236) {
						bw.write("Probes");
						bw.newLine();
						isComet = false;
					}
					else if (cirEnd.getId() > 1000000 && !isComet) {
						bw.write("Comets");
						bw.newLine();
						isComet = true;
					}
				}
				else {  
					// 1 target 
					bw.newLine();
				}
				
				bw.write(cirEnd.getName() + "\t");
				if (cirEnd.getName().length() < 8) {
					bw.write("\t");
				}
				if (cirEnd.getName().length() < 16) {
					bw.write("\t");
				}
				if (cirEnd.getName().length() < 24) {
					bw.write("\t");
				}
				bw.write(Formatter.toSdf(getCalendarFrom(cirEnd.getEndTime())) + "\t\t");
				bw.write(Formatter.toDecimal(cirEnd.getLongSep()) + "\t\t");
				bw.write(Formatter.toDecimal(cirEnd.getDistEnd()) + "\t");
				bw.write(""+cirEnd.isImpacted());
				bw.newLine();
			}
			bw.newLine();
		}
		bw.close();

		return file;
	}

	private static GregorianCalendar getCalendarFrom(long timeInMilli) {
		if (gc == null) {
	        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
			gc = new GregorianCalendar();
		}
		gc.setTimeInMillis(timeInMilli);
		return gc;
	}
	
//	private String getDescriptionCIR(CirCorotation c) {
//		String desc = "Source=" + c.getStartElement().getName()
//				+ ", obs=" + getObserver()
//				+ ", startTime=" + Formatter.toSdf(c.getStartTime())
//				+ ", speed=" + Formatter.toDecimal(c.getSpeed()) + "(km/s)"; 
//		return desc;
//	}
//
//	private String getAsciiPropCIR(CirCorotation c) {
//		String prop = "spread=" + Formatter.toDecimal(getSpread()) + "°"
//				+ ", φ=" + Formatter.toDecimal(c.getLongitude()) + "°"; 
//		return prop;
//	}
//
//	private String getXmlPropCIR(CirCorotation c) {
//		String prop = "spread=" + Formatter.toDecimal(getSpread()) + "(deg)"
//				+ ", phi=" + Formatter.toDecimal(c.getLongitude()) + "(deg)"; 
//		return prop;
//	}

}
