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

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.Date;
import java.util.List;

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

import fr.tlse.irap.pt.client.component.CustomComponent;
import fr.tlse.irap.pt.client.component.ICustomComponent;
import fr.tlse.irap.pt.client.controller.common.Formatter;
import fr.tlse.irap.pt.client.controller.tools.jmap.JMapCorotationComponent;
import fr.tlse.irap.pt.client.controller.tools.jmap.JMapRadialComponent;
import fr.tlse.irap.fit.Fit;
import fr.tlse.irap.fit.FitImages;
import fr.tlse.irap.fit.FitPoint;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXML;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;

/**
 * Graphic component for timetable of arrival times at spacecrafts and planets
 * (see IRAP specification : RadialTool_maquette.pdf on 29/10/2012)
 */
public class FitParameterComponent extends VBox implements ICustomComponent {

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

    @FXML protected VBox vBoxViewFitParam;
    @FXML protected TextField vleField;
    @FXML protected TextField betaField;
    @FXML protected TextField deltaVField; 
    @FXML protected TextField deltaBetaField;
    @FXML protected GridPane gridBestFit;

    @FXML private LineChart<Number, Number> lineChartBestFit;
    private Series<Number, Number> fitPointsPlot = new Series<Number, Number>();
    private Series<Number, Number> clickPointsPlot = new Series<Number, Number>();

    @FXML protected GridPane gridError;
    @FXML protected LineChart<Number, Number> errorLineChart;

    // ContourPlot parameters
    ContourPlot thePlot;
    private String catalog;
    private String observer;
    private List<FitPoint> fitPoints;
    private List<FitPoint> clickPoints;
    private double[][] fitError;

    // ContourPlot color scale
    Canvas colorScale;
    @FXML LineChart<Number, Number> colorScaleChart;

    // Calculated values
    @FXML protected ImageView imageViewFitPlot;
    @FXML protected ImageView imageViewErrorPlot;
    private Date startTime;

    private static int TICK_RATIO = 4; 

    public FitParameterComponent() {
        super();
        CustomComponent.initCustomComponent(this);

        // Formatage des labels des abscisses
        NumberAxis.DefaultFormatter xAxisFormatter = new NumberAxis.DefaultFormatter((NumberAxis) lineChartBestFit.getXAxis()) {
            @Override
            public String toString(Number object) {
            	return Formatter.getTdf().format(object);
            }
        };

        // lineChartBestFit[0]: fit points
        // lineChartBestFit[1]: click points
        lineChartBestFit.getData().add(fitPointsPlot);
        lineChartBestFit.getData().add(clickPointsPlot);

        // some customization
        lineChartBestFit.getStylesheets().add(getClass().getResource("FitParameterComponent.css").toString());
        ((NumberAxis) lineChartBestFit.getXAxis()).setTickLabelFormatter(xAxisFormatter);

        GridPane.setHalignment(imageViewFitPlot, HPos.CENTER);
        GridPane.setHalignment(imageViewErrorPlot, HPos.CENTER);
    }

    public void setJMapRadialComponent(JMapRadialComponent jMapRadialComponent) {
    	catalog = "CME";
        // Subscription on clicked points changes (fitError)
        jMapRadialComponent.setFitParameterComponent(this);
        
    }

    public void setJMapCorotationComponent(JMapCorotationComponent jMapCorotationComponent) {
    	catalog = "CIR";
        // Subscription on clicked points changes (fitError)
        jMapCorotationComponent.setFitParameterComponent(this);
    }

    public Node getView() {
        return this;
    }

    public boolean isEmpty() {
        if (imageViewFitPlot != null) {
            return false;
        } else if (thePlot != null) {
            return false;
        } else {
            return true;
        }
    }

    /**
     *  
     * @param observer
     * @param fit
     */
    public void setFitParam(String observer, Fit fit) {
		setFitParam(observer, fit.getSunTime().getTime(), fit.getBeta(), fit.getSpeed(), fit.getBetaError(), fit.getSpeedError(), null, null, null);    	
    }

    /**
     * 
     * @param betaStart
     * @param speedSolarWind
     * @param fitPoints
     * @param clickPoints
     * @param fitError
     */
    public void setFitParam(Double betaStart, Double speedSolarWind, List<FitPoint> fitPoints, List<FitPoint> clickPoints, double[][] fitError) {
		setFitParam(null, null, betaStart, speedSolarWind, null, null, fitPoints, clickPoints, fitError);    	
    }

    private void setFitParam(String observer, Date startTime, Double betaStart, Double speedSolarWind, Double betaError, Double speedError, List<FitPoint> fitPoints, List<FitPoint> clickPoints, double[][] fitError) {

        clearFitParam();

        this.observer = observer;
        this.startTime = startTime;

        // FitPoints
        this.fitPoints = fitPoints;
        this.clickPoints = clickPoints;

        // FitError
        this.fitError = fitError;

        // update view fields 
        betaField.setText(Formatter.toDecimal1D(betaStart));
        vleField.setText(Formatter.toDecimal1D(speedSolarWind));
        deltaBetaField.setText(Formatter.toDecimal1D(betaError));
        deltaVField.setText(Formatter.toDecimal1D(speedError));

        // draw
        if (startTime != null) {
        	useCalculatedValues();
            lineChartBestFit.setVisible(false);
            errorLineChart.setVisible(false);
            colorScaleChart.setVisible(false);
        }
        else {
        	drawBestFitGraph();
        	drawFitError();
            lineChartBestFit.setVisible(true);
            colorScaleChart.setVisible(true);
            errorLineChart.setVisible(true);
        }
    }

    public void reset() {
        clearFitParam();

        // reset view fields 
        betaField.setText(null);
        vleField.setText(null);
        deltaBetaField.setText(null);
        deltaVField.setText(null);

        // Set default 
        lineChartBestFit.setVisible(true);
        colorScaleChart.setVisible(true);
        errorLineChart.setVisible(true);
    }

    private void drawBestFitGraph() {
        for (int i = 0; i < fitPoints.size(); i++) {
            fitPointsPlot.getData().add(new Data<Number, Number>(fitPoints.get(i).getTime(), fitPoints.get(i).getElongation()));
        }
        long timeGap = 1000;
        FitPoint P1 = fitPoints.get(0);
		FitPoint P2 = fitPoints.get(fitPoints.size() - 1);
        ((NumberAxis) lineChartBestFit.getXAxis()).setLowerBound(P1.getTime() - timeGap);
        ((NumberAxis) lineChartBestFit.getXAxis()).setUpperBound(P2.getTime() + timeGap);
        ((NumberAxis) lineChartBestFit.getXAxis()).setTickUnit((P2.getTime() - P1.getTime()) / TICK_RATIO);
        
        for (int i = 0; i < clickPoints.size(); i++) {
            clickPointsPlot.getData().add(new Data<Number, Number>(clickPoints.get(i).getTime(), clickPoints.get(i).getElongation()));
        }

    }

    /**
     * Convenience method that returns a scaled instance of the provided
     * {@code BufferedImage}.
     * 
     * @param img
     *            the original image to be scaled
     * @param targetWidth
     *            the desired width of the scaled instance, in pixels
     * @param targetHeight
     *            the desired height of the scaled instance, in pixels
     * @param hint
     *            one of the rendering hints that corresponds to
     *            {@code RenderingHints.KEY_INTERPOLATION} (e.g.
     *            {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
     *            {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
     *            {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
     * @param higherQuality
     *            if true, this method will use a multi-step scaling technique
     *            that provides higher quality than the usual one-step technique
     *            (only useful in downscaling cases, where {@code targetWidth}
     *            or {@code targetHeight} is smaller than the original
     *            dimensions, and generally only when the {@code BILINEAR} hint
     *            is specified)
     * @return a scaled version of the original {@code BufferedImage}
     */
    public BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean higherQuality) {
        int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        BufferedImage ret = (BufferedImage) img;
        int w, h;
        if (higherQuality) {
            // Use multi-step technique: start with original size, then
            // scale down in multiple passes with drawImage()
            // until the target size is reached
            w = img.getWidth();
            h = img.getHeight();
        } else {
            // Use one-step technique: scale directly from original
            // size to target size with a single drawImage() call
            w = targetWidth;
            h = targetHeight;
        }

        do {
            if (higherQuality && w > targetWidth) {
                w /= 2;
                if (w < targetWidth) {
                    w = targetWidth;
                }
            }

            if (higherQuality && h > targetHeight) {
                h /= 2;
                if (h < targetHeight) {
                    h = targetHeight;
                }
            }

            BufferedImage tmp = new BufferedImage(w, h, type);
            Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();

            ret = tmp;
        } while (w != targetWidth || h != targetHeight);

        return ret;
    }

    /**
     * Draw the fit error plot.
     */
    public void drawFitError() {
        // draw contour plot
        try {

            double[][] temp = new double[ContourPlotParams.ELONGATION_MAX][ContourPlotParams.SPEED_MAX - ContourPlotParams.SPEED_MIN];
            for (int i = 0; i < fitError.length; i++) {
                for (int j = 0; j < fitError[i].length; j++) {
                    temp[fitError.length - 1 - i][j] = fitError[i][j];
                }
            }
            fitError = temp;

            if (thePlot == null) {
                thePlot = new ContourPlot(errorLineChart.getXAxis().widthProperty().getValue().intValue(), errorLineChart.getYAxis().heightProperty().getValue().intValue());
                thePlot.setTranslateX(errorLineChart.getYAxis().getLayoutX() + errorLineChart.getYAxis().getWidth());
                thePlot.setTranslateY(errorLineChart.getXAxis().getLayoutY() + errorLineChart.getPadding().getTop() - errorLineChart.getYAxis().getHeight());

                // color scale
                colorScale = new Canvas(colorScaleChart.getXAxis().getWidth(), colorScaleChart.getYAxis().getHeight());
                colorScale.setTranslateX(colorScaleChart.getXAxis().getLayoutX());
                colorScale.setTranslateY(colorScaleChart.getXAxis().getLayoutY() + colorScaleChart.getPadding().getTop() - colorScaleChart.getYAxis().getHeight());
                GraphicsContext g = colorScale.getGraphicsContext2D();
                for (int i = 0; i < 251; i += 5) {
                    g.setFill(Color.hsb(250 - i, 1.0, 1.0));
                    g.fillRect(0, colorScale.getHeight() - colorScale.getHeight() * i / 250, colorScale.getWidth(), 5);
                }

                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        GridPane.setColumnIndex(thePlot, 1);
                        GridPane.setRowIndex(thePlot, 1);
                        GridPane.setHalignment(thePlot, HPos.LEFT);
                        GridPane.setValignment(thePlot, VPos.TOP);
                        gridError.getChildren().add(thePlot);

                        // color scale
                        GridPane.setColumnIndex(colorScale, 2);
                        GridPane.setRowIndex(colorScale, 1);
                        GridPane.setHalignment(colorScale, HPos.LEFT);
                        GridPane.setValignment(colorScale, VPos.TOP);
                        gridError.getChildren().add(colorScale);
                    }
                });

                thePlot.setLogInterpolation(true);
                thePlot.setZ(fitError);
            }

            thePlot.paint();

            // Calculate Delta V and Delta Beta
            deltaVField.setText("" + thePlot.getSpeedError());
            deltaBetaField.setText("" + thePlot.getBetaError());

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // System.out.println("Exiting DrawTheContourPlot");
            //log.info("Exiting DrawTheContourPlot");
        }
    }

    /**
     * When data have already been calculated, series and image are drawn as is
     */
    private void useCalculatedValues() {
        // Call the webservice to download images
    	FitImages fitImages;
        if (catalog == "CME") {
            fitImages = ServiceFactory.getFitService().fetchImagesCME(startTime, observer);
        }
        else {
            fitImages = ServiceFactory.getFitService().fetchImagesCIR(startTime, observer);
        }
        // Use already calculated fit plot
        java.awt.Image imageFitPlotAwt = fitImages.getImageFitPlot();
        if (imageFitPlotAwt != null) {
        	Image imageFitPlot = SwingFXUtils.toFXImage(getScaledInstance((BufferedImage) imageFitPlotAwt, 270, 200, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true), null);
        	imageViewFitPlot.setImage(imageFitPlot);
        }
        
        // Use already calculated error plot
        java.awt.Image imageFitErrorPlotAwt = fitImages.getImageFitErrorPlot();
        if (imageFitErrorPlotAwt != null) {
            Image imageErrorPlot = SwingFXUtils.toFXImage(getScaledInstance((BufferedImage) imageFitErrorPlotAwt, 340, 255, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true), null);
            imageViewErrorPlot.setImage(imageErrorPlot);
        }
    }

    /**
     * Clear charts and data
     */
    public void clearFitParam() {

        // reset all previous values
        if (lineChartBestFit != null) {
            lineChartBestFit.getData().get(0).getData().clear();
            lineChartBestFit.getData().get(1).getData().clear();
        }
        if (thePlot != null) {
            try {
                thePlot.getGraphicsContext2D().clearRect(0, 0, thePlot.getWidth(), thePlot.getHeight());
                colorScale.getGraphicsContext2D().clearRect(0, 0, colorScale.getWidth(), colorScale.getHeight());
            } catch (Exception e) {
                e.printStackTrace();
            }
            thePlot = null;
            colorScale = null;
        }
        imageViewFitPlot.setImage(null);
        imageViewErrorPlot.setImage(null);

        // FitPoints
        this.fitPoints = null;
        this.clickPoints = null;

        // FitError
        this.fitError = null;
    }
}
