// 0x7  f    f                        8                                 | 7 
// <01111111 1111 12-bit nan header> <1-bit silent flag, 0 on most cpus> <3-bit tag> <48-bit payload>
// 000 - variable - pointer to variable name
// 001 - atom
// 010 - small integer
// 011 - large integer
// 100 - pair     - <3-bit type> <21-bit unused> <24-bit index to first>
//                  000 - fraction
//                  001 - power
//                  010 - unused
//                  011 - unused
//                  100 - equals
//                  101 - not equals
//                  110 - less than
//                  111 - greater than
// 101 - list     - <1-bit type> <23-bit count> <24-bit index to first>
//                  0 - sum
//                  1 - product
// 110 - unused
// 111 - unused
// 

// new integer encoding
//                             0x0000800000000000 == flag mask
//                                   800000000000 == both  - negative flag
//                                   7FFFFFFFFFFF == small - uint44_t
//                                   7FFFFF000000 == large - 21-bit count
//                                         FFFFFF == large - 24-bit index


#define NAN_MASK               0xFFF0000000000000ull
#define NAN_BITS               0x7FF0000000000000ull
#define QUEIT_BITS             0x7FF0000000000000ull
#define SIGNALING_BITS         0x7FF8000000000000ull
#define TAG_MASK               0xFFF7000000000000ull
#define TAG_VARIABLE_BITS      0x7FF0000000000000ull
#define TAG_ATOM_BITS          0x7FF1000000000000ull
#define TAG_SMALLINT_BITS      0x7FF2000000000000ull
#define TAG_LARGEINT_BITS      0x7FF3000000000000ull
#define TAG_PAIR_BITS          0x7FF4000000000000ull
#define TAG_LIST_BITS          0x7FF5000000000000ull
#define TAG_UNUSED6_BITS       0x7FF6000000000000ull
#define TAG_UNUSED7_BITS       0x7FF7000000000000ull
#define PAIR_MASK              0xFFF7e00000000000ull
#define PAIR_FRACTION_BITS     0x7FF4000000000000ull
#define PAIR_POWER_BITS        0x7FF4200000000000ull
#define PAIR_UNUSED1_BITS      0x7FF4400000000000ull
#define PAIR_UNUSED2_BITS      0x7FF4600000000000ull
#define PAIR_EQUAL_BITS        0x7FF4800000000000ull
#define PAIR_NOT_EQUAL_BITS    0x7FF4a00000000000ull
#define PAIR_LESS_THAN_BITS    0x7FF4c00000000000ull
#define PAIR_GREATER_THAN_BITS 0x7FF4e00000000000ull
#define LIST_MASK              0xFFF7800000000000ull
#define LIST_SUM_BITS          0x7FF5000000000000ull
#define LIST_PRODUCT_BITS      0x7FF5800000000000ull
#define PAYLOAD_MASK           0x0000FFFFFFFFFFFFull
#define PAIR_INDEX_MASK        0x0000000000FFFFFFull // 24-bits
#define LIST_COUNT_MASK        0x00007FFFFF000000ull // 23-bits
#define LIST_INDEX_MASK        0x0000000000FFFFFFull // 24-bits
#define INTEGER_MASK           0xFFFE000000000000ull // mask ignores small or large integer descriminating bit
#define INTEGER_BITS           0x7FF2000000000000ull
#define INTEGER_NEG_FLAG_BIT   0x0000800000000000ull
#define INTEGER_COUNT_MASK     0x00007FFFFF000000ull // 23-bits
#define INTEGER_INDEX_MASK     0x0000000000FFFFFFull // 24-bits
#define INTEGER_SMALL_MASK     0x00007FFFFFFFFFFFull // 47-bits

#define LITERAL_FALSE          (TAG_ATOM_BITS     | 0)
#define LITERAL_TRUE           (TAG_ATOM_BITS     | 1)
#define LITERAL_UNDEFINED      (TAG_ATOM_BITS     | 2)
#define LITERAL_ZERO           (TAG_SMALLINT_BITS | 0)
#define LITERAL_ONE            (TAG_SMALLINT_BITS | 1)
#define LITERAL_TWO            (TAG_SMALLINT_BITS | 2)
#define LITERAL_NEGATIVE_ONE   (TAG_SMALLINT_BITS | INTEGER_NEG_FLAG_BIT | 1)

static uint32_t get_pair_index(uint64_t expr) {
  assert((expr & TAG_MASK) == TAG_PAIR_BITS);
  return expr & LIST_INDEX_MASK;
}

static uint32_t get_list_count(uint64_t expr) {
  assert((expr & TAG_MASK) == TAG_LIST_BITS);
  return (expr & LIST_COUNT_MASK) >> 24;
}

static uint32_t get_list_index(uint64_t expr) {
  assert((expr & TAG_MASK) == TAG_LIST_BITS);
  return expr & LIST_INDEX_MASK;
}

static uint32_t get_integer_count(uint64_t expr) {
  assert((expr & TAG_MASK) == TAG_LARGEINT_BITS);
  return (expr & INTEGER_COUNT_MASK) >> 24;
}

static uint32_t get_integer_index(uint64_t expr) {
  assert((expr & TAG_MASK) == TAG_LARGEINT_BITS);
  return (expr & INTEGER_INDEX_MASK);
}

struct EncodedList {
  uint32_t count;
  uint32_t index;
};

// --------------------------------------------------------------------------------------------------------------------
#define DEFINE_ENCODING(TYPE, CTYPE, IS_FN, PACK_FN, UNPACK_FN) \
  static inline bool         is_##TYPE(uint64_t packed)   IS_FN \
  static inline uint64_t   pack_##TYPE(CTYPE    unpacked) PACK_FN \
  static inline CTYPE    unpack_##TYPE(uint64_t packed)   { assert(is_##TYPE(packed)); UNPACK_FN }

union DoubleBits {
  uint64_t u;
  double d;
};

DEFINE_ENCODING(
  real, double,
  { return (packed & NAN_MASK) != NAN_BITS; },
  { 
    union DoubleBits _;
    _.d = unpacked;
    return _.u;
  },
  {
    union DoubleBits _;
    _.u = packed;
    return _.d;
  }
)

DEFINE_ENCODING(
  variable, const char *,
  { return (packed & TAG_MASK) == TAG_VARIABLE_BITS; },
  { return TAG_VARIABLE_BITS | ((uint64_t)unpacked & PAYLOAD_MASK); },
  { return (const char *)(packed & PAYLOAD_MASK); }
)


DEFINE_ENCODING(
  boolean, bool,
  { return (packed & TAG_MASK) == TAG_ATOM_BITS && (packed & (PAYLOAD_MASK ^ 1)) == 0; },
  { return TAG_ATOM_BITS | !!unpacked; },
  { return packed ^ 1; }
)

static inline bool     is_undefined(uint64_t packed)     { return packed == (TAG_ATOM_BITS | 2); }
static inline uint64_t pack_undefined()                  { return TAG_ATOM_BITS | 2; }
static inline void     unpack_undefined(uint64_t packed) { assert(is_undefined(packed)); }

static inline bool is_smallint(uint64_t packed) {
  return (packed & TAG_MASK) == TAG_SMALLINT_BITS;
}
static inline uint64_t pack_smallint(int64_t value) {
  uint64_t result = TAG_SMALLINT_BITS;
  if(value < 0) {
    result |= INTEGER_NEG_FLAG_BIT;
    value *= -1;
  }
  assert((value & ~INTEGER_SMALL_MASK) == 0);
  return result | value;
}
static inline int64_t unpack_smallint(uint64_t expr) {
  assert(is_smallint(expr));
  return (((expr & INTEGER_NEG_FLAG_BIT) == INTEGER_NEG_FLAG_BIT) ? -1 : 1) * ((int64_t)(expr & INTEGER_SMALL_MASK));
}

static inline bool is_largeint(uint64_t packed) {
  return (packed & TAG_MASK) == TAG_LARGEINT_BITS;
}
static inline uint64_t pack_largeint(uint64_t flags, uint32_t count, uint32_t index) {
  return TAG_LARGEINT_BITS | flags | (count << 24) | index;
}
static inline struct EncodedList unpack_largeint(uint64_t packed) {
  struct EncodedList _;
  _.count = (packed & INTEGER_COUNT_MASK) >> 24;
  _.index = (packed & INTEGER_INDEX_MASK);
  return _;
}

#define DEFINE_PAIR_ENCODING(TYPE, BITS) \
  DEFINE_ENCODING( \
    TYPE, uint32_t, \
    { return (packed & PAIR_MASK) == BITS; }, \
    { return BITS | (unpacked & PAIR_INDEX_MASK); }, \
    { return packed & PAIR_INDEX_MASK; } \
  )

DEFINE_PAIR_ENCODING(fraction, PAIR_FRACTION_BITS)
DEFINE_PAIR_ENCODING(power, PAIR_POWER_BITS)
#define DEFINE_LIST_ENCODING(TYPE, BITS) \
  DEFINE_ENCODING( \
    TYPE, struct EncodedList, \
    { return (packed & LIST_MASK) == BITS; }, \
    { return BITS | (unpacked.count << 24) | unpacked.index; }, \
    { struct EncodedList _; _.count = (packed & LIST_COUNT_MASK) >> 24; _.index = (packed & LIST_INDEX_MASK); return _; } \
  )

DEFINE_LIST_ENCODING(sum, LIST_SUM_BITS)
DEFINE_LIST_ENCODING(product, LIST_PRODUCT_BITS)

DEFINE_PAIR_ENCODING(equals, PAIR_EQUAL_BITS)
DEFINE_PAIR_ENCODING(not_equals, PAIR_NOT_EQUAL_BITS)
DEFINE_PAIR_ENCODING(less_than, PAIR_LESS_THAN_BITS)
DEFINE_PAIR_ENCODING(greater_than, PAIR_GREATER_THAN_BITS)

enum Tag {
  Tag_Real,
  Tag_Variable,
  Tag_Boolean,   // atom 0 and 1
  Tag_Undefined, // atom 2
  Tag_SmallInt,
  Tag_LargeInt,
  Tag_Fraction,
  Tag_Power,
  Tag_Sum,
  Tag_Product,
  Tag_Equals,
  Tag_NotEquals,
  Tag_LessThan,
  Tag_GreaterThan,
  Tag_Invalid
};

static enum Tag get_tag(uint64_t bits) {
  if(is_real(bits)) {
    return Tag_Real;
  } else if(is_variable(bits)) {
    return Tag_Variable;
  } else if(is_boolean(bits)) {
    return Tag_Boolean;
  } else if(is_undefined(bits)) {
    return Tag_Undefined;
  } else if(is_smallint(bits)) {
    return Tag_SmallInt;
  } else if(is_fraction(bits)) {
    return Tag_Fraction;
  } else if(is_power(bits)) {
    return Tag_Power;
  } else if(is_equals(bits)) {
    return Tag_Equals;
  } else if(is_not_equals(bits)) {
    return Tag_NotEquals;
  } else if(is_less_than(bits)) {
    return Tag_LessThan;
  } else if(is_greater_than(bits)) {
    return Tag_GreaterThan;
  } else if(is_sum(bits)) {
    return Tag_Sum;
  } else if(is_product(bits)) {
    return Tag_Product;
  } else {
    return Tag_Invalid;
  }
}

#undef DEFINE_LIST_ENCODING
#undef DEFINE_PAIR_ENCODING
#undef DEFINE_ENCODING