#define ENABLE_STACK_TRACE    0  // #stack_trace
// #define ENABLE_STACK_TRACE    BUILD_DEBUG  // #stack_trace

// #hacky fwd declares
struct Source_Code_Location {
  string file_name;
  string function_name;
  s32 line_number;
};

struct Stack_Trace_Node {
  Stack_Trace_Node* next;
  // Information
  Source_Code_Location data;
  s32 call_depth;
};

struct Error;
struct Graphics;

struct Thread_Context {
  ExpandableArena* temp;  // Used for temporary allocations, scratch space.
  ExpandableArena* arena; // general purpose local arena

  Allocator allocator;
  s32 thread_idx;
  // u16 _padding0;
  u16 default_allocator_alignment = 16;
  string allocator_label;
  
  Logger logger = {nullptr, nullptr};
  String_Builder* log_builder;    // String builder used by log() and log_error_internal()
  String_Builder* string_builder; // Secondary builder just for convenience!
#if ENABLE_STACK_TRACE
  Stack_Trace_Node* stack_trace; // use `list(stack_trace)` in watch window of raddbg to view as array!
#endif
  
  Array<Thread*> child_threads; // maybe should be linked-list?
  Thread_Context* parent_thread_context = nullptr; // so we can remove from above array
  
  string thread_name;
  
  Allocator error_allocator = default_allocator();
  Error* first_error = nullptr;
  Error* current_error = nullptr;
  Arena* error_arena;
  
  // Graphics stuff:
  Graphics* graphics;
  
  void* userdata; // for appending other arenas, etc. 
};

// C_LINKAGE thread_static TCTX* tctx_thread_local;
thread_static Thread_Context* thread_local_context;

// #TODO #NewContext void create_thread_context (Thread_Context** context, string thread_name, bool is_main_thread);
// Thread-context #Errors:

internal void Bootstrap_Main_Thread_Context ();

struct Push_Allocator {
  Thread_Context* context;
  Allocator old_allocator;
  
  Push_Allocator (Allocator new_allocator) {
    context = thread_context();
    if (this->context != nullptr) {
      old_allocator = context->allocator;
      context->allocator = new_allocator;
    } else {
      old_allocator = default_allocator();
    }
  }
  
  ~Push_Allocator () {
    if (this->context != nullptr) {
      context->allocator = old_allocator;
    }
  }
};

// #stack_trace
void push_stack_trace_internal (Thread_Context* context, string file_name, string function_name, s32 line_number) {
#if ENABLE_STACK_TRACE
  if (context == nullptr) return;
  Assert(context != nullptr);
  // #no_context allocation
  Stack_Trace_Node* new_node = (Stack_Trace_Node*)default_allocator_new(sizeof(Stack_Trace_Node));
  
  new_node->data.file_name = file_name;
  new_node->data.function_name = function_name;
  new_node->data.line_number = line_number;
  new_node->next = nullptr;
  
  if (context->stack_trace == nullptr) {
    new_node->call_depth = 1;
    
  } else {
    new_node->call_depth = context->stack_trace->call_depth + 1;
    
    new_node->next = context->stack_trace;
  }
  
  context->stack_trace = new_node;
#endif
}

void pop_stack_trace_internal (Thread_Context* context) {
#if ENABLE_STACK_TRACE
  if (context == nullptr) return;
  Stack_Trace_Node* old_node = context->stack_trace;
  context->stack_trace = old_node->next;
  
  default_allocator_free(old_node);
#endif
}

#if ENABLE_STACK_TRACE
#define stack_trace() \
    Push_Stack_Trace Concat(_push_stack_trace_guard_, __LINE__)(__FILE__, __FUNCTION__, __LINE__)
#else
#define stack_trace()
#endif

struct Push_Stack_Trace {
  Thread_Context* context;
  
  Push_Stack_Trace (string file_name, string function_name, s32 line_number) {
    context = thread_context();
    push_stack_trace_internal(context, file_name, function_name, line_number);
  }
  
  ~Push_Stack_Trace () {
    pop_stack_trace_internal(context);
  }
};

// #TODO: precede with something like: os_write_string_unsynchronized("Fatal Error!\n\nStack trace:", true);
string generate_stack_trace (Thread_Context* context) {
#if ENABLE_STACK_TRACE
  // #no_context - we want this to work even if context is utterly broken.
  String_Builder* sb = new_string_builder(Arena_Reserve::Size_64K, "generate_stack_trace");
  
  Stack_Trace_Node* node = context->stack_trace;

  print_to_builder(sb, "Thread index: %d, thread name: %s\n\n", context->thread_idx, context->thread_name.data);
  
  while (node) {
    append(sb, format_string("%s:%d: %s\n", node->data.file_name.data, node->data.line_number, node->data.function_name.data));
    
    node = node->next;
  }
  
  append(sb, "\n");
  
  push_allocator(default_allocator());
  
  string stack_trace_copy = builder_to_string(sb);
  
  free_string_builder(sb);
  
  return stack_trace_copy;
#else
  return {};
#endif
}

// We don't want to use context logger here!
void print_stack_trace () {
  Thread_Context* context = thread_context();
  
  constexpr bool TO_STANDARD_ERROR = true;
  
  os_write_string_unsynchronized(generate_stack_trace(context), TO_STANDARD_ERROR);
  // Stack_Trace_Node* node = context->stack_trace;
  // while (node) {
  //   os_write_string_unsynchronized(node->data.file_name, TO_STANDARD_ERROR);
  //   string line_number_str = format_string(":%d: ", node->data.line_number); // maybe I shouldn't do this?
  //   os_write_string_unsynchronized(line_number_str, TO_STANDARD_ERROR);
  //   // os_write_string_unsynchronized("'", TO_STANDARD_ERROR);
  //   os_write_string_unsynchronized(node->data.function_name, TO_STANDARD_ERROR);
  //   os_write_string_unsynchronized("\n", TO_STANDARD_ERROR);
    
  //   node = node->next;
  // }
}
