/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Version : 25/08/2016 : SCA : V2.2.2
 * 	Add APIS interface
 * Version : 11/10/2018 : SCA : V2.2.5
 * 	Redmine #6069 : new AMDA WS
 * Version : 30/11/2022 : LBE : V2.6
 * DM-ID : Redmine #7316 : 30/11/2022 : logs added on AMDA getPlot call
 * Version : 17/10/2023 : LBE : V2.6
 * DM-ID : Redmine #4906 : 17/10/2023 : Changed helioviewer java access
 * VERSION : V2.12
 * DM-ID : redmine#11884 : 27/03/2025 : KHK : installing latest versions JHelioViewer
 * DM-ID : redmine#11884 : 14/04/2025 : LBE : Fixed macos launch
 * VERSION : V2.13
 * FA-ID : redmine#12143 : 04/07/2025 : KHK : update calling Medoc http
 * FA-ID : redmine#12325 : 30/09/2025 : KHK : Problem accessing the APIS interface
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.externalLinks;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import jakarta.xml.ws.Holder;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cdpp_irap.impex.v0.EnumMission;
import cdpp_irap.impex.v0.MethodsAMDA;
import eu.omp.irap.vespa.epntapclient.EpnTapConnection;
import eu.omp.irap.vespa.epntapclient.granule.Granule;
import eu.schudt.javafx.controls.calendar.DatePicker;
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.services.ServiceFactory;
import fr.tlse.irap.pt.client.services.constant.CONFIG;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import javafx.scene.control.ChoiceBox;

/**
 *
 * @author frup03304
 * @version
 */
public class ExternalLinks {

    private static final Logger log = LoggerFactory.getLogger(ExternalLinks.class);

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    private static final SimpleDateFormat dateFormatNoZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");

    private static long timeOut = 30000; // 30s
    private static long waitStep = 1000; // 1s
    /**
     * Days between -4713 and 1970 for julian days computations.
     */
    private static double DURATION_JDto70 = 2440587.5;
    /**
     * Number of milliseconds in a day.
     */
    private static long DAYS2MILLI = 86400000;

    /**
     * Call AMDA interface
     *
     * @param t
     * @param interval
     * @param mission
     * @param parameters
     */
    public static boolean callAmda(Date t, long interval, String mission, String parameters) throws Exception {
        Date t1 = new Date();
        t1.setTime(t.getTime() - (interval / 2 * 1000));
        Date t2 = new Date();
        t2.setTime(t.getTime() + (interval / 2 * 1000));

        String resultPlotURL = getAmdaPlot(t1, t2, mission);
        if (resultPlotURL != null) {
            DesktopBrowser.open(resultPlotURL);
            return true;
        }
        return false;
    }

    private static String getAmdaPlot(Date t1, Date t2, String mission) throws InterruptedException {
        String usr = null;
        String pwd = null;
        Holder<Boolean> success = new Holder<Boolean>();
        Holder<String> status = new Holder<String>();
        Holder<String> id = new Holder<String>();
        Holder<String> plotFileURL = new Holder<String>();

        MethodsAMDA amdaService = ServiceFactory.getAmdaService();
        try {
            log.info("Request AMDA getPlot service : " + EnumMission.fromValue(mission));
            amdaService.getPlot(sdf.format(t1), sdf.format(t2), EnumMission.fromValue(mission), usr, pwd, success, status, id, plotFileURL);
            if (success.value) {
                if (status.value.equals("done")) {
                    log.info("URL file : " + plotFileURL.value);
                    return plotFileURL.value;
                } else if (status.value.equals("in_progress")) {
                    /**
                     * WAIT FOR JOB END *
                     */
                    long loopStart = System.currentTimeMillis();
                    Holder<List<String>> dataFileURLs = new Holder<List<String>>();
                    while (System.currentTimeMillis() - loopStart < timeOut) {
                        amdaService.getStatus(id.value, success, dataFileURLs, plotFileURL, status);
                        if (success.value) {
                            log.info("URL file : " + plotFileURL.value);
                            return plotFileURL.value;
                        } else {
                            log.info("wait");
                            Thread.sleep(waitStep);
                        }
                    }
                    log.info("expired timeout");
                } else {
                    log.info("expired timeout");
                }
            } else {
                log.warn("AMDA getPlot returned error: " + success.value + " " + status.value);
            }
        } catch (Exception e) {
            log.warn(e.getMessage());
            return null;
        }
        return null;
    }

    /**
     * Call APIS interface
     *
     * @param target
     * @param t
     * @param interval
     * @param processLevel
     * @return
     * @throws Exception
     */
    public static List<Granule> getApisGranules(String planet, Date t, long interval, int procLevel) throws Exception {
        Date t1 = new Date();
        t1.setTime(t.getTime() - (interval * 1000));
        Date t2 = new Date();
        t2.setTime(t.getTime() + (interval * 1000));

        try {
            log.info("Get URLs of APIS images using VESPA EPNTAP service with parameters : (" + planet + ", " + t1 + ", " + t2 + ", " + procLevel + ")");
            EpnTapConnection lCon = new EpnTapConnection();

            String apisRequest = "SELECT TOP 50 * "
                    //String apisRequest = "SELECT TOP 50 apis.epn_core.thumbnail_url "
                    + "FROM apis.epn_core where "
                    + "UPPER(target_name) = UPPER('" + planet + "') "
                    + "AND UPPER(dataproduct_type) = UPPER('im') "
                    + "AND processing_level = " + procLevel
                    + " AND (access_format = 'application/pdf' OR processing_level = 3) "
                    + "AND ((time_min>" + getJDNFromDate(t1) + " AND time_max<" + getJDNFromDate(t2) + ") "
                    + "OR (time_min is NULL or time_max is NULL)) "
                    + "ORDER BY time_min OFFSET 0";

            log.info("Call ADQL with query : " + apisRequest);            
            return lCon.sendADQLQuery(CONFIG.EPNCOREV2_URL, apisRequest);
        } catch (Exception e) {
            log.warn(e.getMessage());
            throw e;
            //return null;
        }
    }

    /**
     * Call Helioviewer http link
     *
     * @param date
     * @param observer
     * @param instrument
     * @param detector
     * @param measure
     */
    public static void callMedocHttp(Date date, String observer, String instrument, String detector, String measure) {
        try {
            // http://helioviewer.ias.u-psud.fr/helioviewer/index.php?date=2011-06-01T00:00:00Z&imageScale=4.8408818&imageLayers=[SDO,AIA,AIA,171,1,100],[SOHO,LASCO,C2,white-light,1,100]
            // 12143 update url to https 
            // measure = White Light => white-light
            // https://helioviewer.ias.u-psud.fr/helioviewer/index.php?date=2012-02-08T00:00:00Z&imageScale=5000;&imageLayers=[STEREO_A,SECCHI,COR1,white-light,1,100]
            measure = measure.replace(" ", "-");
            measure = measure.toLowerCase();
            
            final String medocURL = CONFIG.MEDOC_URL + "/index.php?"
                    + "date=" + dateFormat.format(date) + "&"
                    + "imageScale=5000;&"
                    + "imageLayers=[" + observer + "," + instrument + "," + detector + "," + measure + ",1,100]";
            log.info("Open browser with URL file : " + medocURL);           
            DesktopBrowser.open(medocURL);

        } catch (Exception e) {
            e.printStackTrace();
            DialogBox.error(" Error while calling MEDOC interface.\n" + e.getMessage());
        }

    }

    /**
     * Call Helioviewer API java
     *
     * @param startDate
     * @param cadence
     * @param observer
     * @param instrument
     * @param detector
     * @param measure
     * @throws Exception
     */
//	public static void callMedocApi(Date startDate, long interval, long cadence, String observer, String instrument, String detector, String measure) throws Exception {
//		// java -jar C:/Program Files/JHelioviewer/JHelioviewer_MEDOC.jar -jhv [startTime="+startTime+";endTime="+endTime+";linked=true;cadence="+cadence+";imageScale=5000;imageLayers=["+observer+","+instrument+","+detector+","+measure+",1,100]];
//		String startTime = dateFormat.format(startDate);
//		Date endDate = new Date();
//		endDate.setTime(startDate.getTime() + (interval*1000));
//		String endTime = dateFormat.format(endDate);
//		
//		String jhvArgs = "[startTime=" + startTime + ";"
//				+ "endTime=" + endTime + ";" 
//				+ "linked=true;"
//				+ "cadence=" + cadence + ";"
//				+ "imageScale=5000;"
//				+ "imageLayers=[" + observer + "," + instrument + "," + detector + "," + measure + ",1,100]]";
//
//		CommandLine commandLine = new CommandLine("java");
//		commandLine.addArgument("-Xmx1g", false);
//		commandLine.addArgument("-jar", false);
//		commandLine.addArgument(getJHelioJarFile().toString(), false);
//		commandLine.addArgument("-jhv", false);
//		commandLine.addArgument(jhvArgs, false);
//		
//		try {
//			String libPath= System.getProperty("java.library.path");
//			log.info("Java lib path :" +libPath);
//			log.info("Execute command : " + commandLine.toString());
//			// launch command in synchronous mode with a timeout of 60 seconds
//			launchCommand(commandLine, 60000, false);
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
//	}
    public static void callMedocApi(final Date startDate, final long interval, final long cadence, final String observer, final String instrument, final String detector, final String measure) throws Exception {       
        final String lStartTime = ExternalLinks.dateFormatNoZ.format(startDate);
        final Date endDate = new Date();
        endDate.setTime(startDate.getTime() + interval * 1000L);
        final String lEndTime = ExternalLinks.dateFormatNoZ.format(endDate);
        
 
        //final CommandLine commandLine = new CommandLine("java");
        // use JAVA_HOME to get path to java in JDK
//        String javaHome = System.getenv("JAVA_HOME");
//        if (javaHome == null) {
//            throw new IllegalStateException("JAVA_HOME");
//        }
        //ExternalLinks.log.info("JAVA_HOME :" + javaHome);
//        String cmdJava = javaHome + "\\bin\\java";      
//        ExternalLinks.log.info("cmdJava JAVA_HOME :" + cmdJava);
//        final CommandLine commandLine = new CommandLine(cmdJava);

        //  Get java in JRE provided in JH package 
        String pathProgramFiles = getJavaJREinJHelioPackage().toString(); 
        
        String cmdJavaJRE = pathProgramFiles.replace(".exe", ""); // in Windows platform
        final CommandLine commandLine = new CommandLine(cmdJavaJRE);
        ExternalLinks.log.info("Java JRE get from JH package :" + cmdJavaJRE);

        // 11884 - review arguments with latest JH version
//        commandLine.addArgument("-jar", false);
//        commandLine.addArgument(getJHelioJarFile().toString(), false);
        commandLine.addArgument("--add-exports", false);
        commandLine.addArgument("java.desktop/sun.awt=ALL-UNNAMED", false);
        commandLine.addArgument("--add-exports", false);
        commandLine.addArgument("java.desktop/sun.swing=ALL-UNNAMED", false);
        commandLine.addArgument("-cp", false);
        String Args = "\"" + getJHelioJarFile().toString() + ";";
        Args = Args + getJHelioJarFile().getParent() + "\\natives-linux.jar;";
        Args = Args + getJHelioJarFile().getParent() + "\\natives-macos.jar;";
        Args = Args + getJHelioJarFile().getParent() + "\\natives-windows.jar\"";     
        // Windows separator ;
        // linux separator :
        if (!System.getProperty("os.name").startsWith("Windows")) {
            Args = Args.replace(";", ":");
            Args = Args.replace("\\", "/");
        }               
        commandLine.addArgument(Args, true);
        commandLine.addArgument("org.helioviewer.jhv.JHelioviewer", false);        
        commandLine.addArgument("-request", false);
        final File lTmpFile = File.createTempFile("pt_medoc_request", ".json");
        lTmpFile.deleteOnExit();
        String lDataset = instrument;
        if (!instrument.equals(detector)) {
            lDataset = lDataset + " " + detector;
        }
        final FileOutputStream lFos = new FileOutputStream(lTmpFile);
        final BufferedWriter lBw = new BufferedWriter(new OutputStreamWriter(lFos, "UTF-8"));
        lBw.write("{\n");
        lBw.write("\t\"org.helioviewer.jhv.request.image\":  [\n"); 
        lBw.write("{\n");
        lBw.write("\t\"observatory\":\"" + observer + "\",\n");
        lBw.write("\t\"startTime\":\"" + lStartTime + "\",\n");
        lBw.write("\t\"endTime\":\"" + lEndTime + "\",\n");
        lBw.write("\t\"cadence\": " + cadence + ",\n");
        // 11884
        //lBw.write("\t\"dataset\":\"" + lDataset + " " + measure.replace('-', ' ') + "\"\n");
        lBw.write("\t\"dataset\":\"" + lDataset + " " + measure + "\"\n");
        lBw.write("}\n");
        lBw.write("\t]\n");
        lBw.write("}\n");       
        lBw.close();
        commandLine.addArgument(lTmpFile.getAbsolutePath().replace('\\', '/'), false);
        try {
            //final String libPath = System.getProperty("java.library.path");
            //ExternalLinks.log.info("Java lib path :" + libPath);
            ExternalLinks.log.info("Execute command : " + commandLine.toString());           
            launchCommand(commandLine, 0L, false);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void launchCommand(CommandLine commandLine, long computeJobTimeout, boolean computeInBackground) throws ExecuteException, IOException {
        int exitValue;
        ExecuteWatchdog watchdog = null;

        // create the executor and consider the exitValue '0' as success
        Executor executor = new DefaultExecutor();
        executor.setExitValue(0);

        // create a watchdog if requested
        if (computeJobTimeout > 0) {
            watchdog = new ExecuteWatchdog(computeJobTimeout);
            executor.setWatchdog(watchdog);
        }

        try {
            exitValue = executor.execute(commandLine);
            log.info("Executing job has properly ended with exit value=" + exitValue);
        } catch (Exception e) {
            log.warn("Executing job has ended with Error");
            e.printStackTrace();
        }
    }
   
    /**
     * Get JHelioviewer_MEDOC.jar file installed on the client side. For Windows, under C:/Program Files/JHelioviewer/
     * For Mac OSX, under /Applications/JHelioviewer.app/Contents/Resources/Java For Linux, under
     * /usr/share/JHelioviewer
     *
     * @return
     * @throws FileNotFoundException
     */
    private static File getJHelioJarFile() throws FileNotFoundException {
        String path;
        File file;
        if (System.getProperty("os.name").startsWith("Windows")) {
            path = System.getenv("ProgramFiles");            
            if (path == null) {
                throw new IllegalStateException("ProgramFiles");
            }           
            // 11884 update JHelioviewer jar path 
            //file = new File(path, "/JHelioviewer/" + CONFIG.MEDOC_API);
            file = new File(path, "/JHelioviewer/classes/" + CONFIG.MEDOC_API);
            
            if (!file.isFile()) {
                throw new FileNotFoundException(file.toString());
            }
        } else if (System.getProperty("os.name").startsWith("Mac OS")) {           
            file = new File("/Applications/JHelioviewer.app/Contents/Resources/app/classes/" + CONFIG.MEDOC_API);
            if (!file.isFile()) {
                throw new FileNotFoundException(file.toString());
            }
        } else {
            // TODO : check on platform
            file = new File("/usr/share/JHelioviewer/classes/" + CONFIG.MEDOC_API);
            if (!file.isFile()) {
                throw new FileNotFoundException(file.toString());
            }
        }
        return file;
    }

    /**
     * Get JRE installed provided in Helioviewer package on the client side.
     *
     * @return
     * @throws FileNotFoundException
     */
    private static File getJavaJREinJHelioPackage() throws FileNotFoundException {
        String path;
        File file;
        if (System.getProperty("os.name").startsWith("Windows")) {
            path = System.getenv("ProgramFiles");
            if (path == null) {
                throw new IllegalStateException("ProgramFiles");
            }
            file = new File(path, "/JHelioviewer/jre/bin/java.exe");
            if (!file.isFile()) {
                throw new FileNotFoundException(file.toString());
            }
        } else if (System.getProperty("os.name").startsWith("Mac OS")) {            
            file = new File("/Applications/JHelioviewer.app/Contents/Resources/jre.bundle/Contents/Home/bin/java" );
            if (!file.isFile()) {
                throw new FileNotFoundException(file.toString());
            }
        } else {          
            // TODO : check on platform
            file = new File("/usr/share/JHelioviewer/jre/bin/java" );
            if (!file.isFile()) {
                throw new FileNotFoundException(file.toString());
            }
        }
        return file;
    } 
    
    
    
    /**
     * Compute time in seconds
     *
     * @param timeInUnit
     * @param unitTime
     * @return
     */
    public static long computeTimeInSeconds(long timeInUnit, String unitTime) {
        if (unitTime.equals("sec")) {
            return timeInUnit;
        } else if (unitTime.equals("min")) {
            return timeInUnit * 60;
        } else if (unitTime.equals("hours")) {
            return timeInUnit * 3600;
        } else if (unitTime.equals("days")) {
            return timeInUnit * 86400;
        } else {
            return 0;
        }
    }

    public static void setStyleDate(DatePicker datePicker) {
        datePicker.getTextField().setMaxWidth(350.0);
        datePicker.getTextField().setPrefWidth(350.0);
        datePicker.getTextField().setMaxHeight(20.0);
        datePicker.getTextField().setPrefHeight(20.0);
        datePicker.autosize();
    }

    public static void initUnitTime(ChoiceBox<String> unitTimeCb) {
        unitTimeCb.getItems().clear();
        unitTimeCb.getItems().add("sec");
        unitTimeCb.getItems().add("min");
        unitTimeCb.getItems().add("hours");
        unitTimeCb.getItems().add("days");
    }

    /**
     * Get date from JDN.
     *
     * @param pDate JDN.
     * @return parsed Date.
     * @throws ParseException
     */
    public static Date getDateFromJDN(double pDate) {
        //DecimalFormat lDf = new DecimalFormat("");
        //System.out.println("pDate = " + pDate);
        if (!Double.isNaN(pDate)) {
            Date lDate = new Date((long) ((pDate - DURATION_JDto70) * DAYS2MILLI));
            return lDate;
        } else {
            return null;
        }
    }

    /**
     * Get JDN from date string.
     *
     * @param pStrDate date with ET format.
     * @return parsed Date.
     * @throws ParseException
     */
    public static double getJDNFromDate(Date pDate) {
        //DecimalFormat lDf = new DecimalFormat("");
        //System.out.println("pDate = " + pDate);
        if (pDate != null) {
            double lJdn = ((double) pDate.getTime() / DAYS2MILLI) + DURATION_JDto70;
            return lJdn;
        } else {
            return 0;
        }
    }
}
