/* ----------------------------------------
 * Origin  : Akkodis 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : V2.12
 * DM-ID : Redmine#11103 : 25/03/2025 : LBE : Radio spectro interface added
 * ----------------------------------------
 */
 
 package fr.tlse.irap.pt.client.controller.tools.plot;

import java.util.ArrayList;
import java.util.List;

import fr.tlse.irap.pt.client.controller.common.Formatter;
import java.text.NumberFormat;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.chart.ValueAxis;
import javafx.util.Duration;

/**
 * A logarithmic axis implementation for JavaFX 2 charts<br>
 * <br>
 *
 * @author Kevin Senechal
 *
 */
public class LogarithmicAxis extends ValueAxis<Number> {

    /**
     * The time of animation in ms
     */
    private static final double ANIMATION_TIME = 2000;
    private final Timeline lowerRangeTimeline = new Timeline();
    private final Timeline upperRangeTimeline = new Timeline();

    private final DoubleProperty logUpperBound = new SimpleDoubleProperty();
    private final DoubleProperty logLowerBound = new SimpleDoubleProperty();

    public LogarithmicAxis() {
//        super(Math.pow(10, -10), Math.pow(10, -3));
        super();
        bindLogBoundsToDefaultBounds();
    }

    public LogarithmicAxis(double lowerBound, double upperBound) {
        super(lowerBound, upperBound);
        try {
            validateBounds(lowerBound, upperBound);
            bindLogBoundsToDefaultBounds();
        } catch (IllegalLogarithmicRangeException e) {
            e.printStackTrace();
        }
    }

//    public void setLogarithmizedUpperBound(double d) {
//        double nd = Math.pow(10, Math.ceil(Math.log10(d)));
//        setUpperBound(nd == d ? nd * 10 : nd);
//    }
    /**
     * Bind our logarithmic bounds with the super class bounds, consider the
     * base 10 logarithmic scale.
     */
    private void bindLogBoundsToDefaultBounds() {
        logLowerBound.bind(new DoubleBinding() {

            {
                super.bind(lowerBoundProperty());
            }

            @Override
            protected double computeValue() {
                return Math.log10(lowerBoundProperty().get());
            }
        });
        logUpperBound.bind(new DoubleBinding() {

            {
                super.bind(upperBoundProperty());
            }

            @Override
            protected double computeValue() {
                return Math.log10(upperBoundProperty().get());
            }
        });
    }

    /**
     * Validate the bounds by throwing an exception if the values are not
     * conform to the mathematics log interval: ]0,Double.MAX_VALUE]
     *
     * @param lowerBound
     * @param upperBound
     * @throws IllegalLogarithmicRangeException
     */
    private void validateBounds(double lowerBound, double upperBound) throws IllegalLogarithmicRangeException {
        if (lowerBound < 0 || upperBound < 0) {
            throw new IllegalLogarithmicRangeException("The logarithmic range should be include to ]0,Double.MAX_VALUE] and the lowerBound should be less than the upperBound");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected List<Number> calculateMinorTickMarks() {
        Number[] range = getRange();
        
        List<Number> minorTickMarksPositions = new ArrayList<Number>();
        if (range != null) {

            Number lowerBound = range[0];
            Number upperBound = range[1];
            double lLogLowerBound = Math.log10(lowerBound.doubleValue());
            double lLogUpperBound = Math.log10(upperBound.doubleValue());
            int minorTickMarkCount = getMinorTickCount();
            

            // GFI Correction
            if (lLogLowerBound < lLogUpperBound) {
                for (double i = -30; i <= lLogUpperBound; i += 1) {
                    //for (double i = logLowerBound; i <= logUpperBound; i += 1) {
                    for (double j = 0; j <= 9; j += (1. / minorTickMarkCount)) {
                        double value = j * Math.pow(10, i);
                        //if (i >= logLowerBound) {
                        minorTickMarksPositions.add(value);
                        //}
                    }
                }
            } else {
                for (double i = -30; i <= lLogLowerBound; i += 1) {
                    //for (double i = logLowerBound; i <= logUpperBound; i += 1) {
                    //minorTickMarkCount = 10;
                    for (double j = 0; j <= 9; j += (1. / minorTickMarkCount)) {
                        double value = j * Math.pow(10, i);
                        //if (i >= logLowerBound) {
                        minorTickMarksPositions.add(value);
                        //}
                    }
                }
            }
        }
        return minorTickMarksPositions;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected List<Number> calculateTickValues(double length, Object range) {
        List<Number> tickPositions = new ArrayList<Number>();
        if (range != null) {
            Number lowerBound = ((Number[]) range)[0];
            Number upperBound = ((Number[]) range)[1];

            double lLogLowerBound = Math.log10(lowerBound.doubleValue());
            double lLogUpperBound = Math.log10(upperBound.doubleValue());

            int lEndJ = 1;
            if (Math.abs(lLogUpperBound - lLogLowerBound) < 1) {
                //if small range, add big ticks and labels between 10 powers
                lEndJ = 10;
            }

            if (lLogLowerBound < lLogUpperBound) {
                //for (double i = logLowerBound; i <= logUpperBound; i += 1) {
                for (double i = -30; i <= lLogUpperBound; i += 1) {
                    // GFI Correction
                    for (double j = 1; j <= lEndJ; j++) {
                        double value = j * Math.pow(10, i);
                        //if (i >= logLowerBound) {
                        tickPositions.add(value);
                        //}
                    }
                    /* Stephane
              double value = Math.pow(10, i);
              tickPositions.add(value);
                     */
                }
            } else {//reverse axis

                //for (double i = logLowerBound; i <= logUpperBound; i += 1) {
                for (double i = -30; i <= lLogLowerBound; i += 1) {
                    // GFI Correction
                    for (double j = 1; j <= lEndJ; j++) {
                        double value = j * Math.pow(10, i);
                        //if (i >= logLowerBound) {
                        tickPositions.add(value);
                        //}
                    }
                }
            }
        }
        return tickPositions;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Number[] getRange() {
        return new Number[]{lowerBoundProperty().get(), upperBoundProperty().get()};
    }

//@Override
    protected String getTickMarkLabels(Number value) {
        NumberFormat formatter = NumberFormat.getInstance();
        formatter.setMaximumIntegerDigits(6);
        formatter.setMinimumIntegerDigits(1);
        return formatter.format(value);
    }

    /**
     * {@inheritDoc}
     */
    //@Override
    protected String getTickMarkLabel(Number value) {
        // GFI Correction
        //return "10^" + (int) Math.log10(value.intValue());
        return Formatter.getFormatterExp().format(value);
//        String res = "10";
//        int log = (int) Math.log10(value.doubleValue());
//        if (log < 0) {
//        	res += "\u207B";
//        }
//        switch (log) {
//        	case 1:
//        		res += "\u00B1";
//        		break;
//        	case 2:
//        		res += "\u00B2";
//        		break;
//        	case 3:
//        		res += "\u00B3";
//        		break;
//        	case 4:
//        		res += "\u2074";
//        		break;
//        	case 5:
//        		res += "\u2075";
//        		break;
//        	case 6:
//        		res += "\u2076";
//        		break;
//        	case 7:
//        		res += "\u2077";
//        		break;
//        	case 8:
//        		res += "\u2078";
//        		break;
//        	case 9:
//        		res += "\u2079";
//        		break;
//        }
//
//       	return res;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void setRange(Object range, boolean animate) {
        if (range != null) {
            Number lowerBound = ((Number[]) range)[0];
            Number upperBound = ((Number[]) range)[1];
            try {
                validateBounds(lowerBound.doubleValue(), upperBound.doubleValue());
            } catch (IllegalLogarithmicRangeException e) {
                e.printStackTrace();
            }
            if (animate) {
                try {
                    lowerRangeTimeline.getKeyFrames().clear();
                    upperRangeTimeline.getKeyFrames().clear();

                    lowerRangeTimeline.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, new KeyValue(lowerBoundProperty(), lowerBoundProperty().get())),
                            new KeyFrame(new Duration(ANIMATION_TIME), new KeyValue(lowerBoundProperty(), lowerBound.doubleValue())));

                    upperRangeTimeline.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, new KeyValue(upperBoundProperty(), upperBoundProperty().get())),
                            new KeyFrame(new Duration(ANIMATION_TIME), new KeyValue(upperBoundProperty(), upperBound.doubleValue())));
                    lowerRangeTimeline.play();
                    upperRangeTimeline.play();
                } catch (Exception e) {
                    lowerBoundProperty().set(lowerBound.doubleValue());
                    upperBoundProperty().set(upperBound.doubleValue());
                }
            }
            lowerBoundProperty().set(lowerBound.doubleValue());
            upperBoundProperty().set(upperBound.doubleValue());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Number getValueForDisplay(double displayPosition) {
        double delta = logUpperBound.get() - logLowerBound.get();
        if (getSide().isVertical()) {
            return Math.pow(10, (((displayPosition - getHeight()) / -getHeight()) * delta) + logLowerBound.get());
        } else {
            return Math.pow(10, (((displayPosition / getWidth()) * delta) + logLowerBound.get()));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public double getDisplayPosition(Number value) {
        double delta = logUpperBound.get() - logLowerBound.get();
            double deltaV = Math.log10(value.doubleValue()) - logLowerBound.get();
            if (getSide().isVertical()) {
                return (1. - ((deltaV) / delta)) * getHeight();
            } else {
                return ((deltaV) / delta) * getWidth();
            }
    }

    @Override
    protected Object autoRange(double minValue, double maxValue, double length, double labelSize) {
        Double[] range = new Double[]{minValue, maxValue};
        return range;
    }

}
