// vim:fileencoding=utf-8:foldmethod=marker

#include "cpath.h"
#include "../allocators/arena/mem_arena_allocator.h"
#include "../../common/aliases/aliases.h"
#include "../../common/misc/misc_utils.h"
#include "../../base/dbl_list/dbl_list.h"
#include "../../base/mem/allocator/mem_allocator.h"
#include "../../base/strings/str8/str8.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

u32 wapp_cpath_join_path(Str8 *dst, const Str8List *parts) {
  if (!dst || !parts) {
    return CPATH_JOIN_INVALID_ARGS;
  }

  if (parts->node_count == 0) {
    return CPATH_JOIN_EMPTY_PARTS;
  }

  Str8 separator = wapp_str8_buf(4);
  wapp_str8_push_back(&separator, WAPP_PATH_SEP);

  u64 required_capacity = parts->node_count * separator.size + wapp_str8_list_total_size(parts);
  if (dst->capacity < required_capacity) {
    return CPATH_JOIN_INSUFFICIENT_DST_CAPACITY;
  }

  // Handle first node
  const Str8Node *first_node = wapp_dbl_list_get(Str8, Str8Node, parts, 0);
  wapp_str8_copy_str8_capped(dst, first_node->item);

  // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of
  // MSVC Spectre mitigation warnings
  const Str8Node *node = first_node;
  u64 node_index       = 1;
  b8 running          = node_index < parts->node_count;
  while (running && node->next) {
    node = node->next;
    if (node->item->size == 0) {
      continue;
    }

    if (dst->size > 0) {
      char dst_last    = wapp_str8_get(dst, dst->size - 1);
      char node_start  = wapp_str8_get(node->item, 0);
      b8 add_path_sep = dst_last != WAPP_PATH_SEP && node_start != WAPP_PATH_SEP;

      if (add_path_sep) {
        wapp_str8_concat_capped(dst, &separator);
      }
    }

    wapp_str8_concat_capped(dst, node->item);

    ++node_index;
    running = node_index < parts->node_count;
  }

  return CPATH_JOIN_SUCCESS;
}

Str8 *dirup(const Allocator *allocator, Str8RO *path, u64 levels) {
  Str8 *output = NULL;
  if (!allocator || !path) {
    goto RETURN_DIRUP;
  }

  b8 absolute   = wapp_str8_get(path, 0) == WAPP_PATH_SEP;
  Str8 separator = wapp_str8_buf(4);
  wapp_str8_push_back(&separator, WAPP_PATH_SEP);

  if (path->size == 0) {
    output = wapp_str8_alloc_buf(allocator, 16);
    if (!output) {
      goto RETURN_DIRUP;
    }

    wapp_str8_push_back(output, absolute ? WAPP_PATH_SEP : '.');
    goto RETURN_DIRUP;
  }

  if (levels < 1) {
    output = wapp_str8_alloc_str8(allocator, path);
    goto RETURN_DIRUP;
  }

  Allocator tmp_arena = wapp_mem_arena_allocator_init(MB(8));
  if (wapp_mem_allocator_invalid(&tmp_arena)) {
    goto RETURN_DIRUP;
  }

  Str8List *parts = wapp_str8_split(&tmp_arena, path, &separator);
  if (!parts) {
    goto RETURN_DIRUP;
  }

  if (levels >= parts->node_count) {
    output = wapp_str8_alloc_buf(allocator, 16);
    if (!output) {
      goto LIST_CLEANUP_DIRUP;
    }

    wapp_str8_push_back(output, absolute ? WAPP_PATH_SEP : '.');
  } else {
    for (u64 i = 0; i < levels; ++i) {
      wapp_dbl_list_pop_back(Str8, Str8Node, parts);
    }
    
    u64 alignment  = sizeof(void *) * 2;
    u64 alloc_size = wapp_str8_list_total_size(parts) + parts->node_count * separator.size;
    u64 modulo     = alloc_size & (alignment - 1);
    alloc_size    += alignment - modulo;

    output = wapp_str8_alloc_buf(allocator, alloc_size);
    if (output) {
      if (absolute) {
        wapp_str8_push_back(output, WAPP_PATH_SEP);
      }

      Str8 *joined = wapp_str8_join(&tmp_arena, parts, &separator);
      if (joined) {
        wapp_str8_concat_capped(output, joined);
      }
    }
  }

LIST_CLEANUP_DIRUP:
  wapp_mem_arena_allocator_destroy(&tmp_arena);

RETURN_DIRUP:
  return output;
}
