package org.helioviewer.jhv.timelines.draw;

import org.helioviewer.jhv.math.MathUtils;

public class YAxis {

    private double start;
    private double end;

    private final YAxisScale scale;

    private static final float DISCARD_LEVEL_LINEAR_LOW = -100;
    private static final float DISCARD_LEVEL_LOG_LOW = 1e-10f;
    private static final float DISCARD_LEVEL_HIGH = 1e7f; // solar wind temp, xray flux: 1e4;

    private static final double ZOOMSTEP_PERCENTAGE = 0.02;
    private static final float UNSCALED_MIN_BOUND = Float.MIN_VALUE;
    private static final float UNSCALED_MAX_BOUND = Float.MAX_VALUE;

    private final double scaledMinBound;
    private final double scaledMaxBound;
    private boolean highlighted = false;

    public YAxis(double _start, double _end, YAxisScale _scale) {
        start = _start;
        end = _end;
        scale = _scale;
        scaledMinBound = scale(UNSCALED_MIN_BOUND);
        scaledMaxBound = scale(UNSCALED_MAX_BOUND);
    }

    public double start() {
        return start;
    }

    public double end() {
        return end;
    }

    public void reset(double _start, double _end) {
        start = _start;
        end = _end;
    }

    public void setHighlighted(boolean _highlighted) {
        highlighted = _highlighted;
    }

    public boolean isHighlighted() {
        return highlighted;
    }

    public double pixel2ScaledValue(int y0, int h, int p) {
        double smin = scale(start);
        double smax = scale(end);
        return (smax - smin) * (-p + y0 + h) / h + smin;
    }

    public int scaledvalue2pixel(int y0, int h, double value) {
        double smin = scale(start);
        double smax = scale(end);
        return (int) (-h * (value - smin) / (smax - smin) + y0 + h);
    }

    public int value2pixel(int y0, int h, double value) {
        return scaledvalue2pixel(y0, h, scale(value));
    }

    public String getLabel() {
        return scale.getLabel();
    }

    public void shiftDownPixels(double distanceY, int height) {
        double scaledMin = scale(start);
        double scaledMax = scale(end);

        double ratioValue = (scaledMax - scaledMin) / height;
        double shift = distanceY * ratioValue;
        double startValue = scaledMin + shift;
        double endValue = scaledMax + shift;
        if (startValue < scaledMinBound) {
            double oldStart = startValue;
            startValue = scaledMinBound;
            endValue = startValue + (endValue - oldStart);
        } else if (endValue > scaledMaxBound) {
            double oldEnd = endValue;
            endValue = scaledMaxBound;
            startValue = endValue - (oldEnd - startValue);
        }
        start = invScale(startValue);
        end = invScale(endValue);
    }

    public void zoomSelectedRange(double scrollValue, double relativeY, double height) {
        double scaledMin = scale(start);
        double scaledMax = scale(end);
        double scaled = scaledMin + (scaledMax - scaledMin) * (relativeY / height);
        double delta = scrollValue * ZOOMSTEP_PERCENTAGE;

        double newScaledMin = (1 + delta) * scaledMin - delta * scaled;
        newScaledMin = Math.max(scaledMinBound, newScaledMin);

        double newScaledMax = (1 + delta) * scaledMax - delta * scaled;
        newScaledMax = Math.min(scaledMaxBound, newScaledMax);

        start = invScale(newScaledMin);
        end = invScale(newScaledMax);
    }

    public float clip(float val) {
        return scale.clip(val);
    }

    public double scale(double val) {
        return scale.scale(val);
    }

    private double invScale(double val) {
        return scale.invScale(val);
    }

    public interface YAxisScale {

        float clip(float val);

        double scale(double val);

        double invScale(double val);

        String getLabel();

    }

    private static String fixupUnit(String unit) {
        return unit.replace("^2", "\u00B2").replace("^3", "\u00B3");
    }

    public static class YAxisLogScale implements YAxisScale {

        private final String label;

        public YAxisLogScale(String _label) {
            label = "log(" + fixupUnit(_label) + ')';
        }

        @Override
        public float clip(float val) {
            return MathUtils.clip(val, DISCARD_LEVEL_LOG_LOW, DISCARD_LEVEL_HIGH);
        }

        @Override
        public double scale(double val) {
            return Math.log10(val);
        }

        @Override
        public double invScale(double val) {
            return Math.pow(10, val);
        }

        @Override
        public String getLabel() {
            return label;
        }
    }

    public static class YAxisIdentityScale implements YAxisScale {

        private final String label;

        public YAxisIdentityScale(String _label) {
            label = fixupUnit(_label);
        }

        @Override
        public float clip(float val) {
            return MathUtils.clip(val, DISCARD_LEVEL_LINEAR_LOW, DISCARD_LEVEL_HIGH);
        }

        @Override
        public double scale(double val) {
            return val;
        }

        @Override
        public double invScale(double val) {
            return val;
        }

        @Override
        public String getLabel() {
            return label;
        }

    }

    public static class YAxisPositiveIdentityScale implements YAxisScale {

        private final String label;

        public YAxisPositiveIdentityScale(String _label) {
            label = fixupUnit(_label);
        }

        @Override
        public float clip(float val) {
            return MathUtils.clip(val, 0, DISCARD_LEVEL_HIGH);
        }

        @Override
        public double scale(double val) {
            return val;
        }

        @Override
        public double invScale(double val) {
            return val;
        }

        @Override
        public String getLabel() {
            return label;
        }

    }

}

@@ -530,17 +530,18 @@
 D = 
+-
 Float.M
-IN
+AX
 _VAL
@@ -895,32 +895,37 @@
 inBound = scale(
+clip(
 UNSCALED_MIN_BOU
@@ -923,24 +923,25 @@
 D_MIN_BOUND)
+)
 ;%0A        sc
@@ -961,16 +961,21 @@
 = scale(
+clip(
 UNSCALED
@@ -985,16 +985,17 @@
 X_BOUND)
+)
 ;%0A    %7D%0A

Eliminate one Float.MIN_VALUE