/* ----------------------------------------
 * Origin  : Inetum
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 *  Creation 
 * Version : 06/04/2023 : LBE : V2.7
 * DM-ID : redmine#8642 : 06/04/2023 : Added 3D shockwave
 * Version : 12/09/2023 : LBE : V2.8
 * DM-ID : Redmine#10500 : 12/09/2023 : new END points
 * Version : 19/07/2024 : KKH 
 * DM-ID : Redmine#11685: 19/07/2024 : remove Nose axis
 * Version : V2.13
 * DM-ID : redmine#12279 : 10/09/2025 : LBE : Add field lines in 3d panel
 * DM-ID : redmine#12279 : 21/10/2025 : KHK : Synchronization of 3D and Carrington map when changing PFSS altitude
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.drawing3d;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import fr.tlse.irap.pt.client.component.CustomComponent;
import fr.tlse.irap.pt.client.component.ICustomComponent;
import fr.tlse.irap.pt.client.controller.common.ISelectedItem;
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.drawingPlane.DrawHelper;
import fr.tlse.irap.pt.client.controller.eclipticPlane.EclipticPlaneController;
import fr.tlse.irap.pt.client.controller.timestamp.TimestampController;
import fr.tlse.irap.pt.client.controller.tools.carrington.CarringtonHelper;
import fr.tlse.irap.pt.client.controller.tools.carrington.CarringtonModel;
import fr.tlse.irap.fieldLine.FieldLine;
import fr.tlse.irap.fieldLine.FieldPoint;
import fr.tlse.irap.map.CarringtonMap;
import fr.tlse.irap.map.CarringtonMapType;
import fr.tlse.irap.pt.client.controller.dialogbox.DialogBox;
import fr.tlse.irap.pt.client.controller.tools.carrington.CarringtonGridFieldLines;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.shock.Shockwave3d;
import fr.tlse.irap.shock.Shockwave3dEvent;
import fr.tlse.irap.shock.Shockwave3dEvents;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.utils.CoordConverter;
import fr.tlse.irap.vplot.SolarSpeedWrapper;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.AmbientLight;
import javafx.scene.Camera;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Control;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.PickResult;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape3D;
import javafx.scene.shape.Sphere;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import org.fxyz3d.geometry.Point3D;
import org.fxyz3d.scene.paint.Palette;
import org.fxyz3d.shapes.Cone;
import org.fxyz3d.shapes.composites.PolyLine3D;
import org.fxyz3d.shapes.composites.PolyLine3D.LineType;
import org.fxyz3d.shapes.primitives.ScatterMesh;

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

public class Drawing3dController extends Group implements Initializable, ICustomComponent {

    protected static final Logger log = LoggerFactory.getLogger(Drawing3dController.class);
    static final String RECT_BORDER_ID = "rectangleBorder";
    static final int FIRST_NODE = 1;
    static final double AU = 150e6;
    static final double RSUN = 696340;
    private double widthZone;
    private double heightZone;

    private static double MOUSE_SPEED = 0.1;
    private static double ROTATION_SPEED = 2.0;

    double mouseStartPosX, mouseStartPosY;
    double mousePosX, mousePosY;
    double mouseOldX, mouseOldY;
    double mouseDeltaX, mouseDeltaY;

    //11685 Min and max color bar
    private double minColorBar = 100;
    private double maxColorBar = 2000;
    //0 : Shock speed, 1 : MachAlfven, 2 : Machfm, 3 : CompRadio, 4 : Theta
    private final double[] tMinColorBar = new double[]{100, 0, 0, 0, 0};
    private final double[] tMaxColorBar = new double[]{2000, 10, 10, 4, 90};
    private String[] tDataTypeName = new String[]{"Shock speed", "MachAlfven", "Machfm", "CompRatio", "ThetaBN"};

    @FXML
    protected Rectangle rectangleBorder;
    @FXML
    protected Drawing3dController rootDrawing3d;
    protected EclipticPlaneController eclipticPlaneCtrl;
    protected SubScene subScene;
    /**
     * main group with camera, lights and objGroup
     */
    protected Xform rootGroup;
    /**
     * group with objects (sun, axes, field lines, ...
     */
    protected Xform objGroup;

    //time input component handle
    protected TimestampController timestampComponent;

    ScrollPane sPaneParent;
    //3D panel context menu
    static final MapMenu mapMenu = new MapMenu();

    //3D panel text overlay
    Label txt2DTimestamp = new Label("");
    Label txt2DNosePosition = new Label("");

    EventHandler<MouseEvent> filter;
    Date currentDate = new Date(0);
    Date startDate = new Date(0);
    Date stopDate = new Date(0);

    //data for update
    private CarringtonModel carringtonModel = new CarringtonModel();
    private CarringtonMapType cmType = new CarringtonMapType();
    private CarringtonMap carringtonMap;
    private Shockwave3dEvent shockwaveEvent;
    /**
     * gridField lines from carrington component
     */
    private List<List<FieldLine>> gridFieldLines = null;
    private Map<SpaceElement, FieldLine3d> probesFieldLines = null;
    private List<Date> gridFlTimes = null;
    private int currGridFiledLine = -1;

    //private ComboBox swEventCbx;
    private int dataTypeNo = 0;//replace static combo box index as selection is in menu now

    //3d variables
    Camera camera = null;
    Shape3D sunShape = null;
    Pivot cameraTransform = null;
    Group axes = null;

    /**
     * List of shock waves events, there should be only one, but technically
     * there could be more. Contains as many shapes as events. Each shape as to
     * be update when time slider is updated.
     */
    Xform shockwavesShapes = null;
    /**
     * List of field lines shapes Updated when needed
     */
    Xform gridFieldLinesShapes = null;

    public Drawing3dController() {
        super();
        CustomComponent.initCustomComponent(this);
        probesFieldLines = new HashMap<>();
    }

    /**
     * Initialize component
     *
     * @param viewType
     */
    public void initComponent(Control pParent, EclipticPlaneController pEpc) {

        sPaneParent = (ScrollPane) pParent;
        eclipticPlaneCtrl = pEpc;
        //swEventCbx = pSwEventCbx;

        this.widthZone = rectangleBorder.getWidth();
        this.heightZone = rectangleBorder.getHeight();

        filter = new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                event.consume();
            }
        };

        // init menu
        rootDrawing3d.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                event.consume();
                //log.info("**** mouse click");
                if (event.getButton() == MouseButton.SECONDARY) {
                    mapMenu.show(rootDrawing3d, event.getScreenX(), event.getScreenY());
                    event.consume();
                }
                if (event.getButton() == MouseButton.PRIMARY) {
                    mapMenu.hide();
                    PickResult res = event.getPickResult();
                    log.debug("res " + res);
                    //you can get a reference to the clicked node like this 
                    if (res.getIntersectedNode() instanceof Node) {
                        Node lNode = ((Node) res.getIntersectedNode());
                        log.debug("Got node " + lNode.getClass() + " " + lNode.getParent());
                        if (lNode.getParent() instanceof PolyLine3D) {
                            PolyLine3D lLine = (PolyLine3D) lNode.getParent();
                            log.debug("Line parent " + lLine.getParent());
                            if (lLine.getUserData() instanceof FieldLine3d) {
                                FieldLine3d lFl3d = (FieldLine3d) lLine.getUserData();
                                showLinePropertiesDialog(lFl3d, lFl3d.getSpaceElement());
                            }
                        }
                    }
                }
            }
        });
        //manage zooming to scene center
        rootDrawing3d.setOnScroll(
                new EventHandler<ScrollEvent>() {
            @Override
            public void handle(ScrollEvent event) {
                double zoomFactor = 0.95;
                double deltaY = event.getDeltaY();

                if (deltaY < 0) {
                    zoomFactor = 1.05;
                }
                //log.info("**** mouse scroll " + cameraTransform.s.getX()+" "+zoomFactor);
                cameraTransform.setScale(zoomFactor);
                event.consume();
            }
        });

        //listen to shockwave slider change
        eclipticPlaneCtrl.getSwEventSlider().valueProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> arg0, Number oldValue, Number newValue) {
                //log.debug("Time slider has changed => new step = " + newValue.intValue());
                //currentDate = new Date(startDate.getTime() + newValue.longValue() * (stopDate.getTime() - startDate.getTime()) / 100);
                buildShockwavesShapes();
            }
        });
        //Lambda style listener used just for fun...
//        swEventCbx.valueProperty().addListener((obs, oldVal, newVal) -> {
//            buildShockwavesShapes();
//        });

        subScene = init3dScene();
        initMouseControl();
    }

    /**
     * Dynamic component initialization
     */
    public void initComponentWith(final TimestampController timestampComponent) {
        this.timestampComponent = timestampComponent;
        // Listen to current result date change  
        this.timestampComponent.getResultDateProperty().addListener(new ChangeListener<Date>() {
            @Override
            public void changed(ObservableValue<? extends Date> observableObject, Date oldDate, Date newDate) {
                if (newDate != null) {
                    //log.debug("Result date has changed => request positions");
                    update(timestampComponent.getStartDate(), timestampComponent.getResultDate());
                }
            }
        });
        //listen to slider change
        this.timestampComponent.getResultTScrollBar().valueProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> arg0, Number oldValue, Number newValue) {
                //log.debug("Time slider has changed => new step = " + newValue.intValue());
                currentDate = new Date(startDate.getTime() + newValue.longValue() * (stopDate.getTime() - startDate.getTime()) / 100);
                //buildShockwavesShapes();
            }
        });

    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
    }

    /**
     * Create scene, all units are in km Start with sun at real size and camera
     * at 1 AU
     *
     * @return
     */
    private SubScene init3dScene() {

        rootGroup = new Xform();
        objGroup = new Xform();
        SubScene lSubScene = new SubScene(rootGroup, widthZone, heightZone, true, SceneAntialiasing.BALANCED);
        lSubScene.setFill(Color.WHITE);
        sunShape = buildSunShape();//automatic added to objGroup
        //objGroup.getChildren().add(sunShape);
        rootGroup.getChildren().add(objGroup);
        //buildShockwavesShapes();

//        IcosahedronMesh ico = new IcosahedronMesh(10, 2);
//        ico.setTextureModeVertices3D(1600, p -> (double) p.x * p.y * p.z);
//        rootGroup.getChildren().add(ico);
        int lStart = 0000;
        int lStop = 500000;
        int lNbPoints = 5000;

        axes = getAxes(80000);
        axes.setVisible(false);
        objGroup.getChildren().add(axes);

        camera = new PerspectiveCamera(true);
        lSubScene.setCamera(camera);
        //Group cameraGroup = new Group();
        camera.setRotate(0);
        //camera.setTranslateZ(-AU / 5);
        //cameraGroup.setTranslateZ(-lStop*20);
        camera.setFarClip(5 * AU);
        camera.setNearClip(10000);

        //camera.setFarClip(lStop * 10);
        //camera.setNearClip(lStop / 10000);
        //camera.setTranslateX(lStop * 5);
        //camera.setTranslateY(lStop * 5);
        //camera.setTranslateZ(-lStop * 2);
        cameraTransform = new Pivot();
        cameraTransform.getChildren().add(camera);
        setViewer(null);
        //cameraTransform.ry.setAngle(-10.0);
        //cameraTransform.rx.setAngle(-120.0);
        //cameraTransform.rz.setAngle(220.0);
        //cameraGroup.getChildren().add(cameraTransform);
        rootGroup.getChildren().add(cameraTransform);

        AmbientLight lALight = new AmbientLight(Color.WHITE);
        //lALight.set
        PointLight lPLight = new PointLight();
        lPLight.setColor(Color.WHITE);

        Group lightGroup = new Group();
        //lightGroup.getChildren().add(lPLight);
        lightGroup.getChildren().add(lALight);

        rootGroup.getChildren().add(lightGroup);
        lPLight.setRotate(45);
        lightGroup.setTranslateZ(-AU);

        rootDrawing3d.getChildren().add(lSubScene);
        rootDrawing3d.getChildren().add(txt2DTimestamp);
        rootDrawing3d.getChildren().add(txt2DNosePosition);
        txt2DNosePosition.setLayoutX(5);
        txt2DNosePosition.setLayoutY(heightZone - 30);
        txt2DNosePosition.setFont(Font.font("null", FontWeight.NORMAL, 12));
        txt2DTimestamp.setLayoutX(5);
        txt2DTimestamp.setLayoutY(heightZone - 15);
        txt2DTimestamp.setFont(Font.font("null", FontWeight.NORMAL, 12));

        return lSubScene;
    }

    private static Group getAxis(Color pCol) {
        Cylinder lCyl = new Cylinder(1, 60);
        lCyl.getTransforms().addAll(new Translate(0, 30, 0));
        lCyl.setMaterial(new PhongMaterial(pCol));
        int lConeLength = 5;
        Cone lCone = new Cone(10, 2, lConeLength, pCol);
        lCone.getTransforms().addAll(new Rotate(180, Rotate.Z_AXIS), new Translate(0, -60 - lConeLength, 0));
        Group lGrp = new Group();
        lGrp.getChildren().addAll(lCyl, lCone);
        return lGrp;
    }

    private static Group getAxes(double scale) {
        Group lGp = new Group();
        Group lAxisX = getAxis(Color.RED);
        lAxisX.getTransforms().addAll(new Rotate(-90, Rotate.Z_AXIS));
        Group lAxisY = getAxis(Color.GREEN);
        //lAxisY.getTransforms().addAll(new Rotate(-90, Rotate.Z_AXIS));
        Group lAxisZ = getAxis(Color.BLUE);
        lAxisZ.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS));

//        Cylinder axisY = new Cylinder(1, 60);
//        axisY.getTransforms().add(new Translate(0, 30, 0));
//        axisY.setMaterial(new PhongMaterial(Color.GREEN));
//        
//        
//        Cylinder axisZ = new Cylinder(1, 60);
//        axisZ.setMaterial(new PhongMaterial(Color.BLUE));
//        axisZ.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS), new Translate(0, 30, 0));
        lGp.getChildren().addAll(lAxisX, lAxisY, lAxisZ);
        lGp.getTransforms().add(new Scale(scale, scale, scale));
        return lGp;
    }

    /**
     * Mouse controls to rotate scene
     */
    private void initMouseControl() {
        rootDrawing3d.setOnMousePressed(me -> {
            mouseStartPosX = me.getSceneX();
            mouseStartPosY = me.getSceneY();
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            mouseOldX = me.getSceneX();
            mouseOldY = me.getSceneY();
        });

        rootDrawing3d.setOnMouseDragged(me -> {
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            mouseDeltaX = (mousePosX - mouseOldX);
            mouseDeltaY = (mousePosY - mouseOldY);

            if (me.isPrimaryButtonDown()) {
                /*System.out.println("Mouse move " + (mouseDeltaX * MOUSE_SPEED * ROTATION_SPEED)
                        + " " + (mouseDeltaY * MOUSE_SPEED * ROTATION_SPEED));
                 */
                //objGroup.addRotation(mouseDeltaX * MOUSE_SPEED * ROTATION_SPEED, Rotate.Z_AXIS);
                //objGroup.addRotation(mouseDeltaY * MOUSE_SPEED * ROTATION_SPEED, Rotate.X_AXIS);
                cameraTransform.setRotationAxis(new javafx.geometry.Point3D(0, 0, 1));
                cameraTransform.setRotate(cameraTransform.getRotate() - mouseDeltaX * MOUSE_SPEED * ROTATION_SPEED);

            }
        });
    }

    /**
     * load context menu
     */
    public void loadMenu() {

        List<Item> menus = new ArrayList<Item>();

        //add viewer choice
        Item loadShockMenuItem = new Item("3D shock cat");
        //this service is called sync as it should return immediatly
        Shockwave3dEvents lS3es = ServiceFactory.getShockService().fetchShockwaves3dList();
        SimpleDateFormat lSdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
        lSdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        for (Date lDate : lS3es.getEvents()) {
            Item lItem = new Item(lSdf.format(lDate));
            loadShockMenuItem.add(lItem);
            mapMenu.getMap().put(lItem, new ISelectedItem() {
                @Override
                public void run() {
                    //update at
                    updateShowckwave(lDate);
                    timestampComponent.setSelectedDate(lDate);
//                    timestampComponent.setResultTime(lDate);
//                    if (timestampComponent.getResultTScrollBar().isDisable()) {
//                        timestampComponent.updateResultDate();
//                    }
                }
            });
        }
        menus.add(loadShockMenuItem);

        Item setSHPFSSGridLinesMenuItem = new Item("Show/hide PFSS lines");
        menus.add(setSHPFSSGridLinesMenuItem);
        mapMenu.getMap().put(setSHPFSSGridLinesMenuItem, new ISelectedItem() {
            @Override
            public void run() {
                showHidePFSSGridLines();
            }
        });

        if (SpaceElementCollection.getSpaceElementWrapper() != null) {
            Item setViewerMenuItem = new Item("Set camera");
            menus.add(setViewerMenuItem);
            Item itemReset = new Item("Reset view");
            setViewerMenuItem.add(itemReset);

            mapMenu.getMap().put(itemReset, new ISelectedItem() {
                @Override
                public void run() {
                    setViewer(null);
                }
            });
            for (final SpaceElement spaceElement : SpaceElementCollection.getSpaceElementWrapper().getSpaceElements()) {
                //only add stereos and PSP
                if (spaceElement.isDefined()
                        && (spaceElement.getId() == -234
                        || spaceElement.getId() == -235
                        || spaceElement.getId() == -96
                        || spaceElement.getId() == -21
                        || spaceElement.getId() == -144
                        || spaceElement.getId() == 399)) {
                    Item itemProbe = new Item(spaceElement.getLongName());
                    setViewerMenuItem.add(itemProbe);

                    mapMenu.getMap().put(itemProbe, new ISelectedItem() {
                        @Override
                        public void run() {
                            setViewer(spaceElement);
                        }
                    });
                }
            }

            //show parker spiral
            Item setSHParkerSpiralMenuItem = new Item("Show/hide Parker spiral");
            menus.add(setSHParkerSpiralMenuItem);
            Item itemAllProbe = new Item("All");
            setSHParkerSpiralMenuItem.add(itemAllProbe);
            mapMenu.getMap().put(itemAllProbe, new ISelectedItem() {
                @Override
                public void run() {
                    log.debug("Show/hide All");
                    runShowHideAll();
                }
            });
            for (final SpaceElement spaceElement : SpaceElementCollection.getSpaceElementWrapper().getProbes()) {
                if (spaceElement.isDefined()) {
                    Item itemProbe = new Item(spaceElement.getLongName());
                    setSHParkerSpiralMenuItem.add(itemProbe);
                    mapMenu.getMap().put(itemProbe, new ISelectedItem() {
                        @Override
                        public void run() {
                            setParkerSpiral(spaceElement);
                            log.debug("Show/hide {}", spaceElement.getLongName());
                        }
                    });
                }
            }

        }

        //Item lSetColorMapMenuItem = new Item("Set Colors");
        Item lSetShowAxesMenuItem = new Item("Show/hide axes");
        mapMenu.getMap().put(lSetShowAxesMenuItem, new ISelectedItem() {
            @Override
            public void run() {
                //show axes
                if (axes != null) {
                    axes.setVisible(!axes.isVisible());
                }
            }
        });
        menus.add(lSetShowAxesMenuItem);
        Item lSetDateTypeMenuItem = new Item("Set Data");
        Item lS1 = new Item("Shock speed");
        Item lS2 = new Item("Mach_Alfven");
        Item lS3 = new Item("Mach_fm");
        Item lS4 = new Item("Comp_Ratio");
        Item lS5 = new Item("Theta_BN");
        int lIdx = 0;
        for (Item lItem : new Item[]{lS1, lS2, lS3, lS4, lS5}) {
            final int lId = lIdx++;
            mapMenu.getMap().put(lItem, new ISelectedItem() {
                @Override
                public void run() {
                    dataTypeNo = lId;
                    buildShockwavesShapes();
                }
            });
            lSetDateTypeMenuItem.add(lItem);
        }
        menus.add(lSetDateTypeMenuItem);
        mapMenu.getItems().clear();
        mapMenu.addAll(menus);
    }

    /**
     * Set camera on an SpaceElement position, pointing Sun
     *
     * @param pSe
     */
    private void setViewer(SpaceElement pSe) {
        if (cameraTransform == null) {
            return;
        }

        if (pSe == null) {
            //init position
            cameraTransform.setPosition(AU / 5, AU / 5, AU / 10);
            cameraTransform.lookAt(new com.sun.javafx.geom.Vec3d(0, 0, 0), new com.sun.javafx.geom.Vec3d(0, 0, 1));
            return;
        }

        cameraTransform.setRotate(0);
        fr.tlse.irap.pos.Position lPos = pSe.getPosition();

//        double[] lSunAtt = SpaceElementCollection.getSpaceElementWrapper().getSun().getAttitude();
//        double[] lPosCarSph = CoordConverter.rectangularToCarrington(lPos.getX(), lPos.getY(), lPos.getZ(), lSunAtt);
//        lPosCarSph = CarringtonHelper.getCarringtonCoordinates(pSe);
//        System.out.println("*******************Got long Lat coord "+lPosCarSph[0]+" "+lPosCarSph[1]);
//        lPosCarSph = CarringtonHelper.computeCarringtonCoordinates(pSe,currentDate,false);
//        System.out.println("*******************Got long Lat at time "+lPosCarSph[0]+" "+lPosCarSph[1]);
        double[] lPosCarSph = CarringtonHelper.computeCarringtonCoordinates(pSe, currentDate, true);
        //log.info("Got long Lat at time " + lPosCarSph[0] + " " + lPosCarSph[1] + " length=" + lPos.length());
        double[] lPosCar3d = CoordConverter.toCartesian(lPosCarSph[0], lPosCarSph[1], lPos.length());

        cameraTransform.setPosition(lPosCar3d[0], lPosCar3d[1], lPosCar3d[2]);
        log.info("Set position to " + lPosCar3d[0] + " " + lPosCar3d[1] + " " + lPosCar3d[2]);
        //cameraTransform.lookAt(new javafx.geometry.Point3D(lPos.getX(),lPos.getY(),lPos.getZ()), javafx.geometry.Point3D.ZERO);
        //use pivot with matrix transform methods
        cameraTransform.lookAt(new com.sun.javafx.geom.Vec3d(0, 0, 0), new com.sun.javafx.geom.Vec3d(0, 0, 1));
    }

    /**
     * If line is shown, hide it. Otherwise, create or show it.
     *
     * @param pSe
     */
    private void setParkerSpiral(SpaceElement pSe) {
        if (probesFieldLines.get(pSe) != null && probesFieldLines.get(pSe).isVisible()) {
            setParkerSpiral(pSe, false,-1);
        } else {
            setParkerSpiral(pSe, true,-1);
        }
    }

    /**
     * Show/Hide a Parker field line from a
     *
     * @param pSe
     * @param pVsw Default Solar Wind speed. If -1, value is asked in a window.
     */
    private void setParkerSpiral(SpaceElement pSe, boolean pShow, double pVsw) {
        //test if just show hide object or compute is needed
        FieldLine3d lFlShape = probesFieldLines.get(pSe);
        if (lFlShape != null) {
            lFlShape.setVisible(pShow);
        } else if(pShow) {
            //build shape from field line
            double lVsw = pVsw;
            if(lVsw==-1) {
                lVsw = showLinePropertiesDialog(null, pSe);
            }
            if (lVsw != -1) {//ok button selected
                FieldLine lFl = FieldLine3d.getParkerFieldLine(pSe, lVsw);
                lFlShape = new FieldLine3d(lFl, lVsw, 200000f, Color.web(pSe.getColor()));
                lFlShape.setSpaceElement(pSe);
                probesFieldLines.put(pSe, lFlShape);
                objGroup.getChildren().add(lFlShape.getTg());
            }
        }
    }

    /**
     * If at least one line displayed, hide all. else show all
     */
    private void runShowHideAll() {
        boolean lShow = true;
        for (FieldLine3d lFl : probesFieldLines.values()) {
            if (lFl.isVisible()) {
                lShow = false;
            }
        }
        for (final SpaceElement spaceElement : SpaceElementCollection.getSpaceElementWrapper().getProbes()) {
            if (spaceElement.isDefined()) {
                setParkerSpiral(spaceElement, lShow, 400);
            }
        }
    }

    /**
     * get solar wind dialog input or edit in place field line properties
     *
     * @param pLine
     * @return
     */
    private double showLinePropertiesDialog(FieldLine3d pLine, SpaceElement pSe) {
        Dialog<String> dialog = new Dialog<>();
        if (pLine != null) {
            dialog.setTitle(pLine.getSpaceElement().getName() + " line properties");
        } else {
            dialog.setTitle("Choose solar wind speed (km/s)");
        }
        DecimalFormat lDf = new DecimalFormat("#.#");

        // Set the button types.
        ButtonType okButtonType = new ButtonType("OK", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(okButtonType, ButtonType.CANCEL);

        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        gridPane.setPadding(new Insets(20, 100, 10, 10));

        double lVsw = 400;
        if (pLine != null) {
            lVsw = pLine.getSolarWindSpeed();
        }

        int lYPos = 0;
        Label lLabel = new Label("SW speed");
        gridPane.add(lLabel, 0, lYPos);
        final TextField lTfVsw = new TextField("" + lVsw);
        gridPane.add(lTfVsw, 1, lYPos);
        Label lLUnit = new Label("km/s");
        gridPane.add(lLUnit, 2, lYPos);
        lYPos++;
        final TextField lTfSize = new TextField();
        if(pLine != null) {
            //add line width property
            lLabel = new Label("Line width");
            gridPane.add(lLabel, 0, lYPos);
            lTfSize.setText(""+pLine.getLineWidth());
            gridPane.add(lTfSize, 1, lYPos);
            Label lSizeUnit = new Label("km");
            gridPane.add(lSizeUnit, 2, lYPos);
            lYPos++;
        }
        
        
        CheckBox lCbx = new CheckBox("Get value from in situ data");
        gridPane.add(lCbx, 0, lYPos);
        lCbx.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (newValue) {
                    Date lStopDate = new Date(startDate.getTime() + 3600 * 1000l);
                    SolarSpeedWrapper speedsWrapper = ServiceFactory.getVPlotService().fetchSolarSpeeds(startDate, lStopDate, pSe.getName());
                    if (speedsWrapper.getSolarSpeeds().isEmpty()) {
                        DialogBox.warn("No data found");
                        lCbx.setSelected(false);
                    } else {
                        lTfVsw.setText(lDf.format(speedsWrapper.getSolarSpeeds().get(0).getSpeed()));
                    }
                }
            }
        });
        dialog.getDialogPane().setContent(gridPane);

//      // Convert the result value to return input value on ok button
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == okButtonType) {
                String lStr = lTfVsw.getText().isEmpty() ? "" + 400 : lTfVsw.getText();
                return lStr;
            }
            return null;
        });

        Optional<String> lRes = dialog.showAndWait();

        if (lRes.isPresent()) {
            if (pLine != null) {
                //update shape
                FieldLine lFl = FieldLine3d.getParkerFieldLine(pLine.getSpaceElement(), Double.parseDouble(lRes.get()));
                pLine.setSolarWindSpeed(Double.parseDouble(lRes.get()));
                float lLineSize = Float.parseFloat(lTfSize.getText());
                pLine.setNewPoints(lFl,lLineSize);
                
            } else {
                lVsw = Double.parseDouble(lRes.get());
            }
        } else {
            lVsw = -1;
        }
        return lVsw;
    }

    /**
     * Build Sun with texture, a transform to set Sphere in right orientation
     *
     * @return
     */
    private Shape3D buildSunShape() {
        //Group lGroup = new Group();
        int lRadius = 696340;
        Sphere sphere = new Sphere(lRadius);
        PhongMaterial material = new PhongMaterial();
        material.setDiffuseColor(Color.ORANGE);
        //material.setSpecularColor(Color.BLACK);
        //material.setSpecularPower(256);
        sphere.setMaterial(material);
        Xform lSunGroup = new Xform();
        lSunGroup.setRotateX(-90);//adjust to IAU frame
        lSunGroup.setRotateZ(-90);//adjust to IAU frame
        lSunGroup.getChildren().add(sphere);
        objGroup.getChildren().add(lSunGroup);
        return sphere;
    }

    /**
     * Map texture on Sun 3D object
     *
     * @param pImg
     */
    private void setSunMap(CarringtonMap pCm) {
        try {
            PhongMaterial lPMat = (PhongMaterial) sunShape.getMaterial();
            if(pCm!=null) {
            Image lSunImg = CarringtonHelper.createImage(pCm);
            
            //lPMat.
            lPMat.setDiffuseColor(Color.WHITE);
            lPMat.setDiffuseMap(lSunImg);
            //log.info("3D sun texture set!");
            } else {
                lPMat.setDiffuseColor(Color.ORANGE);
                lPMat.setDiffuseMap(null);
            }
        } catch (IOException ex) {
            log.warn("Cannot create image from carrington map " + ex.getMessage());
            ex.printStackTrace();
        }
    }

    /**
     * Add field lines in 3D scene from gridFieldLines object
     */
    private void drawGridFieldLines(int pIdx) {
        if (gridFieldLines == null || gridFieldLines.isEmpty()) {
            return;
        }
        log.debug("Draw grid with currIdx={} pIdx={} grflsize={}", currGridFiledLine, pIdx, gridFieldLines.size());
        
        // 12279 - Synchronization of 3D and Carrington map when changing PFSS altitude 
        // if (pIdx != currGridFiledLine && pIdx < gridFieldLines.size()) {
        if ( pIdx < gridFieldLines.size()) {
            currGridFiledLine = pIdx;
        } else {
            return;
        }
        if (gridFieldLinesShapes == null) {
            gridFieldLinesShapes = new Xform();
            objGroup.getChildren().add(gridFieldLinesShapes);
        }
        Xform lFlShapes = new Xform();
        for (FieldLine lFl : gridFieldLines.get(pIdx)) {
            List<Point3D> lPts = new ArrayList<>();
            for (FieldPoint lFp : lFl.getPoints()) {
                lPts.add(new Point3D((float) lFp.getX(), (float) lFp.getY(), (float) lFp.getZ()));
            }
            PolyLine3D lPLine = new PolyLine3D(lPts, 20000f, CarringtonGridFieldLines.getFLColor(lFl), LineType.RIBBON);
            lFlShapes.getChildren().add(lPLine);
        }
        gridFieldLinesShapes.getChildren().clear();
        gridFieldLinesShapes.getChildren().add(lFlShapes);
    }

    private void showHidePFSSGridLines() {
        if (gridFieldLinesShapes == null) {
            return;
        }
        gridFieldLinesShapes.setVisible(!gridFieldLinesShapes.isVisible());
    }

    /**
     * Build or update shock waves shapes structure
     */
    public void buildShockwavesShapes() {
        if (shockwavesShapes == null) {
            shockwavesShapes = new Xform();
            objGroup.getChildren().add(shockwavesShapes);
        }

        if (shockwaveEvent != null) {
            //for (Shockwave3dEvent lSe : shockwaveEvents.getEvents()) {
            //log.debug("Searching  for shockwave at "+currentDate);
            //Shockwave3d lSw3d = lSe.getNearestFromTime(currentDate);
            int lStepNo = (int) eclipticPlaneCtrl.getSwEventSlider().getValue();
            Shockwave3d lSw3d = shockwaveEvent.getShockwaves().get(lStepNo);
            if (lSw3d != null) {
                //log.debug("Found a shockwave to show at " + currentDate);
                //int lSelectedParam = swEventCbx.getSelectionModel().getSelectedIndex();
                //dynamic Colors
                double lMinMax[] = shockwaveEvent.getMinMaxList().get(dataTypeNo);
                //System.out.println("*** Default min/max for param " + dataTypeNo + " : " + lMinMax[0] + "/" + lMinMax[1]);
                String lUnit = "";
                //forced Colors
                switch (dataTypeNo) {
                    case 0://speed
                        //lMinMax = new double[]{100, 2000};
                        //lMinMax = new double[]{minColorBar, maxColorBar};                        
                        lUnit = "km/s";
                        break;
                    case 1://mach alfven
//                        lMinMax = new double[]{0, 10};
                        break;
                    case 2://mach fm
//                        lMinMax = new double[]{0, 10};
                        break;
                    case 3://comp ratio
//                        lMinMax = new double[]{0, 4};
                        break;
                    case 4://Theta_Bn
//                        lMinMax = new double[]{0, 90};
                        lUnit = "Deg";
                        break;
                    default:
                    //do nothing
                    }
                //11685
                lMinMax = new double[]{getTminColorBar(dataTypeNo), getTmaxColorBar(dataTypeNo)};

                //try to index points size based on shape index
                double lSize = (Math.pow(eclipticPlaneCtrl.getSwEventSlider().getValue(), 1.2) / 10 + 1) * 20000d;

                eclipticPlaneCtrl.refreshColorMap3d(lMinMax[0], lMinMax[1], lUnit);

                SimpleDateFormat lSdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
                lSdf.setTimeZone(TimeZone.getTimeZone("UTC"));
                DecimalFormat lDfSteps = new DecimalFormat("000");
                txt2DTimestamp.setText("Step: " + lDfSteps.format(lStepNo + 1) + "   Time: " + lSdf.format(lSw3d.getTime()));
                DecimalFormat lDf = new DecimalFormat("#.##");
                txt2DNosePosition.setText("Nose distance: " + lDf.format(lSw3d.getNoseDist() / RSUN) + " Rsun");

                if (shockwavesShapes.getChildren().isEmpty()) {
                    shockwavesShapes.getChildren().add(buildShockwaveShape(lSw3d, lSize, dataTypeNo, lMinMax[0], lMinMax[1]));

                    // 11685 remove Nose axis
                    //Create Nose axis group
//                    Group lAxis = getAxis(Color.ORANGE);
//                    lAxis.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS));
//                    lAxis.getTransforms().add(new Scale(1,1,1));
//                    Pivot lAxisPivot = new Pivot();
//                    lAxisPivot.getChildren().add(lAxis);
//                    shockwavesShapes.getChildren().add(lAxisPivot);
                } else {
                    setShockwaveShape((ScatterMesh) shockwavesShapes.getChildren().get(0), lSw3d, lSize, dataTypeNo, lMinMax[0], lMinMax[1]);
                }

                //11685 remove Nose axis
//                Pivot lAxis = (Pivot)shockwavesShapes.getChildren().get(1);
//                //lAxis.getTransforms().clear();
//                double lScale = lSw3d.getNoseDist()/50;
//                ((Group)lAxis.getChildren().get(0)).getTransforms().set(1,new Scale(lScale,lScale,lScale));
//                double[] lPosDb = lSw3d.getNosePos();
//                lAxis.lookAt(new Vec3d(lPosDb[0],lPosDb[1],lPosDb[2]), new Vec3d(0, 0, 1));
            }
            //}
        }
    }

    /**
     * Build a shockwave shape from data and parameter index. 0: Shock speed
     * (mk/s) 1: Mach_Alfven 2 :Mach_fm 3: Comp_Ratio 4: Theta_BN
     *
     * @param pObj
     * @param pParamId
     * @return Shape with selected parameter.
     */
    private static ScatterMesh buildShockwaveShape(Shockwave3d pSw3d, double pSize, int pParamId, double pColorMin, double pColorMax) {

        /**
         * **** scatter plot with triangle mesh ******
         */
        List<Point3D> lPts = new ArrayList<>();
        for (int i = 0; i < pSw3d.getPositions().size(); i++) {
            double[] lPos = pSw3d.getPositions().get(i);
            double[] lData = pSw3d.getParameters().get(i);
            if (!Double.isNaN(lData[pParamId])) {
                lPts.add(new Point3D((float) lPos[0], (float) lPos[1], (float) lPos[2], (float) lData[pParamId]));
            }
            //System.out.println("****"+i+" Adding point "+(float)lPos[0]+" val="+lData[pParamId]);
        }
        //System.out.println("*** Added " + lPts.size() + " points");
        // Create a TriangleMesh
        ScatterMesh lMesh = new ScatterMesh(lPts, true, pSize, 0);
        //TriangulatedMesh lMesh = new TriangulatedMesh(lPts,0,100000,0);// not working
        lMesh.setTextureModeVertices3D(2500, p -> p.f, pColorMin, pColorMax);
        lMesh.setTextureModeVertices3D(Palette.ColorPalette.rainbow, 2500, p -> p.f);

        return lMesh;
    }

    /**
     * Update a shockwave shape from data and parameter index. 0: Shock speed
     * (mk/s) 1: Mach_Alfven 2 :Mach_fm 3: Comp_Ratio 4: Theta_BN
     *
     * @param pObj
     * @param pParamId
     * @return Shape with selected parameter.
     */
    private static ScatterMesh setShockwaveShape(ScatterMesh pMesh, Shockwave3d pSw3d, double pSize, int pParamId, double pColorMin, double pColorMax) {

        /**
         * **** scatter plot with triangle mesh ******
         */
        List<Point3D> lPts = new ArrayList<>();
        for (int i = 0; i < pSw3d.getPositions().size(); i++) {
            double[] lPos = pSw3d.getPositions().get(i);
            double[] lData = pSw3d.getParameters().get(i);
            if (!Double.isNaN(lData[pParamId])) {
                lPts.add(new Point3D((float) lPos[0], (float) lPos[1], (float) lPos[2], (float) lData[pParamId]));
            }
            //System.out.println("****"+i+" Adding point "+(float)lPos[0]+" val="+lData[pParamId]);
        }
        //System.out.println("*** Added " + lPts.size() + " points. Colors: " + pColorMin + " " + pColorMax + " , size: " + pSize);
        // Create a TriangleMesh
        pMesh.setScatterData(lPts);
        pMesh.setHeight(pSize);
        pMesh.setTextureModeVertices3D(2500, p -> p.f, pColorMin, pColorMax);

        return pMesh;
    }

    /**
     *
     */
    public void resizeRectangle(Double scale) {
        rectangleBorder.setWidth(rectangleBorder.getWidth() * scale);
        rectangleBorder.setHeight(rectangleBorder.getHeight() * scale);
        rectangleBorder.setX(-rectangleBorder.getWidth() / 2);
        rectangleBorder.setY(-rectangleBorder.getHeight() / 2);
    }

    /**
     *
     */
    public void initRectangleBorder() {
        rectangleBorder.setWidth(widthZone);
        rectangleBorder.setHeight(heightZone);
        rectangleBorder.setX(-rectangleBorder.getWidth() / 2);
        rectangleBorder.setY(-rectangleBorder.getHeight() / 2);
        rectangleBorder.setId(RECT_BORDER_ID);
    }

    /**
     * reset default view
     */
    public void resetView() {
        //rootDrawing3d.getChildren().clear();
        rootDrawing3d.getChildren().add(rectangleBorder);
        initRectangleBorder();
        resizeRectangle(30.0);
    }

    private Node findByName(String id) {
        return DrawHelper.findByName(rootDrawing3d.getChildren(), id);
    }

    public void disableRightClick() {
        rootDrawing3d.addEventFilter(MouseEvent.ANY, filter);
    }

    public void enableRightClick() {
        rootDrawing3d.removeEventFilter(MouseEvent.ANY, filter);
    }

    public void setDrawingCME(boolean isDrawingCME) {
//        this.isDrawingCME = isDrawingCME;
//        if (cme != null) {
//            if (cmeType.equals(CMEType.SSE)) {
//                cmeFixPoint.setDisable(true);
//                cmeSSE.setDisable(!isDrawingCME);
//            } else {
//                cmeFixPoint.setDisable(!isDrawingCME);
//                cmeSSE.setDisable(true);
//            }
//        }
    }

    public boolean isDrawingShock() {
        return shockwaveEvent != null && !shockwaveEvent.getShockwaves().isEmpty();
    }

    private void waitCursor() {
        //rootDrawing3d.setDisable(true);
        //System.out.println("Wait cursor set!");
        eclipticPlaneCtrl.setCursor(Cursor.WAIT);
    }

    private void endCursor() {
        //rootDrawing3d.setDisable(false);
        //System.out.println("Wait cursor removed");
        eclipticPlaneCtrl.setCursor(null);
    }

    private boolean setUpImage(Date pDate) {
//        try {
        CarringtonModel.getTreeCMaps();
        carringtonModel.reset(pDate);
        cmType.setProbe(carringtonModel.getObserver().get());
        cmType.setMeasurement(carringtonModel.getMeasurement().get());
        cmType.setInstrument(carringtonModel.getInstrument().get());
        cmType.getRadialDistances().clear();
//        actionCancelled = false;
//        } catch (Exception e) {
//            log.warn("Image not available - " + e.getMessage());
//            DialogBox.warn("Image not available !");
//            return false;
//        }
        return true;
    }

    /**
     * old method
     */
    public void update(Date pStartDate, Date pStopDate) {
        //only refetch on start date change
        if ((pStartDate == null || pStartDate.equals(startDate)) && stopDate != null) {
            log.debug("Avoid update: start=" + pStartDate + " " + startDate + " stop=" + pStopDate + " " + stopDate);
            return;
        }
        startDate = pStartDate;
        stopDate = pStopDate;
        currentDate = pStartDate;
        setUpImage(currentDate);
        fetchSunMap();
        //fetchShockwaves(currentDate);
    }

    /**
     * update shockwave
     *
     * @param pDate
     */
    public void updateShowckwave(Date pDate) {
        //only refetch on start date change
        if ((pDate == null || pDate.equals(currentDate))) {
            log.debug("Avoid update: start=" + currentDate + " " + pDate);
            return;
        }
        //currentDate = pDate;
        setUpImage(pDate);
        fetchSunMap();
        fetchShockwaves(pDate);
    }

    private void fetchSunMap() {
        final Task<Object> lWorker = this.updateImageWorker();
        lWorker.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) {
                    setSunMap(carringtonMap);
                    //endCursor();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    log.warn("Carrington map not found - " + lWorker.getException().getMessage());
//                    if (actionCancelled) {
//                        DialogBox.warn("Image not available !");
//                        //carringtonModel.update(cmType);
//                    } else {
//                        //carringtonModel.setCarringtonImg(null);
//                    }
//                    endCursor();
                    setSunMap(null);
                }
            }
        });
        new Thread(lWorker).start();
        //waitCursor();
    }

    private Task<Object> updateImageWorker() {
        return new Task<Object>() {
            @Override
            //protected Object call() throws Exception {
            protected Object call() {
                carringtonMap = ServiceFactory.getCarringtonMapService().getCarringtonMap(currentDate, cmType);
                return true;
            }
        };
    }

    private void fetchShockwaves(final Date pDate) {
        final Task<Object> lWorker = this.updateShockwavesWorker(pDate);
        lWorker.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) {
                    log.debug("Shockwave succeeded - found " + shockwaveEvent.getShockwaves().size() + " steps.");
                    if (shockwaveEvent != null && !shockwaveEvent.getShockwaves().isEmpty()) {
                        //get first and init slider number of step on it
                        eclipticPlaneCtrl.getSwEventSlider().setMin(0);
                        eclipticPlaneCtrl.getSwEventSlider().setMax(shockwaveEvent.getShockwaves().size() - 1);
                        eclipticPlaneCtrl.getSwEventSlider().setBlockIncrement(1);
                        eclipticPlaneCtrl.getSwEventSlider().setVisible(true);
                        eclipticPlaneCtrl.getPalette3d().setVisible(true);
                        buildShockwavesShapes();
                    } else {
                        eclipticPlaneCtrl.getSwEventSlider().setVisible(false);
                        eclipticPlaneCtrl.getPalette3d().setVisible(false);
                    }
                    endCursor();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    log.warn("Shockwaves not found - " + lWorker.getException().getMessage());
//                    if (actionCancelled) {
//                        DialogBox.warn("Image not available !");
//                        //carringtonModel.update(cmType);
//                    } else {
//                        //carringtonModel.setCarringtonImg(null);
//                    }
//                    endCursor();
                }
            }
        });
        new Thread(lWorker).start();
        waitCursor();
    }

    private Task<Object> updateShockwavesWorker(final Date pDate) {
        return new Task<Object>() {
            @Override
            //protected Object call() throws Exception {
            protected Object call() {
                //log.debug("**********Calling fetchShockwaves with " + startDate + " and " + stopDate);
                long lStartTime = System.currentTimeMillis();
                shockwaveEvent = ServiceFactory.getShockService().fetchShockwave3d(pDate);
                log.debug("Return fetchShockwave Nb found: " + shockwaveEvent.getShockwaves().size() + ". Read in " + (System.currentTimeMillis() - lStartTime) / 1000d + "s");

                return true;
            }
        };
    }

    //11685 Min and max color bar
    public double getMinColorBar() {
        return minColorBar;
    }

    public void setMinColorBar(double minColorBar) {
        this.minColorBar = minColorBar;
    }

    public double getMaxColorBar() {
        return maxColorBar;
    }

    public void setMaxColorBar(double maxColorBar) {
        this.maxColorBar = maxColorBar;
    }

    public double getTminColorBar(int idx) {
        return tMinColorBar[idx];
    }

    public void setTminColorBar(int idx, double pMinColorBar) {
        this.tMinColorBar[idx] = pMinColorBar;
    }

    public double getTmaxColorBar(int idx) {
        return tMaxColorBar[idx];
    }

    public void setTmaxColorBar(int idx, double pMaxColorBar) {
        this.tMaxColorBar[idx] = pMaxColorBar;
    }

    public int getDataTypeNo() {
        return dataTypeNo;
    }

    public void setDataTypeNo(int dataTypeNo) {
        this.dataTypeNo = dataTypeNo;
    }

    public String gettDataTypeName(int pidx) {
        return tDataTypeName[pidx];
    }

    public void settDataTypeName(String[] tDataTypeName) {
        this.tDataTypeName = tDataTypeName;
    }

    /**
     * gridField lines from carrington component
     *
     * @return the gridFieldLines
     */
    public List<List<FieldLine>> getGridFieldLines() {
        return gridFieldLines;
    }

    /**
     * gridField lines from carrington component
     *
     * @param pGridFieldLines the gridFieldLines to set
     */
    public void setGridFieldLines(List<List<FieldLine>> pGridFieldLines, List<Date> pDates) {
        gridFieldLines = pGridFieldLines;
        gridFlTimes = pDates;
    }

    public void showGridFieldLine(Date pDate) {
        int i = -1;
        if (gridFieldLines == null || gridFlTimes == null || gridFlTimes.isEmpty()) {
            return;
        }
        //System.out.println("GFLines: show " + pDate + " in " + flTimes.size() + " array.");
        //find date
        for (Date lDate : gridFlTimes) {
            //System.out.println("Compare " + lDate + " -- " + pDate);
            if (pDate.before(lDate)) {
                //System.out.println("Show " + lDate + " for " + pDate);
                break;
            }
            i++;
        }
        if (i > -1) {
            drawGridFieldLines(i);
        }
    }

}
