#include "test_str8.h"
#include "mem_allocator.h"
#include "mem_arena_allocator.h"
#include "misc_utils.h"
#include "str8.h"
#include "tester.h"
#include <stdbool.h>

#define ARRLEN(ARR) (sizeof(ARR) / sizeof(ARR[0]))

TestFuncResult test_str8_lit(void) {
  bool result;

  Str8 s1 = wapp_str8_lit("Hello world");
  result  = s1.capacity == 22 && s1.capacity != s1.size;

  Str8 s2 = wapp_str8_lit("Different strokes for different folks");
  result  = result && s2.capacity == 74 && s2.capacity != s2.size;

  Str8 s3 = wapp_str8_lit("Discretion is the better part of valour");
  result  = result && s3.capacity == 78 && s3.capacity != s3.size;

  Str8 s4 = wapp_str8_lit("Distance lends enchantment to the view");
  result  = result && s4.capacity == 76 && s4.capacity != s4.size;

  Str8 s5 = wapp_str8_lit("Do as I say, not as I do");
  result  = result && s5.capacity == 48 && s5.capacity != s5.size;

  Str8 s6 = wapp_str8_lit("Do as you would be done by");
  result  = result && s6.capacity == 52 && s6.capacity != s6.size;

  Str8 s7 = wapp_str8_lit("Do unto others as you would have them do to you");
  result  = result && s7.capacity == 94 && s7.capacity != s7.size;

  return wapp_tester_result(result);
}

TestFuncResult test_str8_lit_ro(void) {
  bool result;

  Str8RO s1 = wapp_str8_lit_ro("Hello world");
  result    = s1.capacity == 11 && s1.capacity == s1.size;

  Str8RO s2 = wapp_str8_lit_ro("Different strokes for different folks");
  result    = result && s2.capacity == 37 && s2.capacity == s2.size;

  Str8RO s3 = wapp_str8_lit_ro("Discretion is the better part of valour");
  result    = result && s3.capacity == 39 && s3.capacity == s3.size;

  Str8RO s4 = wapp_str8_lit_ro("Distance lends enchantment to the view");
  result    = result && s4.capacity == 38 && s4.capacity == s4.size;

  Str8RO s5 = wapp_str8_lit_ro("Do as I say, not as I do");
  result    = result && s5.capacity == 24 && s5.capacity == s5.size;

  Str8RO s6 = wapp_str8_lit_ro("Do as you would be done by");
  result    = result && s6.capacity == 26 && s6.capacity == s6.size;

  Str8RO s7 = wapp_str8_lit_ro("Do unto others as you would have them do to you");
  result    = result && s7.capacity == 47 && s7.capacity == s7.size;

  return wapp_tester_result(result);
}

TestFuncResult test_str8_buf(void) {
  bool result;

  Str8 s1 = wapp_str8_buf(1024);
  result  = s1.capacity == 1024 && s1.size == 0;

  Str8 s2 = wapp_str8_buf(2048);
  result  = result && s2.capacity == 2048 && s2.size == 0;

  Str8 s3 = wapp_str8_buf(4096);
  result  = result && s3.capacity == 4096 && s3.size == 0;

  Str8 s4 = wapp_str8_buf(8192);
  result  = result && s4.capacity == 8192 && s4.size == 0;

  return wapp_tester_result(result);
}

TestFuncResult test_str8_alloc_buf(void) {
  bool result;
  Allocator allocator = wapp_mem_arena_allocator_init(KB(100));
  if (wapp_mem_allocator_invalid(&allocator)) {
    return wapp_tester_result(false);
  }

  u64 capacity = 4096;

  Str8 *s = wapp_str8_alloc_buf(&allocator, capacity);
  if (!s) {
    result = false;
    goto TEST_ALLOC_BUF_CLEANUP;
  }

  result  = s->capacity == capacity;

  const char *cstr = "My name is Abdelrahman";
  wapp_str8_copy_cstr_capped(s, cstr);

  result = result && s->capacity == capacity && s->size == strlen(cstr) && memcmp(s->buf, cstr, s->size) == 0;

TEST_ALLOC_BUF_CLEANUP:
  wapp_mem_arena_allocator_destroy(&allocator);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_alloc_cstr(void) {
  bool result;
  Allocator allocator = wapp_mem_arena_allocator_init(KB(100));
  if (wapp_mem_allocator_invalid(&allocator)) {
    return wapp_tester_result(false);
  }

  char *str  = "Abdelrahman";
  u64 length = strlen(str);
  Str8 *s    = wapp_str8_alloc_cstr(&allocator, str);
  if (!s) {
    return wapp_tester_result(false);
  }

  result = s->size == length && memcmp(s->buf, str, length) == 0;

  wapp_mem_arena_allocator_destroy(&allocator);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_alloc_str8(void) {
  bool result;
  Allocator allocator = wapp_mem_arena_allocator_init(KB(100));
  if (wapp_mem_allocator_invalid(&allocator)) {
    return wapp_tester_result(false);
  }

  Str8 str = wapp_str8_lit("Abdelrahman");
  Str8 *s  = wapp_str8_alloc_str8(&allocator, &str);
  if (!s) {
    return wapp_tester_result(false);
  }

  result = wapp_str8_equal(s, &str);

  wapp_mem_arena_allocator_destroy(&allocator);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_alloc_substr(void) {
  bool result;
  Allocator allocator = wapp_mem_arena_allocator_init(KB(100));
  if (wapp_mem_allocator_invalid(&allocator)) {
    return wapp_tester_result(false);
  }

  Str8 str = wapp_str8_lit("Abdelrahman");
  Str8 *s  = wapp_str8_alloc_substr(&allocator, &str, 3, 8);
  if (!s) {
    return wapp_tester_result(false);
  }

  result = s->size == 5 && memcmp(s->buf, "elrah", s->size) == 0;

  wapp_mem_arena_allocator_destroy(&allocator);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_get_index_within_bounds(void) {
  bool result;

  Str8RO s1 = wapp_str8_lit_ro("Hello world");
  result    = wapp_str8_get(&s1, 4) == 'o';

  Str8RO s2 = wapp_str8_lit_ro("Different strokes for different folks");
  result    = result && wapp_str8_get(&s2, 0) == 'D';

  Str8RO s3 = wapp_str8_lit_ro("Discretion is the better part of valour");
  result    = result && wapp_str8_get(&s3, 13) == ' ';

  Str8RO s4 = wapp_str8_lit_ro("Distance lends enchantment to the view");
  result    = result && wapp_str8_get(&s4, 20) == 'n';

  Str8RO s5 = wapp_str8_lit_ro("Do as I say, not as I do");
  result    = result && wapp_str8_get(&s5, 11) == ',';

  Str8RO s6 = wapp_str8_lit_ro("Do as you would be done by");
  result    = result && wapp_str8_get(&s6, 25) == 'y';

  Str8RO s7 = wapp_str8_lit_ro("Do unto others as you would have them do to you");
  result    = result && wapp_str8_get(&s7, 16) == 's';

  return wapp_tester_result(result);
}

TestFuncResult test_str8_get_index_out_of_bounds(void) {
  Str8 s1      = wapp_str8_lit("Hello world");
  bool result  = wapp_str8_get(&s1, 20) == '\0';
  return wapp_tester_result(result);
}

TestFuncResult test_str8_set(void) {
  bool result;

  Str8 s1 = wapp_str8_lit("Hello world");
  wapp_str8_set(&s1, 4, 'f');
  result  = wapp_str8_get(&s1, 4) == 'f';

  Str8 s2 = wapp_str8_lit("Different strokes for different folks");
  wapp_str8_set(&s2, 0, 'A');
  result  = result && wapp_str8_get(&s2, 0) == 'A';

  Str8 s3 = wapp_str8_lit("Discretion is the better part of valour");
  wapp_str8_set(&s3, 13, 'u');
  result  = result && wapp_str8_get(&s3, 13) == 'u';

  Str8 s4 = wapp_str8_lit("Distance lends enchantment to the view");
  wapp_str8_set(&s4, 20, 'R');
  result  = result && wapp_str8_get(&s4, 20) == 'R';

  Str8 s5 = wapp_str8_lit("Do as I say, not as I do");
  wapp_str8_set(&s5, 11, '.');
  result  = result && wapp_str8_get(&s5, 11) == '.';

  Str8 s6 = wapp_str8_lit("Do as you would be done by");
  wapp_str8_set(&s6, 25, 'w');
  result  = result && wapp_str8_get(&s6, 25) == 'w';

  Str8 s7 = wapp_str8_lit("Do unto others as you would have them do to you");
  wapp_str8_set(&s7, 16, 'i');
  result  = result && wapp_str8_get(&s7, 16) == 'i';

  return wapp_tester_result(result);
}

TestFuncResult test_str8_equal(void) {
  bool result;

  Str8RO s1 = wapp_str8_lit_ro("hello");
  Str8RO s2 = wapp_str8_lit_ro("hell");
  Str8RO s3 = wapp_str8_lit_ro("hello");
  Str8RO s4 = wapp_str8_lit_ro("goodbye");

  result = wapp_str8_equal(&s1, &s2) == false;
  result = result && wapp_str8_equal(&s1, &s3) == true;
  result = result && wapp_str8_equal(&s1, &s4) == false;

  return wapp_tester_result(result);
}

TestFuncResult test_str8_slice(void) {
  bool result;
  Str8 s = wapp_str8_lit("Different strokes for different folks");

  Str8RO sub1 = wapp_str8_slice(&s, 3, 9);
  result = sub1.size == 6 && sub1.capacity == 6;

  Str8RO sub2 = wapp_str8_slice(&s, 18, 21);
  result = result && sub2.size == 3 && sub2.capacity == 3;

  Str8RO sub3 = wapp_str8_slice(&s, 5, 1);
  result = result && sub3.size == 0 && sub3.capacity == 0;

  Str8RO sub4 = wapp_str8_slice(&s, 70, 80);
  result = result && sub4.size == 0 && sub4.capacity == 0;

  return wapp_tester_result(result);
}

TestFuncResult test_str8_concat(void) {
  bool result;
  Allocator arena = wapp_mem_arena_allocator_init(KB(100));

  Str8 str     = wapp_str8_lit("Hello world");
  Str8 suffix1 = wapp_str8_lit(" from me.");
  Str8 suffix2 = wapp_str8_lit(" This is my code.");
  Str8 concat1 = wapp_str8_lit("Hello world from me.");
  Str8 concat2 = wapp_str8_lit("Hello world from me. This is my code.");

  Str8 *output;

  output = wapp_str8_concat(&arena, &str, &suffix1);
  result = output->size == concat1.size && wapp_str8_equal(output, &concat1);

  output = wapp_str8_concat(&arena, output, &suffix2);
  result = result && output->size == concat2.size && wapp_str8_equal(output, &concat2);

  wapp_mem_arena_allocator_destroy(&arena);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_concat_capped(void) {
  bool result;

  Str8 str     = wapp_str8_lit("Hello world");
  Str8 suffix1 = wapp_str8_lit(" from me.");
  Str8 suffix2 = wapp_str8_lit(" This is my code.");
  Str8 concat1 = wapp_str8_lit("Hello world from me.");
  Str8 concat2 = wapp_str8_lit("Hello world from me. T");

  wapp_str8_concat_capped(&str, &suffix1);
  result = str.size == concat1.size && wapp_str8_equal(&str, &concat1);

  wapp_str8_concat_capped(&str, &suffix2);
  result = result && str.size == concat2.size && wapp_str8_equal(&str, &concat2);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_copy_cstr_capped(void) {
  bool result;

  Str8 buf         = wapp_str8_buf(32);
  const char *src1 = "Hello world";
  const char *src2 = "Hello world from the Wizard Apprentice standard library";
  Str8RO src1_cp   = wapp_str8_lit_ro("Hello world");
  Str8RO src2_cp   = wapp_str8_lit_ro("Hello world from the Wizard Appr");

  wapp_str8_copy_cstr_capped(&buf, src1);
  result = buf.size == src1_cp.size && wapp_str8_equal(&buf, &src1_cp);

  wapp_str8_copy_cstr_capped(&buf, src2);
  result = result && buf.size == src2_cp.size && buf.size == buf.capacity && wapp_str8_equal(&buf, &src2_cp);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_copy_str8_capped(void) {
  bool result;

  Str8 buf       = wapp_str8_buf(32);
  Str8RO src1    = wapp_str8_lit_ro("Hello world");
  Str8RO src2    = wapp_str8_lit_ro("Hello world from the Wizard Apprentice standard library");
  Str8RO src2_cp = wapp_str8_lit_ro("Hello world from the Wizard Appr");

  wapp_str8_copy_str8_capped(&buf, &src1);
  result = buf.size == src1.size && wapp_str8_equal(&buf, &src1);

  wapp_str8_copy_str8_capped(&buf, &src2);
  result = result && buf.size < src2.size && buf.size == buf.capacity && wapp_str8_equal(&buf, &src2_cp);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_find(void) {
  bool result;
  Str8RO s = wapp_str8_lit("Do as I say, not as I do");

  result = wapp_str8_find(&s, wapp_str8_lit_ro("d")) != -1;
  result = result && (wapp_str8_find(&s, wapp_str8_lit_ro("not")) != -1);
  result = result && (wapp_str8_find(&s, wapp_str8_lit_ro("as I say")) != -1);

  result = result && (wapp_str8_find(&s, wapp_str8_lit_ro("f")) == -1);
  result = result && (wapp_str8_find(&s, wapp_str8_lit_ro("hello")) == -1);
  result = result && (wapp_str8_find(&s, wapp_str8_lit_ro("not sa I")) == -1);
  result = result && (wapp_str8_find(&s, wapp_str8_lit_ro("Do unto others as you would have them do to you")) == -1);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_rfind(void) {
  bool result;
  Str8RO s = wapp_str8_lit("Do as I say, not as I do");

  result = wapp_str8_rfind(&s, wapp_str8_lit_ro("d")) != -1;
  result = result && (wapp_str8_rfind(&s, wapp_str8_lit_ro("not")) != -1);
  result = result && (wapp_str8_rfind(&s, wapp_str8_lit_ro("as I say")) != -1);

  result = result && (wapp_str8_rfind(&s, wapp_str8_lit_ro("f")) == -1);
  result = result && (wapp_str8_rfind(&s, wapp_str8_lit_ro("hello")) == -1);
  result = result && (wapp_str8_rfind(&s, wapp_str8_lit_ro("not sa I")) == -1);
  result = result && (wapp_str8_rfind(&s, wapp_str8_lit_ro("Do unto others as you would have them do to you")) == -1);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_split(void) {
  bool result;
  Allocator arena = wapp_mem_arena_allocator_init(KB(100));

  Str8 str        = wapp_str8_lit("hello world from me");
  Str8 delim1     = wapp_str8_lit(" ");
  Str8 delim2     = wapp_str8_lit("from");
  Str8List *list1 = wapp_str8_split(&arena, &str, &delim1);
  Str8List *list2 = wapp_str8_split(&arena, &str, &delim2);

  Str8RO splits1[] = {
    wapp_str8_slice(&str, 0, 5),
    wapp_str8_slice(&str, 6, 11),
    wapp_str8_slice(&str, 12, 16),
    wapp_str8_slice(&str, 17, 19),
  };
  Str8RO splits2[] = {
    wapp_str8_slice(&str, 0, 12),
    wapp_str8_slice(&str, 16, 19),
  };

  u64 index1    = 0;
  u64 count1    = ARRLEN(splits1);
  bool running1 = true;

  u64 index2    = 0;
  u64 count2    = ARRLEN(splits2);
  bool running2 = true;

  result = list1->node_count == count1 && list1->total_size == str.size - 3;
  result = result && list2->node_count == count2 && list2->total_size == str.size - 4;

  // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of
  // MSVC Spectre mitigation warnings
  while (running1) {
    Str8Node *node = wapp_str8_list_get(list1, index1);
    result = result && wapp_str8_equal(node->string, &(splits1[index1]));

    ++index1;
    running1 = index1 < count1;
  }

  // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of
  // MSVC Spectre mitigation warnings
  while (running2) {
    Str8Node *node = wapp_str8_list_get(list2, index2);
    result = result && wapp_str8_equal(node->string, &(splits2[index2]));

    ++index2;
    running2 = index2 < count2;
  }

  wapp_mem_arena_allocator_destroy(&arena);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_split_with_max(void) {
  bool result;
  Allocator arena = wapp_mem_arena_allocator_init(KB(100));

  Str8 str       = wapp_str8_lit("hello world from me");
  Str8 delim     = wapp_str8_lit(" ");
  Str8List *list = wapp_str8_split_with_max(&arena, &str, &delim, 2);

  Str8RO splits[] = {
    wapp_str8_slice(&str, 0, 5),
    wapp_str8_slice(&str, 6, 11),
    wapp_str8_slice(&str, 12, 19),
  };

  u64 index    = 0;
  u64 count    = ARRLEN(splits);
  bool running = true;

  result = list->node_count == count && list->total_size == str.size - 2;

  // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of
  // MSVC Spectre mitigation warnings
  while (running) {
    Str8Node *node = wapp_str8_list_get(list, index);
    result = result && wapp_str8_equal(node->string, &(splits[index]));

    ++index;
    running = index < count;
  }

  wapp_mem_arena_allocator_destroy(&arena);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_rsplit(void) {
  bool result;
  Allocator arena = wapp_mem_arena_allocator_init(KB(100));

  Str8 str        = wapp_str8_lit("hello world from me");
  Str8 delim1     = wapp_str8_lit(" ");
  Str8 delim2     = wapp_str8_lit("from");
  Str8List *list1 = wapp_str8_rsplit(&arena, &str, &delim1);
  Str8List *list2 = wapp_str8_rsplit(&arena, &str, &delim2);

  Str8RO splits1[] = {
    wapp_str8_slice(&str, 0, 5),
    wapp_str8_slice(&str, 6, 11),
    wapp_str8_slice(&str, 12, 16),
    wapp_str8_slice(&str, 17, 19),
  };
  Str8RO splits2[] = {
    wapp_str8_slice(&str, 0, 12),
    wapp_str8_slice(&str, 16, 19),
  };

  u64 index1    = 0;
  u64 count1    = ARRLEN(splits1);
  bool running1 = true;

  u64 index2    = 0;
  u64 count2    = ARRLEN(splits2);
  bool running2 = true;

  result = list1->node_count == count1 && list1->total_size == str.size - 3;
  result = result && list2->node_count == count2 && list2->total_size == str.size - 4;

  // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of
  // MSVC Spectre mitigation warnings
  while (running1) {
    Str8Node *node = wapp_str8_list_get(list1, index1);
    result = result && wapp_str8_equal(node->string, &(splits1[index1]));

    ++index1;
    running1 = index1 < count1;
  }

  // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of
  // MSVC Spectre mitigation warnings
  while (running2) {
    Str8Node *node = wapp_str8_list_get(list2, index2);
    result = result && wapp_str8_equal(node->string, &(splits2[index2]));

    ++index2;
    running2 = index2 < count2;
  }

  wapp_mem_arena_allocator_destroy(&arena);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_rsplit_with_max(void) {
  bool result;
  Allocator arena = wapp_mem_arena_allocator_init(KB(100));

  Str8 str       = wapp_str8_lit("hello world from me");
  Str8 delim     = wapp_str8_lit(" ");
  Str8List *list = wapp_str8_rsplit_with_max(&arena, &str, &delim, 2);

  Str8RO splits[] = {
    wapp_str8_slice(&str, 0, 11),
    wapp_str8_slice(&str, 12, 16),
    wapp_str8_slice(&str, 17, 19),
  };

  u64 index    = 0;
  u64 count    = ARRLEN(splits);
  bool running = true;

  result = list->node_count == count && list->total_size == str.size - 2;

  // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of
  // MSVC Spectre mitigation warnings
  while (running) {
    Str8Node *node = wapp_str8_list_get(list, index);
    result = result && wapp_str8_equal(node->string, &(splits[index]));

    ++index;
    running = index < count;
  }

  wapp_mem_arena_allocator_destroy(&arena);

  return wapp_tester_result(result);
}

TestFuncResult test_str8_join(void) {
  bool result;
  Allocator arena = wapp_mem_arena_allocator_init(KB(100));

  Str8 str        = wapp_str8_lit("hello world from me");
  Str8 delim1     = wapp_str8_lit(" ");
  Str8 delim2     = wapp_str8_lit("from");
  Str8List *list1 = wapp_str8_rsplit(&arena, &str, &delim1);
  Str8List *list2 = wapp_str8_rsplit(&arena, &str, &delim2);
  Str8 *join1     = wapp_str8_join(&arena, list1, &delim1);
  Str8 *join2     = wapp_str8_join(&arena, list2, &delim2);

  result = join1->size == str.size && wapp_str8_equal(join1, &str);
  result = result && join2->size == str.size && wapp_str8_equal(join2, &str);

  wapp_mem_arena_allocator_destroy(&arena);

  return wapp_tester_result(result);
}