// Copyright (c) 2023 Yuki Kimoto
// MIT License

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>

#include "spvm_allocator.h"
#include "spvm_runtime.h"
#include "spvm_hash.h"
#include "spvm_runtime_string.h"
#include "spvm_runtime_basic_type.h"
#include "spvm_runtime_field.h"
#include "spvm_runtime_class_var.h"
#include "spvm_runtime_method.h"
#include "spvm_runtime_arg.h"
#include "spvm_opcode.h"
#include "spvm_mutex.h"

SPVM_RUNTIME* SPVM_RUNTIME_new() {
  SPVM_RUNTIME* runtime = SPVM_ALLOCATOR_alloc_memory_block_unmanaged(sizeof(SPVM_RUNTIME));
  
  // Allocator
  SPVM_ALLOCATOR* allocator = SPVM_ALLOCATOR_new();
  runtime->allocator = allocator;
  
  runtime->basic_type_symtable = SPVM_HASH_new_hash_permanent(runtime->allocator, 0);
  
  runtime->assignment_requirement_symtable = SPVM_HASH_new_hash_permanent(runtime->allocator, 0);
  
  int32_t mutex_size = SPVM_MUTEX_size();
  void* mutex = SPVM_ALLOCATOR_alloc_memory_block_permanent(runtime->allocator, mutex_size);
  SPVM_MUTEX_init(mutex);
  runtime->mutex = mutex;
  
  int32_t mutex_atomic_size = SPVM_MUTEX_size();
  void* mutex_atomic = SPVM_ALLOCATOR_alloc_memory_block_permanent(runtime->allocator, mutex_atomic_size);
  SPVM_MUTEX_init(mutex_atomic);
  runtime->mutex_atomic = mutex_atomic;
  
  SPVM_RUNTIME_init_stdio(runtime);
  
  SPVM_ENV* env = SPVM_NATIVE_new_env();
  
  runtime->env = env;
  
  env->runtime = runtime;
  
  return runtime;
}

void SPVM_RUNTIME_init_stdio(SPVM_RUNTIME* runtime) {
  
  // stdin
  {
    int32_t stdin_fileno = fileno(stdin);
    
    assert(stdin_fileno >= 0);
    
    int32_t stdin_fileno_dup = dup(stdin_fileno);
    
    assert(stdin_fileno_dup > 2);
    
    FILE* spvm_stdin = fdopen(stdin_fileno_dup, "r");
    
    assert(spvm_stdin);
    
#if defined(_WIN32)
    
    setmode(fileno(spvm_stdin), _O_BINARY);
    
#endif
    
    runtime->spvm_stdin = spvm_stdin;
  }
  
  // stdout
  {
    int32_t stdout_fileno = fileno(stdout);
    
    assert(stdout_fileno >= 0);
    
    int32_t stdout_fileno_dup = dup(stdout_fileno);
    
    assert(stdout_fileno_dup > 2);
    
    FILE* spvm_stdout = fdopen(stdout_fileno_dup, "w");
    
    assert(spvm_stdout);
    
#if defined(_WIN32)
    
    setmode(fileno(spvm_stdout), _O_BINARY);
    
#endif
    
    runtime->spvm_stdout = spvm_stdout;
    
  }
  
  // stderr
  {
    int32_t stderr_fileno = fileno(stderr);
    
    assert(stderr_fileno >= 0);
    
    int32_t stderr_fileno_dup = dup(stderr_fileno);
    
    assert(stderr_fileno_dup > 2);
    
    FILE* spvm_stderr = fdopen(stderr_fileno_dup, "w");
    
    assert(spvm_stderr);
    
#if defined(_WIN32)
    
    setmode(fileno(spvm_stderr), _O_BINARY);
    
#endif
    
    setvbuf(spvm_stderr, NULL, _IONBF, 0);
    
    runtime->spvm_stderr = spvm_stderr;
  }
  
}

void SPVM_RUNTIME_free(SPVM_RUNTIME* runtime) {
  
  SPVM_ENV* env = runtime->env;
  
  env->free_env(env);
  
  if (runtime->basic_types) {
    SPVM_ALLOCATOR_free_memory_block_tmp(runtime->allocator, runtime->basic_types);
  }
  
  SPVM_MUTEX_destroy(runtime->mutex);
  
  SPVM_MUTEX_destroy(runtime->mutex_atomic);
  
  fclose(runtime->spvm_stdin);
  
  fclose(runtime->spvm_stdout);
  
  fclose(runtime->spvm_stderr);
  
  // Free allocator
  SPVM_ALLOCATOR_free(runtime->allocator);
  runtime->allocator = NULL;
}
