#include <math.h>

static bool is_number(uint64_t bits) {
  return is_smallint(bits) || is_largeint(bits) || is_fraction(bits) || is_real(bits);
}

static uint64_t number_add__smallint__smallint(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  int64_t l = unpack_smallint(lhs);
  int64_t r = unpack_smallint(rhs);
  int64_t c = l + r;
  return pack_smallint(c);
}

static uint64_t number_add__smallint(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  assert(is_number(rhs));
  switch(get_tag(rhs)) {
  case Tag_SmallInt:
    return number_add__smallint__smallint(a, lhs, rhs);
  default:
    return pack_undefined();
  }
}

static uint64_t number_add(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  assert(is_number(lhs));
  assert(is_number(rhs));
  switch(get_tag(lhs)) {
  case Tag_SmallInt:
    return number_add__smallint(a, lhs, rhs);
  default:
    return pack_undefined();
  }
}

static uint64_t number_multiply(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs);

static uint64_t number_multiply__smallint__smallint(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  return pack_smallint(unpack_smallint(lhs) * unpack_smallint(rhs));
}

static uint64_t number_multiply__smallint__fraction(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  uint64_t n = get_car(a, rhs);
  uint64_t d = get_cdr(a, rhs);
  return solver_Algebra_fraction(a, number_multiply(a, lhs, n), d);
}

static uint64_t number_multiply__smallint(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  assert(is_number(rhs));
  switch(get_tag(rhs)) {
  case Tag_SmallInt:
    return number_multiply__smallint__smallint(a, lhs, rhs);
  case Tag_Fraction:
    return number_multiply__smallint__fraction(a, lhs, rhs);
  default:
    return pack_undefined();
  }
}

static uint64_t number_multiply(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  assert(is_number(lhs));
  assert(is_number(rhs));
  switch(get_tag(lhs)) {
  case Tag_SmallInt:
    return number_multiply__smallint(a, lhs, rhs);
  default:
    return pack_undefined();
  }
}

static uint64_t number_power__smallint__smallint(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  int64_t base = unpack_smallint(lhs);
  int64_t exp = unpack_smallint(rhs);
  bool n = exp < 0;
  if(exp) {
    exp = -exp;
  }
  int32_t result = 1;
  for(;;) {
    if(exp & 1) {
      result *= base;
    }
    exp >>= 1;
    if(exp == 0) {
      break;
    }
    base *= base;
  }
  if(n) {
    return solver_Algebra_fraction(a, LITERAL_ONE, pack_smallint(result));
  } else {
    return pack_smallint(result);
  }
}

static uint64_t number_power__smallint(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  assert(is_number(rhs));
  switch(get_tag(rhs)) {
  case Tag_SmallInt:
    return number_power__smallint__smallint(a, lhs, rhs);
  default:
    return pack_undefined();
  }
}

static uint64_t number_power(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  assert(is_number(lhs));
  assert(is_number(rhs));
  switch(get_tag(lhs)) {
  case Tag_SmallInt:
    return number_power__smallint(a, lhs, rhs);
  default:
    return pack_undefined();
  }
}

static int number_compare__smallint__smallint(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  return unpack_smallint(rhs) - unpack_smallint(lhs);
}

static int number_compare__smallint(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  assert(is_number(rhs));
  switch(get_tag(rhs)) {
  case Tag_SmallInt:
    return number_compare__smallint__smallint(a, lhs, rhs);
  default:
    return -1;
  }
}

static int number_compare(struct solver_Algebra *a, uint64_t lhs, uint64_t rhs) {
  assert(is_number(lhs));
  assert(is_number(rhs));
  switch(get_tag(lhs)) {
  case Tag_SmallInt:
    return number_compare__smallint(a, lhs, rhs);
  default:
    return 0;
  }
}


uint64_t solver_Algebra_real(struct solver_Algebra *a, double value) {
  if(value == floor(value)) {
    return solver_Algebra_integer(a, (int64_t)value);
  }
  return pack_real(value);
}