package io.github.jjsimas.chart2d;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

final class NiceScale {

  private NiceScale() {
  }

  static TickSet ticks(double min, double max, int hint) {
    if (!Double.isFinite(min) || !Double.isFinite(max)) {
      min = 0.0;
      max = 1.0;
    }
    if (min == max) {
      double padding = min == 0.0 ? 1.0 : Math.abs(min * 0.1);
      min -= padding;
      max += padding;
    }
    double rawRange = niceNumber(max - min, false);
    double step = niceNumber(rawRange / Math.max(2, hint - 1), true);
    double niceMin = Math.floor(min / step) * step;
    double niceMax = Math.ceil(max / step) * step;

    List<Double> values = new ArrayList<>();
    for (double value = niceMin; value <= niceMax + (step / 2.0); value += step) {
      values.add(normalizeZero(value));
    }
    return new TickSet(values, niceMin, niceMax, step);
  }

  static String format(double value, double step) {
    int decimals = 0;
    if (step < 1.0) {
      decimals = (int) Math.ceil(-Math.log10(step));
      decimals = Math.max(0, Math.min(6, decimals));
    }
    StringBuilder pattern = new StringBuilder("0");
    if (decimals > 0) {
      pattern.append('.');
      for (int i = 0; i < decimals; i++) {
        pattern.append('0');
      }
    }
    DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(Locale.US);
    format.applyPattern(pattern.toString());
    return format.format(normalizeZero(value));
  }

  private static double niceNumber(double value, boolean round) {
    double exponent = Math.floor(Math.log10(value));
    double fraction = value / Math.pow(10.0, exponent);
    double niceFraction;
    if (round) {
      if (fraction < 1.5) {
        niceFraction = 1.0;
      }
      else if (fraction < 3.0) {
        niceFraction = 2.0;
      }
      else if (fraction < 7.0) {
        niceFraction = 5.0;
      }
      else {
        niceFraction = 10.0;
      }
    }
    else if (fraction <= 1.0) {
      niceFraction = 1.0;
    }
    else if (fraction <= 2.0) {
      niceFraction = 2.0;
    }
    else if (fraction <= 5.0) {
      niceFraction = 5.0;
    }
    else {
      niceFraction = 10.0;
    }
    return niceFraction * Math.pow(10.0, exponent);
  }

  private static double normalizeZero(double value) {
    return Math.abs(value) < 1.0e-10 ? 0.0 : value;
  }

  record TickSet(List<Double> values, double min, double max, double step) {
  }
}
