#include <bit>
#include <iostream>
#include <iterator>
#include <limits>
#include <string>
#include <vector>

#define STANDARD_ONLY

template <typename T>
using nl = std::numeric_limits<T>;

struct TestCase {
  std::string name;
  std::vector<bool> golden;
  virtual bool DoTest(float in) = 0;
};

struct isZero : public TestCase {
  isZero() {
    name = "isZero";
    golden = {0,0,1,1, 0,0,0,0, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile("fmv.w.x f1, x0 \n\t\
                  feq.s %0, f1, %1"
                  : "=r"(result) : "f"(in) : "f1");
    return result;
  };
};

struct isPositiveZero : public TestCase {
  isPositiveZero() {
    name = "isPositiveZero";
    golden = {0,0,1,0, 0,0,0,0, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile("fmv.x.w %0, %1": "=r"(result) : "f"(in));
    return !result;
  };
};

struct isNegativeZero : public TestCase {
  isNegativeZero() {
    name = "isNegativeZero";
    golden = {0,0,0,1, 0,0,0,0, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile("fneg.s %1, %1 \n\t\
                 fmv.x.w %0, %1"
                 : "=r"(result) : "f"(in));
    return !result;
  };
};

struct isNan : public TestCase {
  isNan() {
    name = "isNan";
    golden = {0,0,0,0, 1,1,0,0, 0,0,1,1};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile("feq.s %0, %1, %1" : "=r"(result) : "f"(in));
    return !result;
  };
};

struct isQnan : public TestCase {
  isQnan() {
    name = "isQnan";
    golden = {0,0,0,0, 0,1,0,0, 0,0,1,1};
  }

  virtual bool DoTest(float in) {
    int result0, result1;
    asm volatile("fmv.x.w %0, %2 \n\t\
                  lui %1, 0x7fc00 \n\t\
                  and %0, %0, %1"
                  : "=r"(result0), "=r"(result1) : "f"(in) : "t0", "t1");
    //return !result;
    return result0 == result1;
  };
};

struct isSnan : public TestCase {
  isSnan() {
    name = "isSnan";
    golden = {0,0,0,0, 1,0,0,0, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    #ifdef STANDARD_ONLY
    asm volatile("feq.s t0, %1, %1 \n\t\
                  fmv.x.w t1, %1 \n\t\
                  lui t2, 0x00400 \n\t\
                  and t2, t1, t2 \n\t\
                  or  %0, t0, t2"
                  : "=r"(result) : "f"(in) : "t0", "t1", "t2");
    #else
    asm volatile("feq.s t0, %1, %1 \n\t\
                  fmv.x.w t1, %1 \n\t\
                  bexti t1, t1, 22 \n\t\
                  or %0, t0, t1" : "=r"(result) : "f"(in) : "t1", "t2");
    #endif
    return !result;
  };
};

struct isInfinity : public TestCase {
  isInfinity() {
    name = "isInfinity";
    golden = {0,0,0,0, 0,0,1,1, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile("lui     t0, 0x7f800 \n\t\
                  fmv.w.x f1, t0 \n\t\
                  fabs.s  f2, %1 \n\t\
                  feq.s   %0, f1, f2" : "=r"(result) : "f"(in) : "f1", "f2", "t0");
    return result;
  };
};

struct isPositiveInfinity : public TestCase {
  isPositiveInfinity() {
    name = "isPositiveInfinity";
    golden = {0,0,0,0, 0,0,1,0, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile("lui     t0, 0x7f800 \n\t\
                  fmv.w.x f1, t0 \n\t\
                  feq.s   %0, f1, %1"
                  : "=r"(result) : "f"(in) : "f1", "t0");
    return result;
  };
};

struct isNegativeInfinity : public TestCase {
  isNegativeInfinity() {
    name = "isNegativeInfinity";
    golden = {0,0,0,0, 0,0,0,1, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile("lui     t0, 0xff800 \n\t\
                  fmv.w.x f1, t0 \n\t\
                  feq.s   %0, f1, %1"
                  : "=r"(result) : "f"(in) : "f1", "t0");
    return result;
  };
};

struct isNormal : public TestCase {
  isNormal() {
    name = "isNormal";
    golden = {1,1,0,0, 0,0,0,0, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile goto ("fmv.x.w t0, %1 \n\t\
                        lui     t1, 0x7f800 \n\t\
                        and     t2, t1, t0 \n\t\
                        beqz    t2, %l[notnorm] \n\t\
                        beq     t2, t1, %l[notnorm] \n\t\
                        lui     %0, 1"
                        : "=r"(result) : "f"(in) : "t0", "t1", "t2" : notnorm);
    return result;
    notnorm:
    return 0;
  };
};

struct isPositiveNormal : public TestCase {
  isPositiveNormal() {
    name = "isPositiveNormal";
    golden = {1,0,0,0, 0,0,0,0, 0,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile goto ("fmv.x.w t0, %1 \n\t\
                        bltz    t0, %l[notnorm] \n\t\
                        lui     t1, 0x7f800 \n\t\
                        and     t0, t1, t0 \n\t\
                        beqz    t0, %l[notnorm] \n\t\
                        beq     t0, t1, %l[notnorm] \n\t\
                        lui     %0, 1"
                        : "=r"(result) : "f"(in) : "t0", "t1": notnorm);
    return result;
    notnorm:
    return 0;
  };
};

struct isNegativeNormal : public TestCase {
  isNegativeNormal() {
    name = "isNegativeNormal";
    golden = {0,1,0,0, 0,0,0,0, 0,0,0,0};
  }
  virtual bool DoTest(float in) {
    int result;
    asm volatile goto ("fmv.x.w t0, %1 \n\t\
                        bgtz    t0, %l[notnorm] \n\t\
                        lui     t1, 0x7f800 \n\t\
                        and     t0, t1, t0 \n\t\
                        beqz    t0, %l[notnorm] \n\t\
                        beq     t0, t1, %l[notnorm] \n\t\
                        lui     %0, 1"
                        : "=r"(result) : "f"(in) : "t0", "t1": notnorm);
    return result;
    notnorm:
    return 0;
  };
};

struct isSubnormal : public TestCase {
  isSubnormal() {
    name = "isSubnormal";
    golden = {0,0,0,0, 0,0,0,0, 1,1,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile ("fabs.s f0, %1 \n\t\
                   fmv.w.x f1, zero \n\t\
                   lui t0, 0x00800 \n\t\
                   fmv.w.x f2, t0 \n\t\
                   flt.s t0, f1, f0 \n\t\
                   flt.s t1, f0, f2 \n\t\
                   and %0, t0, t1" : "=r"(result) : "f"(in) : "f0", "f1", "f2", "t0", "t1");

    return result;
  }
};

struct isPositiveSubnormal : public TestCase {
  isPositiveSubnormal() {
    name = "isPositiveSubnormal";
    golden = {0,0,0,0, 0,0,0,0, 1,0,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile ("fmv.w.x f1, zero \n\t\
                   lui t0, 0x00800 \n\t\
                   fmv.w.x f2, t0 \n\t\
                   flt.s t0, f1, %1 \n\t\
                   flt.s t1, %1, f2 \n\t\
                   and %0, t0, t1" : "=r"(result) : "f"(in) : "t0", "t1");
    return result;
  }
};

struct isNegativeSubnormal : public TestCase {
  isNegativeSubnormal() {
    name = "isNegativeSubnormal";
    golden = {0,0,0,0, 0,0,0,0, 0,1,0,0};
  }

  virtual bool DoTest(float in) {
    int result;
    asm volatile ("fmv.w.x f1, zero \n\t\
                   lui t0, 0x80800 \n\t\
                   fmv.w.x f2, t0 \n\t\
                   flt.s t0, %1, f1 \n\t\
                   flt.s t1, f2, %1 \n\t\
                   and %0, t0, t1" : "=r"(result) : "f"(in) : "t0", "t1");
    return result;
  }
};

int main() {
  float values[] = {1.f, -1.f,
                   +0.f, -0.f,
                   nl<float>::signaling_NaN(), nl<float>::quiet_NaN(),
                   nl<float>::infinity(), -nl<float>::infinity(),
                   nl<float>::denorm_min(), -nl<float>::denorm_min(),
                   std::bit_cast<float>(0x7fe00000), std::bit_cast<float>(0xffe00000)};

  TestCase* cases[] = {new isZero, new isPositiveZero, new isNegativeZero,
                       new isNan, new isQnan, new isSnan,
                       new isInfinity, new isPositiveInfinity, new isNegativeInfinity,
                       new isNormal, new isPositiveNormal, new isNegativeNormal,
                       new isSubnormal, new isPositiveSubnormal, new isNegativeSubnormal};

  for (auto *c: cases) {
    std::cout << "Testing " << c->name << std::endl;
    for (size_t i = 0; i < std::size(values); ++i ) {
      if (c->DoTest(values[i]) != c->golden[i])
        std::cout << "Error at " << i << ":" << values[i] << std::endl;
    }
  }

  return 0;
}
