/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Creation 
 * Version : 24/03/2016 : SCA : V2.2 (PSWS-H2020)
 * Add comets
 * Version : 30/10/2019 : LBE : redmine#7277
 * SOLOHI matrix added and PSP one fixed
 * Version : 05/05/2021 : LBE
 * DM-ID : Redmine#7059 : 05/05/2021 : get orbits between 2 dates
 * Version : 05/05/2021 : LBE
 * FA-ID : Redmine#9605 : 28/01/2022 : Get probes data 5 by 5
 * Version : 12/09/2023 : LBE : V2.8
 * DM-ID : Redmine#10500 : 12/09/2023 : new END points
 * ----------------------------------------
 */
package fr.tlse.irap.server.serviceDAO.impl;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.TimeZone;

import javax.vecmath.Matrix3d;
import javax.vecmath.Vector3d;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.log4j.Logger;

import cdpp3dview.CdppServices;
import cdpp3dview.CdppServices_Service;
import cdpp3dview.types.Body;
import cdpp3dview.types.Frame;
import cdpp3dview.types.Instrument;
import cdpp3dview.types.Matrix3D;
import cdpp3dview.types.SbCatalog;
import cdpp3dview.types.SbType;
import cdpp3dview.types.TimeFile;
import cdpp3dview.types.TimeFileType;
import cdpp3dview.types.Tuple3D;
import fr.tlse.irap.pos.Position;
import fr.tlse.irap.server.ApplicationContextHolder;
import fr.tlse.irap.server.serviceDAO.ISpiceDAO;
import fr.tlse.irap.server.serviceDAO.constant.COMETS;
import fr.tlse.irap.server.serviceDAO.constant.CONFIG;
import fr.tlse.irap.server.serviceDAO.utils.SpiceHelper;
import fr.tlse.irap.server.serviceDAO.utils.TimeSerieSaxHandler;
import fr.tlse.irap.spaceElement.Camera;
import fr.tlse.irap.spaceElement.Comet;
import fr.tlse.irap.spaceElement.Planet;
import fr.tlse.irap.spaceElement.Probe;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.spaceElement.SpaceElementWrapper;
import fr.tlse.irap.spaceElement.Sun;
import fr.tlse.irap.utils.CoordConverter;

public class SpiceDAOImpl implements ISpiceDAO, Serializable {

    private final static Logger logger = Logger.getLogger(SpiceDAOImpl.class);

    private static final long serialVersionUID = 1L;
    private static CdppServices_Service jaxWsCdppService = null;
    private static CdppServices lCdpp = null;
    private static CONFIG prop;
    private static COMETS cometsProp;

    private static int emJ2KFrame = 1;
    private static int eclipJ2KFrame = 2;
    private static int nbSecondPerDay = 86400;
    private static int stepCometDays = 2;

    private static Matrix3d matAttXZY = new Matrix3d(1, 0, 0, 0, 0, -1, 0, 1, 0);
    private static Matrix3d matAttXYZ = new Matrix3d(1, 0, 0, 0, 1, 0, 0, 0, 1);
    private static Matrix3d matAttXYZBack = new Matrix3d(1, 0, 0, 0, -1, 0, 0, 0, -1);
    //private static Matrix3d matAttZXY = new Matrix3d(0, 1, 0, 0, 0, 1, 1, 0, 0);
    private static Matrix3d matAttZXY = new Matrix3d(0, 0, 1, 1, 0, 0, 0, 1, 0);

    private static SpaceElementWrapper spaceElements;

    public SpiceDAOImpl() {
        logger.info("SPACE DAO IMPL LOADED");
        prop = (CONFIG) ApplicationContextHolder.getContext().getBean("conf3DView");
        cometsProp = (COMETS) ApplicationContextHolder.getContext().getBean("confComets");
        logger.info("CONSTRUCTOR SpiceDAOImpl CDPP_URL " + prop.getCDPP());
        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        // init Space Elements 
        spaceElements = SpiceHelper.buildSpaceElements();
    }

    public SpiceDAOImpl(CONFIG lProp, COMETS lCometsProp) {
        logger.info("SPACE DAO IMPL LOADED");        
        prop = lProp;
        cometsProp = lCometsProp;
        logger.info("CONSTRUCTOR SpiceDAOImpl CDPP_URL " + prop.getCDPP());       
        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        // init Space Elements 
        spaceElements = SpiceHelper.buildSpaceElements();
    }

    public void initConnection() throws MalformedURLException, ParseException {
        if (jaxWsCdppService == null) {
            URL url = new URL(prop.getCDPP() + "/CdppServices?wsdl");
            logger.debug("initConnection() url=" + url);            
            jaxWsCdppService = new CdppServices_Service(url);
            //logger.debug("service=" + jaxWsCdppService.getServiceName());
            lCdpp = jaxWsCdppService.getWsPort();
            logger.debug("initConnection() port=" + lCdpp);            
        }
    }

    /**
     * Get all space elements from date d with orbits. Return a list of space elements having
     * positions defined between start time and start time + delta t
	 *
     */
    public SpaceElementWrapper getOrbits(Date d) {

        logger.debug("Get elements and orbits for date " + d);
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        //printCdppData();

        // create space elements (without comets)
        SpaceElementWrapper elements = new SpaceElementWrapper();
        elements.setSun(spaceElements.getSun());
        elements.setPlanets(spaceElements.getPlanets());
        elements.setProbes(spaceElements.getProbes());
        // set orbits
        fillOrbitInElements(elements, d);

        logger.debug("End get elements and orbits");
        return elements;
    }

    /**
     * Get orbit of selected elements from date d. Return the list of space element with orbit
     * positions defined between start time and start time + delta t
	 *
     */
    @Override
    public SpaceElementWrapper getOrbitElements(SpaceElementWrapper elements, Date d) {

        logger.debug("Get orbits elements for date " + d + " and some spaceElements");
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        fillOrbitInElements(elements, d);

        logger.debug("End get orbits elements");
        return elements;
    }

    /**
     * Get available comets in a region around a space element between 2 dates
     */
    @Override
    public SpaceElementWrapper getAvailableComets(Date startTime, Date endTime, SpaceElement spaceElement, long distance) {
        logger.debug("Get orbits of available comets from dates " + startTime + " to " + endTime + " and distance " + distance);
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        SpaceElementWrapper elements = new SpaceElementWrapper();
        XMLGregorianCalendar xgcStart = SpiceHelper.date2Xgc(startTime);
        XMLGregorianCalendar xgcEnd = SpiceHelper.date2Xgc(endTime);
        
        
        List<SbCatalog> lSbcList = new ArrayList<SbCatalog>();
        for(SbCatalog lSb:lCdpp.listSbCatalogs()) {
            if(lSb.getType().equals(SbType.COMET)) {
                logger.debug("Found catalog : " + lSb.toString());
                lSbcList.add(lSb);
            }
        }
        
        // add filtered IDs comet list
        String[] strNaifIDs = cometsProp.getIDS().split(",");
        List<Integer> naifIDs = new ArrayList<Integer>(strNaifIDs.length);
        for (int i = 0; i < strNaifIDs.length; i++) {
            naifIDs.add(i, Integer.parseInt(strNaifIDs[i]));
        }
        List<String> periods = Arrays.asList(cometsProp.getPERIODS().split(","));
        // search available comets
        List<Body> bodies = lCdpp.listBodiesInRegion(spaceElement.getId(), distance, naifIDs, 1000001, 2000000, xgcStart, xgcEnd, lSbcList);
        logger.debug("nb Comets : " + bodies.size());
        int step = stepCometDays * nbSecondPerDay;
        int i = 0;
        for (Body body : bodies) {
            // find orbit period
            for (int j = i; j < naifIDs.size(); j++) {
                if (naifIDs.get(j) == body.getNaifId()) {
                    if (periods.get(j) != null) {
                        // dataSize = orbit period (days) / nb days per step
                        int dataSize = 2 + Integer.parseInt(periods.get(j)) / stepCometDays;
                        // create new comet
                        Comet lTmpComet = new Comet(body.getName(), body.getNaifId(), step, dataSize);
                        List<String> lOrbFiles = new ArrayList<String>();
                        lOrbFiles.add(body.getOrbFiles().get(0).getName());
                        lTmpComet.setOrbTimeFiles(lOrbFiles);
                        elements.getComets().add(lTmpComet);
                        
                        i = j + 1;
                        logger.debug("new comet added : " + body.getNaifId() + " - " + body.getName());
                        break;
                    }
                }
            }
        }

        logger.debug("End get available comets");
        return elements;
    }

    @Override
    public List<Camera> getProbeInstruments(int naifId) {
        logger.debug("Get instruments of probe " + naifId);
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        List<Camera> cams = new ArrayList<Camera>();
        long startTime = System.currentTimeMillis();

        for (Instrument inst : lCdpp.listInsts(naifId)) {
            if (inst != null && inst.getVBSight() != null) {
                Camera camera = new Camera(inst.getName(), naifId, inst.getColor(), inst.getName());
                Vector3d vBSight = new Vector3d(inst.getVBSight().getX(), inst.getVBSight().getY(), inst.getVBSight().getZ());
                Vector3d vResult = new Vector3d();

                // Use fixed attitude of S/C 
                if (naifId == spaceElements.getProbe("STEREO-A").getId() || naifId == spaceElements.getProbe("STEREO-B").getId()) {
                    matAttXZY.transform(vBSight, vResult);
                    camera.setName(inst.getName().split("-")[0]);
                    camera.setInstrument("SECCHI");
                } else {
                    if (naifId == spaceElements.getProbe("PSP").getId()) {
                        //PSP Z is pointing to sun, X on ecliptic
                        //matAttZXY.transform(vBSight, vResult);
                        matAttZXY.transform(vBSight, vResult);
                    } else if (naifId == spaceElements.getProbe("SOLO").getId()) {
                        //matAttZXY.transform(vBSight, vResult);
                        System.out.println("Transform SOLO!" + inst.getNaifId() + " " + vBSight.getX() + "/" + vBSight.getY() + "/" + vBSight.getZ());
                        matAttXYZBack.transform(vBSight, vResult);
                    } else {
                        matAttXYZ.transform(vBSight, vResult);
                    }
                    String name = inst.getName().substring(inst.getName().indexOf("_") + 1).replace("_", "-");
                    camera.setName(name);
                    camera.setInstrument(name.split("-")[0]);
                }

                double longLat[] = CoordConverter.toSpheric(vResult.x, vResult.y, vResult.z);
                camera.setAngleFov(longLat[0]);
                camera.setExtentFov(2 * inst.getXFovDeg());
                logger.debug("Instrument FOV readed=" + inst.getName());
                logger.debug("Instrument FOV added name=" + camera.toString());
                cams.add(camera);
            } else {
                logger.warn("Incomplete instrument loaded !");
            }
        }
        logger.info("GetInstruments size : " + cams.size() + ", data time: " + (System.currentTimeMillis() - startTime) + " ms");
        return cams;
    }

    /**
     * Get position of a space element at date d. Return the space element with its real
     * coordinates.
     */
    @Override
    public SpaceElement getPositionElement(SpaceElement e, Date d) throws NoSuchElementException {
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        XMLGregorianCalendar xgc = SpiceHelper.date2Xgc(d);
        int sunId = spaceElements.getSun().getId();
        if (e instanceof Sun) {
            // set Spherical coordinates of the sun attitude in the HAE EclipJ2000 system coordinates.
            Tuple3D att3D = getAttitude3D(sunId, xgc);
            e.setAttitude(CoordConverter.toSpheric(att3D.getX(), att3D.getY(), att3D.getZ()));
//			Matrix3d m3d = getAttitude(sunId, xgc);
//        	Vector3d vId = new Vector3d(1,1,1);
//        	Vector3d vAtt = new Vector3d();
//        	m3d.transform(vId, vAtt);
//			e.setAttitude(CoordConverter.toSpheric(vAtt.x, vAtt.y, vAtt.z));
            // Init coordinates of SUN as center in EclipJ2000
            e.setPosition(0, 0, 0);
        } else if (e instanceof Planet) {           
            int elementId = spaceElements.getPlanet(e.getName()).getId();
            List<Tuple3D> lData = lCdpp.listOrbData(null, elementId, eclipJ2KFrame, sunId, xgc, xgc, nbSecondPerDay);
            if (lData.size() > 0) {
                e.setPosition(lData.get(0).getX(), lData.get(0).getY(), lData.get(0).getZ());                
            } else {
                logger.error("Impossible to get the position of the planet " + e.getName());
                throw new NoSuchElementException("Position is not found");
            }
        } else if (e instanceof Comet) {
            TimeFile lTf = new TimeFile(e.getId(), e.getOrbTimeFiles().get(0), xgc, xgc, TimeFileType.ORBIT);
            List<Tuple3D> lData = lCdpp.listOrbData(lTf, e.getId(), eclipJ2KFrame, sunId, xgc, xgc, nbSecondPerDay);
            if (lData.size() > 0) {
                e.setPosition(lData.get(0).getX(), lData.get(0).getY(), lData.get(0).getZ());
            } else {
                logger.error("Impossible to get the position of the planet " + e.getName());
                throw new NoSuchElementException("Position is not found");
            }
        } else { // SPACECRAFT           
            int elementId = spaceElements.getProbe(e.getName()).getId();
            List<TimeFile> listFile = lCdpp.listFiles(elementId, TimeFileType.ORBIT, xgc, xgc);
            if (listFile.size() > 0) {
                List<Tuple3D> lData = lCdpp.listOrbData(listFile.get(0), elementId, eclipJ2KFrame, sunId, xgc, xgc, nbSecondPerDay);
                if (lData.size() > 0) {
                    e.setPosition(lData.get(0).getX(), lData.get(0).getY(), lData.get(0).getZ());                    
                } else {
                    logger.error("Impossible to get the position of the spacecraft " + e.getName());
                    throw new NoSuchElementException("Position is not found");
                }
            } else {
                logger.info("No orbit file found for spacecraft " + e.getName()+ " "+elementId+" "+xgc.toGregorianCalendar().getTime());
                throw new NoSuchElementException("Position is not found");
            }
        }        
        return e;
    }

    /**
     * Get position of a spacecraft at date d in HEEQ system using the new 3Dview Return the space
     * element with its real coordinates.
     */
    @Override
    public SpaceElement getHEEQPositionElement(SpaceElement e, Date d) throws NoSuchElementException {
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        XMLGregorianCalendar xgc = SpiceHelper.date2Xgc(d);
        List<XMLGregorianCalendar> xmlTimes = new ArrayList<XMLGregorianCalendar>();
        List<Tuple3D> lData;
        List<Integer> lBodiesIdx = new ArrayList<Integer>();
        List<TimeFile> lOrbFiles = new ArrayList<TimeFile>();
        int sunId = spaceElements.getSun().getId();

        Frame HEEQFrame = new Frame(1601010, "HEEQ", null);

        if (e instanceof Probe) {
            int elementId = spaceElements.getProbe(e.getName()).getId();
            List<TimeFile> listFile = lCdpp.listFiles(elementId, TimeFileType.ORBIT, xgc, xgc);
            if (listFile.size() > 0) {
                // create list of NaifID and list of times
                lBodiesIdx.add(elementId);
                lOrbFiles.add(listFile.get(0));
                xmlTimes.add(xgc);
                List<String> orbDataFiles = lCdpp.listOrbData2(lBodiesIdx, HEEQFrame, sunId, xmlTimes, lOrbFiles);
                if (orbDataFiles.size() > 0) {
                    logger.debug("Orbit data file file = " + orbDataFiles.get(0));
                    try {
                        lData = SpiceHelper.parse(prop.getCDPP() + orbDataFiles.get(0));
                        if (lData.size() > 0) {
                            e.setPosition(lData.get(0).getX(), lData.get(0).getY(), lData.get(0).getZ());
                        } else {
                            logger.error("Impossible to get the position of the spacecraft " + e.getName());
                            throw new NoSuchElementException("Position is not found");
                        }
                    } catch (Exception exception) {
                        logger.error("Impossible to get the position of the spacecraft " + e.getName());
                        throw new NoSuchElementException("Position is not found");
                    }
                }
            } else {
                logger.error("Impossible to get the position of the spacecraft " + e.getName());
                throw new NoSuchElementException("Position is not found");
            }
        }
        logger.debug("Space Object " + e.getName() + ", at date " + d);
        logger.debug("===> position found : " + e);
        return e;
    }

    /**
     * Get positions of a body in Frame system using the new 3Dview service
     * @param e with timeValues set for each desire position
     * @param frame
     * @return the space element with its real coordinates (getPositions).
     * @throws NoSuchElementException
     */
    @Override
    public SpaceElement getPositionsElement(SpaceElement e, Frame frame) throws NoSuchElementException {

        ArrayList<Position> positions = new ArrayList<Position>();
        //List<Tuple3d> positions = new ArrayList<Tuple3D>();
        //logger.debug("Call getPositionsElement for " + e.getName()+" with times "+e.getTimeValues()+" and Frame: "+frame);
        if(frame==null || e.getTimeValues()==null || e.getTimeValues().isEmpty()) {
            logger.debug("Call getPositionsElement null or empty");
            return e;
        }
        logger.debug("Call getPositionsElement for " + e.getName() + " with " + e.getTimeValues().size() + " times");
                
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        List<XMLGregorianCalendar> xmlTimes = new ArrayList<XMLGregorianCalendar>();
        for (Date lDate : e.getTimeValues()) {
            xmlTimes.add(SpiceHelper.date2Xgc(lDate));
        }
        XMLGregorianCalendar xgcStart = SpiceHelper.date2Xgc(e.getTimeValues().get(0));
        XMLGregorianCalendar xgcEnd = SpiceHelper.date2Xgc(e.getTimeValues().get(e.getTimeValues().size() - 1));

        List<Tuple3D> lData;
        List<Integer> lBodiesIdx = new ArrayList<Integer>();
        List<TimeFile> lOrbFiles = new ArrayList<TimeFile>();
        int sunId = spaceElements.getSun().getId();

        int elementId = 0;
        if (e instanceof Planet) {
            elementId = spaceElements.getPlanet(e.getName()).getId();
        } else if(e instanceof Comet) {
            elementId = spaceElements.getComet(e.getName()).getId();
        } else {
            elementId = spaceElements.getProbe(e.getName()).getId();
        }
        lBodiesIdx.add(elementId);
        
        // TODO : add planets and comets  
        if (e instanceof Sun) {
            for(Date lDate:e.getTimeValues()) {
                positions.add(new Position(0,0,0));
            }
        } else {
            if (e instanceof Probe) {
                List<TimeFile> lOrbFilesTmp = lCdpp.listFiles(elementId, TimeFileType.ORBIT, xgcStart, xgcEnd);
                if(lOrbFilesTmp.size()>0) {
                    lOrbFiles.add(lOrbFilesTmp.get(0));
                }
            }
            //for thos we allready know orbit file, add it directly (comets)
            if(e.getOrbTimeFiles()!=null && !e.getOrbTimeFiles().isEmpty()) {
                TimeFile lTf = new TimeFile(e.getId(), e.getOrbTimeFiles().get(0), xgcStart, xgcEnd, TimeFileType.ORBIT);
                lOrbFiles.add(lTf);
            }
            if (lOrbFiles.size() > 0 || !(e instanceof Probe)) {
                
                logger.debug("calling orbData2 for " + e.getName()+" "+elementId+" with "+xmlTimes.size()+" times");
                List<String> orbDataFiles = lCdpp.listOrbData2(lBodiesIdx, frame, sunId, xmlTimes, lOrbFiles);
                logger.debug("returned " + orbDataFiles.size()+" files");
                for (String orbDataFile : orbDataFiles) {
                    logger.debug("Orbit data file = " + orbDataFile+ "for "+e.getName());
                    try {
                        lData = SpiceHelper.parse(prop.getCDPP() + orbDataFile);
                        positions.addAll(toListPos(lData));

                    } catch (Exception exception) {
                        logger.error("Impossible to get the position of the spacecraft " + e.getName());
                        throw new NoSuchElementException("Position is not found");
                    }
                }
            } else {
                logger.error("Impossible to get the position of the spacecraft " + e.getName());
                throw new NoSuchElementException("Position is not found");
            }
        }
        logger.debug("Space Object " + e.getName());
        String lPossStr = "";
        for(Position lPos:positions) {
            lPossStr+=" ["+lPos.getX()+";"+lPos.getY()+";"+lPos.getZ()+"]";
        }
        logger.debug("===> positions found : " + lPossStr);
        e.setPositions(positions);
        return e;
    }

    /**
     * Get list of matrix attitudes
     *
     * @param frame
     * @param times
     * @return
     * @throws Exception
     */
    @Override
    public List<Matrix3d> getMatAtt(Frame frame, List<XMLGregorianCalendar> times) throws Exception {
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        List<String> lFiles = lCdpp.listAttData2(Arrays.asList(new Integer[]{emJ2KFrame}), frame, times, null);

        List<Matrix3d> attM3d = SpiceHelper.urlToM3d(prop.getCDPP() + lFiles.get(0));
        return attM3d;
    }

    /**
     * Init all positions Space Objects (Planets, probes and comets) according to parameters
     * pre-defined in SpaceObject : step = step in seconds between 2 positions stored in
     * SpaceObject, dataSize = number of positions stored in SpaceObject => for planets, this couple
     * corresponds to all orbit positions => for Spacecrafts, this couple corresponds to the delta
     * period stored (30 days positions by default).
     */
    private void fillOrbitInElements(SpaceElementWrapper elements, Date d) {
        List<XMLGregorianCalendar> lXmlTimes = new ArrayList<XMLGregorianCalendar>();
        List<Date> lTimeValues = new ArrayList<Date>();
        //List<Tuple3D> lData;
        XMLGregorianCalendar xgcStartDate, xgcEndDate;

        List<Integer> lBodiesIdx = new ArrayList<Integer>();
        List<TimeFile> lOrbFiles = new ArrayList<TimeFile>();

        int sunId = spaceElements.getSun().getId();
        Frame ej2K = new Frame(17, "ECLIPJ2000", null);

        if (elements.getSun() != null) {
            fillOrbitInElement(elements.getSun(), spaceElements.getSun(), eclipJ2KFrame, sunId, d, true);
        }
        for (Planet p : elements.getPlanets()) {
            fillOrbitInElement(p, spaceElements.getPlanet(p.getName()), eclipJ2KFrame, sunId, d, true);
        }
        for (Comet c : elements.getComets()) {
            // Do not set data persistence for comets  
            fillOrbitInElement(c, null, eclipJ2KFrame, sunId, d, false);
        }

        for (Probe p : elements.getProbes()) {
            // init positions
            p.setPositions(null);
            p.setStartDate(d);

            long lTime = System.currentTimeMillis();
            logger.info("Get orbit : " + p.getName() + " from " + d + " to " + p.getEndDate());
            // init spacecraft positions
            xgcStartDate = SpiceHelper.date2Xgc(p.getStartDate());
            xgcEndDate = SpiceHelper.date2Xgc(p.getEndDate());
            List<TimeFile> listFile = lCdpp.listFiles(p.getId(), TimeFileType.ORBIT, xgcStartDate, xgcEndDate);
            if (listFile.size() > 0) {
                // create list of NaifID and list of times
                lBodiesIdx.add(p.getId());
                lOrbFiles.add(listFile.get(0));
            }
            logger.info("Got Files list ("+listFile.size()+") in "+(System.currentTimeMillis()-lTime)/1000d+"s");
        }

        if (lBodiesIdx.size() > 0) {
            // create list of times only for the 1st spacecraft
            Probe p = elements.getProbe(lBodiesIdx.get(0));
            if (p != null) {
                for (int i = 0; i < p.getDataSize(); i++) {
                    Date date = new Date((p.getStartTime() + i * p.getStep()) * 1000);
                    lTimeValues.add(date);
                    lXmlTimes.add(SpiceHelper.date2Xgc(date));
                    //logger.info("xmlTimes["+i+"]="+date+" " + xmlTimes.get(i));
                }
            }
            // get S/C orbits

            //process by bunch of 5 probes
            List<Integer> lBodiesIdxTmp = new ArrayList<Integer>();
            List<TimeFile> lOrbFilesTmp = new ArrayList<TimeFile>();
            for (int lBdIdx = 0; lBdIdx < lBodiesIdx.size(); lBdIdx++) {
                lBodiesIdxTmp.add(lBodiesIdx.get(lBdIdx));
                lOrbFilesTmp.add(lOrbFiles.get(lBdIdx));
                //for each 10 and last one
                if (lBdIdx % 5 == 0 || lBdIdx == lBodiesIdx.size() - 1) {

                    List<String> orbDataFiles = lCdpp.listOrbData2(lBodiesIdxTmp, ej2K, sunId, lXmlTimes, lOrbFilesTmp);
                    for (String file : orbDataFiles) {
                        logger.debug("Orbit data file = " + file);
                        try {
                            TimeSerieSaxHandler lSax = new TimeSerieSaxHandler(prop.getCDPP() + file, Arrays.asList("x", "y", "z"));
                            lSax.open();
                            int naifId = SpiceHelper.extractNaifId(file);
                            p = elements.getProbe(naifId);
                            if (p != null) {
                                if (p.getInServiceDate() != null && d.before(p.getInServiceDate())) {
                                    logger.debug(p.getName() + " has a InServiceDate defined after the search start date");
                                } else {
                                    List<Position> lPos = new ArrayList<Position>();
                                    int i = 0;
                                    for (XMLGregorianCalendar xmlTime : lXmlTimes) {
                                        Date time = xmlTime.toGregorianCalendar().getTime();
                                        boolean isTime = false;
                                        for (Date timeRes : lSax.getTimeValues()) {
                                            if (time.equals(timeRes)) {
                                                isTime = true;
                                                break;
                                            }
                                        }
                                        Position pos = new Position(Double.NaN, Double.NaN, Double.NaN);
                                        if (isTime) {
                                            double[] values = lSax.getValues().get(i);
                                            pos.setX(values[0]);
                                            pos.setY(values[1]);
                                            pos.setZ(values[2]);
                                            //logger.info("adding values "+Arrays.toString(values));
                                            i++;
                                        }
                                        lPos.add(pos);
                                        //							System.out.println("Adding position for ID="+naifId+" and time=" +time);
                                        //							System.out.println("===> "+pos.getX() +" " + pos.getY() + " "+pos.getZ());                                       
                                    }
                                    logger.info(p.getName()+" got "+lPos.size()+" positions");
                                    p.setPositions(lPos);
                                    p.setTimeValues(lTimeValues);
                                }
                            }

//					List<Tuple3D> lData = SpiceHelper.parse(prop.getCDPP()+file);
//					int naifId = SpiceHelper.extractNaifId(file);
//					e = elements.getProbe(naifId);
//					if (e != null) {
//                    	e.setPositions(toListPos(lData));
//    				}
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                    //clear arrays
                    lBodiesIdxTmp.clear();
                    lOrbFilesTmp.clear();
                }
            }
//        	// get S/C attitude
//			List<String> attDataFiles = lCdpp.listAttData2(lBodiesIdx, ej2K, xmlTimes, lOrbFiles);
//			for (String file:attDataFiles) {
//				logger.debug("Att data file = " + file);
//				try {
//					lData = SpiceHelper.parse(prop.getCDPP()+file);
//					int naifId = SpiceHelper.extractNaifId(file);
//					p = elements.getProbe(naifId);
//					if (e != null) {
//				        Matrix3d m = SpiceHelper.urlToM3d(prop.getCDPP()+file).get(0);
//				        List<Double> ld = new ArrayList<Double>();
//				        for (int col=0; col<3; col++) {
//				        	for (int row=0; row<3; row++) {
//				        		ld.add(m.getElement(row, col));
//				        	}
//				        }
//                      p.setMatAtt3D(new Matrix3D(ld));
//    				}
//				} catch (Exception ex) {
//					ex.printStackTrace();
//				}
//			}
        }
    }

    /**
     *
     * @param element
     * @param elementBase
     * @param frameId
     * @param centerId
     * @param d
     */
    private void fillOrbitInElement(SpaceElement element, SpaceElement elementBase, int frameId, int centerId, Date d, boolean isPersistent) {
        logger.info("Get orbit : " + element.getId() + " - " + element.getName());
        if (!isPersistent || elementBase.getPositions() == null) {
            element.setStartDate(d);
            XMLGregorianCalendar xgcStartDate = SpiceHelper.date2Xgc(element.getStartDate());
            XMLGregorianCalendar xgcEndDate = SpiceHelper.date2Xgc(element.getEndDate());
            List<Date> lTimeValues = new ArrayList<Date>();
            for (long i = 0; i < element.getDataSize(); i++) {
                Date date = new Date((element.getStartTime() + i * element.getStep()) * 1000l);
                lTimeValues.add(date);
            }
            TimeFile lOrbTimeFile = null;
            if(element.getOrbTimeFiles()!=null && !element.getOrbTimeFiles().isEmpty()) {
                logger.debug("Adding orbit file :" + element.getOrbTimeFiles().get(0));
                lOrbTimeFile = new TimeFile(element.getId(), element.getOrbTimeFiles().get(0), xgcStartDate, xgcEndDate, TimeFileType.ORBIT);
            }

            // Compute planets locations (orbits)
            List<Tuple3D> lData = lCdpp.listOrbData(lOrbTimeFile, element.getId(), frameId, centerId, xgcStartDate, xgcEndDate, element.getStep());
            element.setPositions(toListPos(lData));
            element.setTimeValues(lTimeValues);
            logger.debug("Get orbit : add " + lTimeValues.size() + " times to " + element.getName());
            if (isPersistent) {
                // set data persistence on server side
                elementBase.setStartTime(element.getStartTime());
                elementBase.setPositions(element.getPositions());
                elementBase.setTimeValues(element.getTimeValues());
                logger.debug("Get orbit : save " + lTimeValues.size() + " times of " + element.getName());
            }
        } else {
            // get data stored on server side
            element.setStartTime(elementBase.getStartTime());
            element.setPositions(elementBase.getPositions());
            element.setTimeValues(elementBase.getTimeValues());
            logger.debug("Get orbit : get base " + elementBase.getTimeValues().size() + " times of " + element.getName());
        }
    }

    /**
     * Get attitude of a space object at a date in Ecliptic J2000
     *
     * @param spaceElementId
     * @param XMLGregorianCalendar
     * @return
     * @throws NoSuchElementException
     */
    private Tuple3D getAttitude3D(int spaceElementId, XMLGregorianCalendar xgc) throws NoSuchElementException {
        Tuple3D attitude = new Tuple3D();
        List<Matrix3D> lRot = lCdpp.listAttData(null, spaceElementId, eclipJ2KFrame, xgc, xgc, nbSecondPerDay);
        if (lRot.size() > 0) {
            List<Double> mat = lRot.get(0).getValues();
            attitude.setX(mat.get(0));
            attitude.setY(mat.get(3));
            attitude.setZ(mat.get(6));
            //logger.info("getAttitude3D - position in Ecliptic plane: x=" + attitude.getX() + ",y=" + attitude.getY() + ",z=" + attitude.getZ());
        } else {
            throw new NoSuchElementException("Spacecraft attitude is not found for ID=" + spaceElementId);
        }

        return attitude;
    }

    /**
     * Get attitude matrix from Ecliptic J2000 to local space object system coordinate
     *
     * @param naifId
     * @param XMLGregorianCalendar : date
     * @return
     * @throws NoSuchElementException
     */
    private Matrix3d getAttitude(int naifId, XMLGregorianCalendar xgc) throws NoSuchElementException {
        List<Matrix3D> attMat = lCdpp.listAttData(null, naifId, eclipJ2KFrame, xgc, xgc, nbSecondPerDay);
        try {
            List<Double> lv = attMat.get(0).getValues();
            Matrix3d m3d = new Matrix3d();
            int i = 0;
            for (int row = 0; row < 3; row++) {
                for (int col = 0; col < 3; col++) {
                    m3d.setElement(row, col, lv.get(i++));
                }
            }
            //logger.info("getAttitude - position in Ecliptic plane: x=" + attitude.getX() + ",y=" + attitude.getY() + ",z=" + attitude.getZ());
            return m3d;

        } catch (Exception e) {
            throw new NoSuchElementException("Spacecraft attitude is not found for ID=" + naifId);
        }
    }

    private static List<Position> toListPos(List<Tuple3D> tuples) {
        List<Position> data = new ArrayList<Position>();
        for (Tuple3D pos : tuples) {
            data.add(new Position(pos.getX(), pos.getY(), pos.getZ()));
        }
        return data;
    }

    /**
     * print LCDPP data
     */
    private void printCdppData() {
        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        List<Frame> frames = lCdpp.listFrames2();
        for (Frame frame : frames) {
            logger.debug("Frame : " + frame + "(Id=" + frame.getId() + ")");
        }
    }

    @Override
    public List<Position> transformPositions(List<Position> positions, String frameFrom, String centerFrom, String frameTo, String centerTo, List<XMLGregorianCalendar> times) throws NoSuchElementException {

        List<Position> posResults = new ArrayList<Position>();
        List<Tuple3D> l3dPos = new ArrayList<Tuple3D>();
        for (Position pos : positions) {
            l3dPos.add(new Tuple3D(pos.getX(), pos.getY(), pos.getZ()));
        }

        try {
            initConnection();
        } catch (Exception e1) {
            e1.printStackTrace();
        }

        // set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        String orbDataFile = lCdpp.listNewFrameAtt(
                times, l3dPos, null, frameFrom, centerFrom, frameTo, centerTo, null);
        logger.debug("Orbit data file = " + orbDataFile);
        try {
            List<Tuple3D> lData = SpiceHelper.parse(prop.getCDPP() + orbDataFile);
            posResults.addAll(toListPos(lData));

        } catch (Exception exception) {
            exception.printStackTrace();
        }
        logger.debug("===> positions found : " + posResults);
        return posResults;
    }

}
