#ifndef MATH_OPERATORS_H
#define MATH_OPERATORS_H

#include <cmath>
#include "compare.h"

// clang-format off

namespace moper
{
  // unary operators

  constexpr auto abs   = [](double x) noexcept { return std::abs(x); };
  constexpr auto INT   = [](auto   x) noexcept { return (int)(x); };
  constexpr auto nint  = [](double x) noexcept { return std::round(x); };
  constexpr auto sqr   = [](double x) noexcept { return x * x; };
  constexpr auto nop   = [](auto   x) noexcept { return x; };
  constexpr auto reci  = [](double x) noexcept { return 1.0 / x; };
  constexpr auto NOT   = [](auto   x) noexcept { return is_equal(x, 0); };
  constexpr auto exp   = [](double x) noexcept { return std::exp(x); };
  constexpr auto log   = [](double x) noexcept { return std::log(x); };
  constexpr auto log10 = [](double x) noexcept { return std::log10(x); };
  constexpr auto sin   = [](double x) noexcept { return std::sin(x); };
  constexpr auto cos   = [](double x) noexcept { return std::cos(x); };
  constexpr auto tan   = [](double x) noexcept { return std::tan(x); };
  constexpr auto asin  = [](double x) noexcept { return std::asin(x); };
  constexpr auto acos  = [](double x) noexcept { return std::acos(x); };
  constexpr auto atan  = [](double x) noexcept { return std::atan(x); };

  // binary operators

  // relational operators
  constexpr auto LT  = [](auto x, auto y) noexcept { return x < y; };
  constexpr auto GT  = [](auto x, auto y) noexcept { return x > y; };
  constexpr auto LE  = [](auto x, auto y) noexcept { return x <= y; };
  constexpr auto GE  = [](auto x, auto y) noexcept { return x >= y; };
  constexpr auto NE  = [](auto x, auto y) noexcept { return is_not_equal(x, y); };
  constexpr auto EQ  = [](auto x, auto y) noexcept { return is_equal(x, y); };
  constexpr auto LEG = [](auto x, auto y) noexcept { return (x < y) ? -1.0 : (x > y); };

  // logical operators
  constexpr auto AND = [](auto x, auto y) noexcept { return is_not_equal(x, 0) && is_not_equal(y, 0); };
  constexpr auto OR  = [](auto x, auto y) noexcept { return is_not_equal(x, 0) || is_not_equal(y, 0); };

  // arithmetic operators
  constexpr auto add = [](double x, double y) noexcept { return x + y; };
  constexpr auto sub = [](double x, double y) noexcept { return x - y; };
  constexpr auto mul = [](double x, double y) noexcept { return x * y; };
  constexpr auto div = [](double x, double y) noexcept { return x / y; };
  constexpr auto pow = [](double x, double y) noexcept { return std::pow(x, y); };
  constexpr auto sqrt = [](double x) noexcept { return std::sqrt(x); };

  // arithmetic operators with missing values
  constexpr auto addm = [](auto x, auto y, auto mvx, auto mvy, auto is_EQ) { return (is_EQ(x, mvx) || is_EQ(y, mvy)) ? mvx : add(x, y); };
  constexpr auto subm = [](auto x, auto y, auto mvx, auto mvy, auto is_EQ) { return (is_EQ(x, mvx) || is_EQ(y, mvy)) ? mvx : sub(x, y); };
  constexpr auto mulm = [](auto x, auto y, auto mvx, auto mvy, auto is_EQ) { return (is_EQ(x, 0) || is_EQ(y, 0)) ? 0 : (is_EQ(x, mvx) || is_EQ(y, mvy)) ? mvx : mul(x, y); };
  constexpr auto divm = [](auto x, auto y, auto mvx, auto mvy, auto is_EQ) { return (is_EQ(x, mvx) || is_EQ(y, mvy) || is_EQ(y, 0)) ? mvx : div(x, y); };

  constexpr auto powm = [](auto x, auto y, auto mvx, auto mvy, auto is_EQ) { return (is_EQ(x, mvx) || is_EQ(y, mvy)) ? mvx : std::pow(x, y); };

  constexpr auto sqrtm = [](auto x, auto mvx, auto is_EQ) { return (is_EQ(x, mvx) || x < 0) ? mvx : std::sqrt(x); };

  constexpr auto divmx = [](auto x, auto y, auto mvx, auto is_EQ) { return (is_EQ(x, mvx) || is_EQ(y, 0)) ? mvx : x / y; };
  constexpr auto divx = [](auto x, auto y, auto mvx, auto is_EQ) { return is_EQ(y, 0) ? mvx : x / y; };
}

constexpr auto ADD = moper::add;
constexpr auto SUB = moper::sub;
constexpr auto MUL = moper::mul;
constexpr auto DIV = moper::div;
constexpr auto POW = moper::pow;
constexpr auto SQRT = moper::sqrt;

#define ADDM(x, y)  moper::addm(x, y, missval1, missval2, is_EQ)
#define SUBM(x, y)  moper::subm(x, y, missval1, missval2, is_EQ)
#define MULM(x, y)  moper::mulm(x, y, missval1, missval2, is_EQ)
#define DIVM(x, y)  moper::divm(x, y, missval1, missval2, is_EQ)
#define POWM(x, y)  moper::powm(x, y, missval1, missval2, is_EQ)
#define SQRTM(x)    moper::sqrtm(x, missval1, is_EQ)
#define DIVMX(x, y) moper::divmx(x, y, missval1, is_EQ)
#define DIVX(x, y)  moper::divx(x, y, missval1, is_EQ)

// clang-format on

#endif
