#include <inttypes.h>

#define PRECEDENCE_NUMBER 1000
#define PRECEDENCE_SUM     110
#define PRECEDENCE_PRODUCT 120
#define PRECEDENCE_POWER   130

static int precedence(uint64_t bits) {
  switch(get_tag(bits)) {
  case Tag_Real:
  case Tag_Variable:
  case Tag_SmallInt:
  case Tag_LargeInt:
  case Tag_Fraction:
    return PRECEDENCE_NUMBER;
  case Tag_Power:
    return PRECEDENCE_POWER;
  case Tag_Sum:
    return PRECEDENCE_SUM;
  case Tag_Product:
    return PRECEDENCE_PRODUCT;
  default:
    return 0;
  }
}

static void show(struct solver_Algebra *a, uint64_t bits);

static void show_real(struct solver_Algebra *a, uint64_t bits) {
  printf("%f", unpack_real(bits));
}

static void show_variable(struct solver_Algebra *a, uint64_t bits) {
  printf("%s", unpack_variable(bits));
}

static void show_boolean(struct solver_Algebra *a, uint64_t bits) {
  bool v = unpack_boolean(bits);
  printf("%s", v ? "true" : "false");
}

static void show_undefined(struct solver_Algebra *a, uint64_t bits) {
  unpack_undefined(bits);
  printf("undefined");
}

static void show__smallint(struct solver_Algebra *a, uint64_t bits) {
  printf(PRId64, unpack_smallint(bits));
}

static void show__largeint(struct solver_Algebra *a, uint64_t bits) {
  // TODO
}

static void show__fraction(struct solver_Algebra *a, uint64_t bits) {
  uint32_t index = unpack_fraction(bits);
  uint64_t numerator = a->values.data[index + 0];
  uint64_t denominator = a->values.data[index + 1];
  show(a, numerator);
  printf("/");
  show(a, denominator);
}

#define SURROUND(C, STMT) \
  do { \
    if(C) { \
      printf("("); \
    } \
    STMT; \
    if(C) { \
      printf(")"); \
    } \
  } while(0)

static void show__power(struct solver_Algebra *a, uint64_t bits) {
  uint32_t index = unpack_power(bits);
  uint64_t base = a->values.data[index + 0];
  uint64_t exponent = a->values.data[index + 1];
  bool lhp = precedence(base) < PRECEDENCE_POWER;
  bool rhp = precedence(exponent) < PRECEDENCE_POWER;

  SURROUND(lhp, show(a, base));

  printf("^");

  SURROUND(rhp, show(a, exponent));
}

static void show__sum(struct solver_Algebra *a, uint64_t bits) {
  struct EncodedList sum = unpack_sum(bits);
  for(uint32_t i = 0; i < sum.count; i++) {
    uint64_t operand = a->values.data[sum.index + i];
    if(i > 0) {
      printf(" + ");
    }
    bool p = precedence(operand) < PRECEDENCE_SUM;
    SURROUND(p, show(a, operand));
  }
}

static void show__product(struct solver_Algebra *a, uint64_t bits) {
  struct EncodedList product = unpack_product(bits);
  bool scale = product.count == 2 && is_number(a->values.data[product.index + 0]);
  for(uint32_t i = 0; i < product.count; i++) {
    uint64_t operand = a->values.data[product.index + i];
    bool p = precedence(operand) < PRECEDENCE_PRODUCT;
    if(!scale && i > 0) {
      printf(" * ");
    }
    SURROUND(p, show(a, operand));
  }
}

#undef SURROUND

static void show__equals(struct solver_Algebra *a, uint64_t bits) {
  uint32_t index = unpack_equals(bits);
  uint64_t lhs = a->values.data[index + 0];
  uint64_t rhs = a->values.data[index + 1];
  show(a, lhs);
  printf(" == ");
  show(a, rhs);
}

static void show__not_equals(struct solver_Algebra *a, uint64_t bits) {
  uint32_t index = unpack_not_equals(bits);
  uint64_t lhs = a->values.data[index + 0];
  uint64_t rhs = a->values.data[index + 1];
  show(a, lhs);
  printf(" != ");
  show(a, rhs);
}

static void show__less_than(struct solver_Algebra *a, uint64_t bits) {
  uint32_t index = unpack_less_than(bits);
  uint64_t lhs = a->values.data[index + 0];
  uint64_t rhs = a->values.data[index + 1];
  show(a, lhs);
  printf(" < ");
  show(a, rhs);
}

static void show__greater_than(struct solver_Algebra *a, uint64_t bits) {
  uint32_t index = unpack_greater_than(bits);
  uint64_t lhs = a->values.data[index + 0];
  uint64_t rhs = a->values.data[index + 1];
  show(a, lhs);
  printf(" > ");
  show(a, rhs);
}

static void show(struct solver_Algebra *a, uint64_t bits) {
  switch(get_tag(bits)) {
  case Tag_Real:
    show_real(a, bits);
    break;
  case Tag_Variable:
    show_variable(a, bits);
    break;
  case Tag_Boolean:
    show_boolean(a, bits);
    break;
  case Tag_Undefined:
    show_undefined(a, bits);
    break;
  case Tag_SmallInt:
    show__smallint(a, bits);
    break;
  case Tag_LargeInt:
    show__largeint(a, bits);
    break;
  case Tag_Fraction:
    show__fraction(a, bits);
    break;
  case Tag_Power:
    show__power(a, bits);
    break;
  case Tag_Sum:
    show__sum(a, bits);
    break;
  case Tag_Product:
    show__product(a, bits);
    break;
  case Tag_Equals:
    show__equals(a, bits);
    break;
  case Tag_NotEquals:
    show__not_equals(a, bits);
    break;
  case Tag_LessThan:
    show__less_than(a, bits);
    break;
  case Tag_GreaterThan:
    show__greater_than(a, bits);
    break;
  case Tag_Invalid:
    break;
  }
}

void solver_Algebra_show(struct solver_Algebra *a, uint64_t bits) {
  show(a, bits);
  printf("\n");
}