/**
 * Copyright 2004-present Facebook. All Rights Reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree.
 */

#pragma once

#define _USE_MATH_DEFINES
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>

namespace fb360_dep {
namespace math_util {

static inline float randf0to1() {
  return float(rand()) / float(RAND_MAX);
}

static inline float toRadians(const float deg) {
  return deg * static_cast<float>(M_PI) / 180.0f;
}

template <typename T>
inline T square(const T x) {
  return x * x;
}

template <typename T>
inline T clamp(const T& x, const T& a, const T& b) {
  return x < a ? a : x > b ? b : x;
}

template <typename T>
inline T reflect(const T x, const T r) {
  return x < T(0) ? -x : x >= r ? 2 * r - x - 1 : x;
}

template <typename T>
inline T wrap(const T x, const T r) {
  return x < T(0) ? r + x : x >= r ? x - r : x;
}

template <typename V, typename T>
inline V lerp(const V x0, const V x1, const T alpha) {
  return x0 * (T(1) - alpha) + x1 * alpha;
}

template <typename T>
inline T lerp(const T x0, const T x1, const T alpha) {
  return x0 * (T(1) - alpha) + x1 * alpha;
}

const static double LOG_E = log(M_E);
template <typename T>
class GaussianApproximation {
 protected:
  const T xMin, xMax, yMin, yMax;
  const T xRangeRecip, yRange;
  const T sigma, scale;
  const T a0, a2, a3; // a1 == 0
  const T b0, b1, b2, b3;

 public:
  GaussianApproximation(const T xMin_, const T xMax_, const T yMin_, const T yMax_)
      : xMin(xMin_),
        xMax(xMax_),
        yMin(yMin_),
        yMax(yMax_),
        xRangeRecip(2 / (xMax_ - xMin_)),
        yRange(yMax_ - yMin_),
        sigma(sqrt(2.0) * 0.21), // 0.21 instead of 0.2 to keep b(x) > 0
        scale(1 / (2 * sigma * sigma)),
        a0(1),
        a2(-(-2 * LOG_E * scale + 12 * exp(scale / 4) - 12) / exp(scale / 4)),
        a3((-4 * LOG_E * scale + 16 * exp(scale / 4) - 16) / exp(scale / 4)),
        b0((2 * LOG_E * scale - 4) / exp(scale / 4)),
        b1(-(8 * LOG_E * scale - 24) / exp(scale / 4)),
        b2((10 * LOG_E * scale - 36) / exp(scale / 4)),
        b3(-(4 * LOG_E * scale - 16) / exp(scale / 4)) {}

  T inline operator()(const T x) const {
    const T xr = (x - xMin) * xRangeRecip - T(1);
    const T xp = xr > T(0) ? xr : -xr;
    const T yp = // Horner's rule
        (xp < T(1.0))
        ? ((xp < T(0.5)) ? a0 + xp * (xp * (a2 + xp * a3)) : b0 + xp * (b1 + xp * (b2 + xp * b3)))
        : T(0.0);
    return yp * yRange + yMin;
  }
};

template <typename T, typename V>
class BezierCurve {
 protected:
  std::vector<V> points_;

 public:
  BezierCurve() {}

  BezierCurve(std::vector<V> points) {
    for (auto p : points) {
      points_.push_back(p);
    }
  }

  void addPoint(const V p) {
    points_.push_back(p);
  }

  void clearPoints() {
    points_.clear();
  }

  inline V operator()(const int i, const int j, const T t) const {
    return (i == j) ? points_[i] : lerp((*this)(i, j - 1, t), (*this)(i + 1, j, t), t);
  }

  inline V operator()(const T t) const {
    return (*this)(0, points_.size() - 1, t);
  }
};

// Returns the (row, col)-tuple corresponding to the kth element in a (numRows x numCols) matrix
static inline std::tuple<int, int>
linearToMatrixIndex(int k, int numRows, int numCols, bool isRowMajor = true) {
  return isRowMajor ? std::make_tuple(k / numCols, k % numCols)
                    : std::make_tuple(k % numRows, k / numRows);
}

// Returns the linear index corresponding to the (row, col)-tuple rowCik in a (numRows x numCols)
// matrix
static inline int
matrixToLinearIndex(std::tuple<int, int> rowCol, int numRows, int numCols, bool isRowMajor = true) {
  return isRowMajor ? std::get<0>(rowCol) * numCols + std::get<1>(rowCol)
                    : std::get<0>(rowCol) + numRows * std::get<1>(rowCol);
}

// Descending sort function for pairs (sort by first element)
template <class T, class U>
bool sortdescPair(const std::pair<T, U>& a, const std::pair<T, U>& b) {
  return (std::get<0>(a) > std::get<0>(b));
}

} // end namespace math_util
} // end namespace fb360_dep
