#include <gc.h>
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define grev_malloc GC_malloc

typedef uint64_t u64;
typedef int64_t i64;

static const u64 INT_MASK = 0b1;

static const u64 BOXED_MASK = 0b11;

// Tags
static const u64 VECTOR_TAG = 3;
static const u64 CLOSURE_TAG = 4;
static const u64 TUPLE_TAG = 5;
static const u64 FLOAT_TAG = 6;
static const u64 VARIANT_TAG = 7;

static const u64 CONST_TRUE = 0b110L;
static const u64 CONST_FALSE = 0b010L;

enum error {
  NotImplemented = 10,
  ArrayIndexOutOfBounds = 11,
  TypeMismatch = 12,
  UnexpectedValue = 13,
  TupleIndexOutOfBounds = 14,
};

struct tuple {
  u64 tag;
  u64 len;
  u64 vals[];
};

struct closure {
  u64 tag;
  u64 fptr;
  u64 captured_vals[];
};

struct vec {
  u64 tag;
  u64 size;
  u64 vals[];
};

struct variant {
  u64 tag;
  u64 id;
  u64 case_id;
  u64 vals[];
};

struct float_ {
  u64 tag;
  double val;
};

struct header {
  u64 tag;
};

typedef union value {
  u64 imm;
  struct tuple *tuple;
  struct closure *closure;
  struct vec *vec;
  struct variant *variant;
  struct float_ *float_;
  struct header *header;
} val;

void error(enum error e) {
  printf("Runtime error, ");
  switch (e) {
  case NotImplemented:
    printf("not implemented\n");
    break;
  case ArrayIndexOutOfBounds:
    printf("array index out of bounds\n");
    break;
  case TypeMismatch:
    printf("type mismatch\n");
    break;
  case UnexpectedValue:
    printf("unexpected value\n");
    break;
  default:
    printf("unknown error code, %d\n", e);
    break;
  };
  exit(e);
}

u64 print(val value);

u64 print_int(u64 value) {
  printf("%" PRIi64, (((i64)value) >> 1));
  return value;
}

u64 print_bool(u64 value) {
  if (value == CONST_TRUE) {
    printf("true");
  } else if (value == CONST_FALSE) {
    printf("false");
  } else {
    printf("unknown bool value: %" PRIu64, value);
    error(UnexpectedValue);
  }
  return value;
}

u64 print_closure(u64 value) {
  printf("<closure>");
  return value;
}

u64 print_tuple(u64 value) {
  struct tuple *tuple_ptr = (struct tuple *)value;
  u64 length = tuple_ptr->len;
  printf("(");
  for (size_t i = 0; i < length; i++) {
    print((val)tuple_ptr->vals[i]);
    printf(", ");
  }
  printf(")");
  return value;
}

u64 print_vector(u64 value) {
  printf("<vector>");
  return value;
}

u64 print_variant(u64 value) {
  printf("<variant>");
  return value;
}

u64 print_float(u64 value) {
  struct float_ *float_ptr = (struct float_ *)value;
  printf("%g", float_ptr->val);
  return value;
}

u64 print_char(u64 val) {
  char c = (char)val;
  printf("%c", c);
  return val;
}

u64 print(val value) {
  if (value.imm & INT_MASK) {
    print_int(value.imm);
  } else if ((value.imm & CONST_FALSE) == CONST_FALSE) {
    print_bool(value.imm);
  } else {
    switch (value.header->tag) {
    case TUPLE_TAG:
      print_tuple(value.imm);
      break;
    case CLOSURE_TAG:
      print_closure(value.imm);
      break;
    case FLOAT_TAG:
      print_float(value.imm);
      break;
    case VECTOR_TAG:
      print_vector(value.imm);
      break;
    case VARIANT_TAG:
      print_variant(value.imm);
      break;
    default:
      printf("'%" PRIx64 "'\n", value.imm);
      error(UnexpectedValue);
    }
  }
  printf("\n");
  return value.imm;
}

void assert_int(val n) {
  if ((n.imm & INT_MASK) == INT_MASK) {
    return;
  } else {
    printf("Runtime error, type mismatch: expected value of type 'int', but "
           "got: ");
    print(n);
    exit(TypeMismatch);
  }
}

void assert_bool(val n) {
  if ((n.imm & CONST_FALSE) == CONST_FALSE) {
    return;
  } else {
    printf("Runtime error, type mismatch: expected value of type 'bool', but "
           "got: ");
    print(n);
    exit(TypeMismatch);
  }
}

void assert_in_bounds(struct tuple *tuple, u64 i) {
  u64 length = tuple->len;
  if (i < length) {
    return;
  } else {
    printf("Runtime error, tuple index out of bounds: tried to acces index "
           "'%" PRId64 "', but length is '%" PRId64 "'",
           i, length);
    exit(TupleIndexOutOfBounds);
  }
}

void assert_tuple(val n) {
  if ((n.imm & BOXED_MASK) == 0 && n.tuple->tag == TUPLE_TAG) {
    return;
  } else {
    printf("Runtime error, type mismatch: expected tuple, but got: ");
    print(n);
    exit(TypeMismatch);
  }
}

void assert_closure(val n) {
  if ((n.imm & BOXED_MASK) == 0 && n.closure->tag == CLOSURE_TAG) {
    return;
  } else {
    printf("Runtime error, type mismatch: expected closure, but got: ");
    print(n);
    exit(TypeMismatch);
  }
}

void assert_vector(val n) {
  if ((n.imm & BOXED_MASK) == 0 && n.vec->tag == VECTOR_TAG) {
    return;
  } else {
    printf("Runtime error, type mismatch: expected vector, but got: ");
    print(n);
    exit(TypeMismatch);
  }
}

void assert_variant(val n, u64 variant_id) {
  if ((n.imm & BOXED_MASK) == 0 && n.variant->tag == VARIANT_TAG &&
      n.variant->id == variant_id) {
    return;
  } else {
    printf("Runtime error, type mismatch: expected variant, but got: ");
    print(n);
    exit(TypeMismatch);
  }
}

void assert_float(val n) {
  if ((n.imm & BOXED_MASK) == 0 && n.float_->tag == FLOAT_TAG) {
    return;
  } else {
    printf("Runtime error, type mismatch: expected float, but got: ");
    print(n);
    exit(TypeMismatch);
  }
}

u64 make_vector(u64 size, u64 initial_value) {
  u64 size_untagged = size >> 1;
  struct vec *ptr = (struct vec *)grev_malloc(sizeof(struct vec) +
                                              size_untagged * sizeof(u64 *));
  ptr->tag = VECTOR_TAG;
  ptr->size = size_untagged;
  for (size_t i = 0; i < size_untagged; ++i) {
    ptr->vals[i] = initial_value;
  }
  return (u64)ptr;
}

u64 vector_get(u64 vector, u64 idx) {
  struct vec *vector_ptr = (struct vec *)vector;
  u64 idx_untagged = idx >> 1;
  if (idx_untagged < 0 || idx_untagged >= vector_ptr->size) {
    error(ArrayIndexOutOfBounds);
  }
  return vector_ptr->vals[idx_untagged];
}

u64 vector_set(u64 vector, u64 idx, u64 val) {
  struct vec *vector_ptr = (struct vec *)vector;
  u64 idx_untagged = idx >> 1;
  if (idx_untagged < 0 || idx_untagged >= vector_ptr->size) {
    error(ArrayIndexOutOfBounds);
  }
  vector_ptr->vals[idx_untagged] = val;
  return vector;
}

u64 vector_length(u64 vector) {
  struct vec *vector_ptr = (struct vec *)vector;
  u64 size_tagged = (vector_ptr->size << 1) | INT_MASK;
  return size_tagged;
}

u64 tuple_get(val value, u64 idx) {
  assert_in_bounds(value.tuple, idx);
  return value.tuple->vals[idx];
}

u64 make_variant(u64 id, u64 case_id, u64 num_args) {
  struct variant *ptr = (struct variant *)grev_malloc(sizeof(struct variant) +
                                                      num_args * sizeof(u64 *));
  ptr->tag = VARIANT_TAG;
  ptr->id = id;
  ptr->case_id = case_id;
  return (u64)ptr;
}

u64 make_float(double val) {
  struct float_ *ptr = (struct float_ *)grev_malloc(sizeof(struct float_));
  ptr->tag = FLOAT_TAG;
  ptr->val = val;
  return (u64)ptr;
}

u64 read_int() {
  i64 val;
  scanf("%" SCNi64, &val);
  return (val << 1) | INT_MASK;
}

u64 read_float() {
  double val;
  scanf("%lf", &val);
  u64 value = make_float(val);
  return value;
}

u64 read_char() {
  char c;
  scanf(" %c", &c);
  return (u64)c << 1 | INT_MASK;
}

u64 print_time(u64 value) {
  struct closure *closure_ptr = (struct closure *)value;
  u64 (*fptr)(u64, u64) = (u64 (*)(u64, u64))closure_ptr->fptr;

  struct timespec start, end;
  clock_gettime(CLOCK_MONOTONIC, &start);

  fptr(value, INT_MASK);

  clock_gettime(CLOCK_MONOTONIC, &end);

  double elapsed =
      (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
  fprintf(stderr, "Execution time: %.6f seconds\n", elapsed);

  return (0 << 1) | INT_MASK;
}
