#include "interactive_curve_view_range.h"
#include <ion.h>
#include <cmath>
#include <float.h>
#include <stddef.h>
#include <assert.h>
#include <poincare/preferences.h>

using namespace Poincare;

namespace Shared {

uint32_t InteractiveCurveViewRange::rangeChecksum() {
  float data[5] = {xMin(), xMax(), yMin(), yMax(), m_yAuto ? 1.0f : 0.0f};
  size_t dataLengthInBytes = 5*sizeof(float);
  assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4
  return Ion::crc32Word((uint32_t *)data, dataLengthInBytes/sizeof(uint32_t));
}

void InteractiveCurveViewRange::setYAuto(bool yAuto) {
  m_yAuto = yAuto;
  notifyRangeChange();
}

void InteractiveCurveViewRange::setXMin(float xMin) {
  MemoizedCurveViewRange::protectedSetXMin(xMin, k_lowerMaxFloat, k_upperMaxFloat);
  notifyRangeChange();
}

void InteractiveCurveViewRange::setXMax(float xMax) {
  MemoizedCurveViewRange::protectedSetXMax(xMax, k_lowerMaxFloat, k_upperMaxFloat);
  notifyRangeChange();
}

void InteractiveCurveViewRange::setYMin(float yMin) {
  MemoizedCurveViewRange::protectedSetYMin(yMin, k_lowerMaxFloat, k_upperMaxFloat);
}

void InteractiveCurveViewRange::setYMax(float yMax) {
  MemoizedCurveViewRange::protectedSetYMax(yMax, k_lowerMaxFloat, k_upperMaxFloat);
}

void InteractiveCurveViewRange::zoom(float ratio, float x, float y) {
  float xMi = xMin();
  float xMa = xMax();
  float yMi = yMin();
  float yMa = yMax();
  if (ratio*std::fabs(xMa-xMi) < Range1D::k_minFloat || ratio*std::fabs(yMa-yMi) < Range1D::k_minFloat) {
    return;
  }
  float centerX = std::isnan(x) || std::isinf(x) ? xCenter() : x;
  float centerY = std::isnan(y) || std::isinf(y) ? yCenter() : y;
  float newXMin = centerX*(1.0f-ratio)+ratio*xMi;
  float newXMax = centerX*(1.0f-ratio)+ratio*xMa;
  m_yAuto = false;
  if (!std::isnan(newXMin) && !std::isnan(newXMax)) {
    m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat);
  }
  float newYMin = centerY*(1.0f-ratio)+ratio*yMi;
  float newYMax = centerY*(1.0f-ratio)+ratio*yMa;
  if (!std::isnan(newYMin) && !std::isnan(newYMax)) {
    m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat);
  }
}

void InteractiveCurveViewRange::panWithVector(float x, float y) {
  m_yAuto = false;
  if (clipped(xMin() + x, false) != xMin() + x || clipped(xMax() + x, true) != xMax() + x || clipped(yMin() + y, false) != yMin() + y || clipped(yMax() + y, true) != yMax() + y || std::isnan(clipped(xMin() + x, false)) || std::isnan(clipped(xMax() + x, true)) || std::isnan(clipped(yMin() + y, false)) || std::isnan(clipped(yMax() + y, true))) {
    return;
  }
  m_xRange.setMax(xMax()+x, k_lowerMaxFloat, k_upperMaxFloat);
  MemoizedCurveViewRange::protectedSetXMin(xMin() + x, k_lowerMaxFloat, k_upperMaxFloat);
  m_yRange.setMax(yMax()+y, k_lowerMaxFloat, k_upperMaxFloat);
  MemoizedCurveViewRange::protectedSetYMin(yMin() + y, k_lowerMaxFloat, k_upperMaxFloat);
}

void InteractiveCurveViewRange::roundAbscissa() {
  // Set x range
  float newXMin = std::round(xCenter()) - (float)Ion::Display::Width/2.0f;
  float newXMax = std::round(xCenter()) + (float)Ion::Display::Width/2.0f-1.0f;
  if (std::isnan(newXMin) || std::isnan(newXMax)) {
    return;
  }
  m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat);
  setXMin(newXMin);
}

void InteractiveCurveViewRange::normalize() {
  /* We center the ranges on the current range center, and put each axis so that
   * 1cm = 2 units. */
  m_yAuto = false;
  // Set x range
  float newXMin = xCenter() - NormalizedXHalfRange();
  float newXMax = xCenter() + NormalizedXHalfRange();
  if (!std::isnan(newXMin) && !std::isnan(newXMax)) {
    m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat);
  }
  // Set y range
  float newYMin = yCenter() - NormalizedYHalfRange();
  float newYMax = yCenter() + NormalizedYHalfRange();
  if (!std::isnan(newYMin) && !std::isnan(newYMax)) {
    m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat);
  }
}

void InteractiveCurveViewRange::setTrigonometric() {
  m_yAuto = false;
  // Set x range
  float x = (Preferences::sharedPreferences()->angleUnit() == Preferences::AngleUnit::Degree) ? 600.0f : 10.5f;
  m_xRange.setMax(x, k_lowerMaxFloat, k_upperMaxFloat);
  MemoizedCurveViewRange::protectedSetXMin(-x, k_lowerMaxFloat, k_upperMaxFloat);
  // Set y range
  float y = 1.6f;
  m_yRange.setMax(y, k_lowerMaxFloat, k_upperMaxFloat);
  MemoizedCurveViewRange::protectedSetYMin(-y, k_lowerMaxFloat, k_upperMaxFloat);
}

void InteractiveCurveViewRange::setDefault() {
  if (m_delegate == nullptr) {
    return;
  }
  if (!m_delegate->defautRangeIsNormalized()) {
    m_xRange.setMax(m_delegate->interestingXHalfRange(), k_lowerMaxFloat, k_upperMaxFloat);
    setXMin(-xMax());
    m_yAuto = true;
    return;
  }

  m_yAuto = false;
  // Compute the interesting ranges
  float a,b,c,d;
  m_delegate->interestingRanges(&a, &b, &c, &d);
  m_xRange.setMin(a, k_lowerMaxFloat, k_upperMaxFloat);
  m_xRange.setMax(b, k_lowerMaxFloat, k_upperMaxFloat);
  m_yRange.setMin(c, k_lowerMaxFloat, k_upperMaxFloat);
  m_yRange.setMax(d, k_lowerMaxFloat, k_upperMaxFloat);

  // Add margins
  float xRange = xMax() - xMin();
  float yRange = yMax() - yMin();
  m_xRange.setMin(m_delegate->addMargin(xMin(), xRange, false, true), k_lowerMaxFloat, k_upperMaxFloat);
  // Use MemoizedCurveViewRange::protectedSetXMax to update xGridUnit
  MemoizedCurveViewRange::protectedSetXMax(m_delegate->addMargin(xMax(), xRange, false, false), k_lowerMaxFloat, k_upperMaxFloat);
  m_yRange.setMin(m_delegate->addMargin(yMin(), yRange, true, true), k_lowerMaxFloat, k_upperMaxFloat);
  MemoizedCurveViewRange::protectedSetYMax(m_delegate->addMargin(yMax(), yRange, true, false), k_lowerMaxFloat, k_upperMaxFloat);

  // Normalize the axes, so that a polar circle is displayed as a circle
  xRange = xMax() - xMin();
  yRange = yMax() - yMin();
  float xyRatio = xRange/yRange;
  float normalizedXYRatio = NormalizedXHalfRange()/NormalizedYHalfRange();
  if (xyRatio < normalizedXYRatio) {
    float newXRange = normalizedXYRatio * yRange;
    assert(newXRange > xRange);
    float delta = (newXRange - xRange) / 2.0f;
    m_xRange.setMin(xMin() - delta, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetXMax(xMax()+delta, k_lowerMaxFloat, k_upperMaxFloat);
  } else if (xyRatio > normalizedXYRatio) {
    float newYRange = NormalizedYHalfRange()/NormalizedXHalfRange() * xRange;
    assert(newYRange > yRange);
    float delta = (newYRange - yRange) / 2.0f;
    m_yRange.setMin(yMin() - delta, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetYMax(yMax()+delta, k_lowerMaxFloat, k_upperMaxFloat);
  }
}

void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) {
  if (std::isnan(position)) {
    return;
  }
  if (axis == Axis::X) {
    float range = xMax() - xMin();
    if (std::fabs(position/range) > k_maxRatioPositionRange) {
      range = Range1D::defaultRangeLengthFor(position);
    }
    m_xRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetXMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat);
  } else {
    m_yAuto = false;
    float range = yMax() - yMin();
    if (std::fabs(position/range) > k_maxRatioPositionRange) {
      range = Range1D::defaultRangeLengthFor(position);
    }
    m_yRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetYMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat);
  }
}

void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation) {
  float xRange = xMax() - xMin();
  float yRange = yMax() - yMin();
  if (x < xMin() + leftMarginRation*xRange - FLT_EPSILON && !std::isinf(x) && !std::isnan(x)) {
    m_yAuto = false;
    float newXMin = x - leftMarginRation*xRange;
    m_xRange.setMax(newXMin + xRange, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat);
  }
  if (x > xMax() - rightMarginRatio*xRange + FLT_EPSILON && !std::isinf(x) && !std::isnan(x)) {
    m_yAuto = false;
    m_xRange.setMax(x + rightMarginRatio*xRange, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetXMin(xMax() - xRange, k_lowerMaxFloat, k_upperMaxFloat);
  }
  if (y < yMin() + bottomMarginRation*yRange - FLT_EPSILON && !std::isinf(y) && !std::isnan(y)) {
    m_yAuto = false;
    float newYMin = y - bottomMarginRation*yRange;
    m_yRange.setMax(newYMin + yRange, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat);
  }
  if (y > yMax() - topMarginRatio*yRange + FLT_EPSILON && !std::isinf(y) && !std::isnan(y)) {
    m_yAuto = false;
    m_yRange.setMax(y + topMarginRatio*yRange, k_lowerMaxFloat, k_upperMaxFloat);
    MemoizedCurveViewRange::protectedSetYMin(yMax() - yRange, k_lowerMaxFloat, k_upperMaxFloat);
  }
}

void InteractiveCurveViewRange::notifyRangeChange() {
  if (m_delegate) {
    m_delegate->didChangeRange(this);
  }
}

}
