#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;

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

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

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

typedef union value {
  u64 integer;
  double float_;
  struct closure *closure;
  struct vec *vec;
  struct variant *variant;
} val_t;

enum error {
  NotImplemented = 10,
  ArrayIndexOutOfBounds = 11,
  TypeMismatch = 12,
  UnexpectedValue = 13,
  UnexpectedVariantCase = 15,
};

__attribute__((noreturn)) 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;
  case UnexpectedVariantCase:
    printf("unexpected variant case\n");
    break;
  default:
    printf("unknown error code %d\n", (int)e);
    break;
  };
  exit(e);
}

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

u64 print_bool(u64 value) {
  if (value) {
    printf("true");
  } else {
    printf("false");
  }
  return value;
}

u64 print_float(u64 value) {
  val_t val = (val_t)value;
  printf("%g", val.float_);
  return value;
}

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

u64 print(u64 value) {
  error(NotImplemented);
  printf("can't be implemented for the untagged memory representation\n");
  return value;
}

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

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

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

u64 vector_length(u64 vector) {
  struct vec *vector_ptr = (struct vec *)vector;
  return vector_ptr->size;
}

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->id = id;
  ptr->case_id = case_id;
  return (u64)ptr;
}

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

u64 read_float() {
  val_t val;
  scanf("%lf", &val.float_);
  return val.integer;
}

u64 read_char() {
  char c;
  scanf(" %c", &c);
  return c;
}

u64 allocate_closure(void *fptr, u64 num_captured_vals) {
  struct closure *closure_ptr = (struct closure *)grev_malloc(
      sizeof(struct closure) + sizeof(u64 *) * num_captured_vals);
  closure_ptr->fptr = (u64)fptr;
  return (u64)closure_ptr;
}

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

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

  fptr(ptr, 0);

  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;
}
