#ifndef NUMERIC_TICK_SET_H
#define NUMERIC_TICK_SET_H

#include "tickset.h"

#include <vector>
#include <cmath>
#include <sstream>

class NumericTickSet final : public TickSet {
 public:
  NumericTickSet(double min, double max, unsigned size_request, bool is_integer)
      : min_(min),
        max_(max),
        size_request_(size_request),
        is_integer_(is_integer) {
    set(size_request);
  }

  std::unique_ptr<TickSet> Clone() const override {
    return std::make_unique<NumericTickSet>(*this);
  }

  size_t Size() const override { return ticks_.size(); }

  Tick GetTick(size_t i) const override {
    std::stringstream tickStr;
    tickStr << ticks_[i];
    if (max_ - min_ == 0.0)
      return Tick(0.5, tickStr.str());
    else
      return Tick((ticks_[i] - min_) / (max_ - min_), tickStr.str());
  }

  void Reset() override {
    ticks_.clear();
    set(size_request_);
  }

  void Set(size_t maxSize) override {
    ticks_.clear();
    set(maxSize);
  }
  double UnitToAxis(double unitValue) const override {
    return (min_ == max_) ? 0.0 : (unitValue - min_) / (max_ - min_);
  }
  double AxisToUnit(double axisValue) const override {
    return axisValue * (max_ - min_) + min_;
  }

 private:
  friend std::unique_ptr<NumericTickSet> std::make_unique<NumericTickSet>(
      const NumericTickSet&);

  void set(size_t size_request) {
    if (std::isfinite(min_) && std::isfinite(max_)) {
      if (max_ == min_) {
        ticks_.push_back(min_);
      } else {
        if (size_request == 0) return;
        double tickWidth =
            RoundUpToNiceNumber(std::fabs(max_ - min_) / (double)size_request);
        if (is_integer_ && tickWidth < 1.0) tickWidth = 1.0;
        if (tickWidth == 0.0) tickWidth = 1.0;
        if (min_ < max_) {
          double pos = RoundUpToNiceNumber(min_, tickWidth);
          while (pos <= max_) {
            if (fabs(pos) < tickWidth / 100.0)
              ticks_.push_back(0.0);
            else
              ticks_.push_back(pos);
            pos += tickWidth;
          }
        } else {
          double pos = -RoundUpToNiceNumber(-min_, tickWidth);
          while (pos >= max_) {
            if (std::fabs(pos) < tickWidth / 100.0)
              ticks_.push_back(0.0);
            else
              ticks_.push_back(pos);
            pos -= tickWidth;
          }
        }
        while (ticks_.size() > size_request) ticks_.pop_back();
      }
    }
  }

  double RoundUpToNiceNumber(double number) {
    if (!std::isfinite(number)) return number;
    double roundedNumber = 1.0;
    if (number <= 0.0) {
      if (number == 0.0) {
        return 0.0;
      } else {
        roundedNumber = -1.0;
        number *= -1.0;
      }
    }
    while (number > 10) {
      number /= 10;
      roundedNumber *= 10;
    }
    while (number <= 1) {
      number *= 10;
      roundedNumber /= 10;
    }
    if (number <= 2)
      return roundedNumber * 2;
    else if (number <= 5)
      return roundedNumber * 5;
    else
      return roundedNumber * 10;
  }
  double RoundUpToNiceNumber(double number, double roundUnit) {
    return roundUnit * ceil(number / roundUnit);
  }

  double min_;
  double max_;
  size_t size_request_;
  bool is_integer_;
  std::vector<double> ticks_;
};

#endif
