// #NOTE: All string building, printing and copying operations SHOULD null-terminate the 
// strings for backwards compatibility reasons. #FIX if something doesn't follow this rule!
bool is_valid (string s) {
  return (s.data != nullptr && s.count > 0);
}

bool is_c_string (string s) {
  return (s.data && s.data[s.count] == '\0');
}

u8* to_c_string (string s) {
  u8* result = (u8*)internal_alloc(s.count + 1);
  
  memcpy(result, s.data, s.count);
  result[s.count] = '\0';
  
  return result;
}

string copy_string (string s) {
  // Assert(s.count > 0);
  if (s.count <= 0)
    return "";
  string str = {};
  
  str.count = s.count;
  str.data  = (u8*)internal_alloc(s.count + 1);
  
  memcpy(str.data, s.data, s.count);
  
  str.data[str.count] = '\0'; // null-terminate for backwards compatibility?
  
  return str;
}

string copy_string_no_context (string s) {
  if (s.count <= 0)
    return "";
  string str = {};
  
  str.count = s.count;
  str.data  = (u8*)default_allocator_new(s.count + 1);
  
  memcpy(str.data, s.data, s.count);
  
  str.data[str.count] = '\0'; // null-terminate for backwards compatibility?
  
  return str;
}

string copy_string (char* c_string) {
  string str = {};
  s64 string_length = strlen(c_string);
  if (string_length == 0) 
    return "";
  
  str.data = NewArray<u8>(string_length + 1);
  memcpy(str.data, c_string, string_length);
  str.count = string_length;
  
  str.data[str.count] = '\0'; // null-terminate for backwards compatibility?
  
  return str;
}

string to_string (ArrayView<u8> str) {
  return {str.count, str.data};
}

ArrayView<u8> to_view (string s) {
  return {s.count, s.data};
}

void string_free (string& s) {
  internal_free(s.data);
  
  s.data = nullptr;
  s.count = 0;
}

force_inline string string_view (string s, s64 start_index, s64 view_count) {
  Assert(view_count >= 0); Assert(start_index >= 0);
  if (view_count < 0 || start_index < 0 || start_index >= s.count) return "";
  
  s64 new_count = view_count;
  if (start_index + view_count > s.count) {
    new_count = s.count - start_index;
  }

  return { new_count, s.data + start_index };
}

bool strings_match (string first_string, string second_string) {
  return (first_string == second_string);
}

// #Unicode 
string wide_to_utf8 (u16* source, s32 length) {
  if (length == 0) return { };
  
  s32 query_result = WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)source, length, 
                        nullptr, 0, nullptr, nullptr);
  
  if (query_result <= 0) return { };
  
  // Make room for a null terminator:
  if (length != -1) {
    query_result += 1;
  }
  
  u8* memory = NewArray<u8>(query_result);
  
  string utf8_string;
  utf8_string.count = query_result - 1; // null terminator is not counted
  utf8_string.data = memory;
  
  s32 result = WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)source, length, 
                  (LPSTR)memory, query_result, nullptr, nullptr);
  if (result <= 0) {  
    internal_free(memory);
    return { };
  }
  
  return utf8_string;
}

wstring utf8_to_wide (string source) {
  if (!source) return {};
  s32 query_num_chars = MultiByteToWideChar(CP_UTF8, 0,
                                        (LPCCH)source.data, (s32)source.count,  // @Robustness: Silent failure if too long. @Cleanup.
                                        nullptr, 0);
  if (query_num_chars <= 0) return {};
  
  wstring name_u16s = wstring(query_num_chars);
  s32 result_num_chars = MultiByteToWideChar(CP_UTF8, 0,
                                            (LPCCH)source.data, (s32)source.count,  // @Robustness: Silent failure if too long. @Cleanup.
                                            (LPWSTR)name_u16s.data, query_num_chars);
  
  if (!result_num_chars) {
    internal_free(name_u16s.data);
    return {};
  }
  
  Assert(result_num_chars <= query_num_chars);
  name_u16s.data[result_num_chars] = 0; // null terminate
  
  return name_u16s;
}

string format_string_temp (char* format, ...) { // #sprint
  push_allocator(temp());
  constexpr s64 BUFFER_SIZE = 4096;
  
  string str = {};
  
  str.data = NewArray<u8>(BUFFER_SIZE);
  
  va_list args;
  va_start(args, format);
  // Note that this *is* null-terminated for compatibility.
  str.count = (s64)vsnprintf((char*)str.data, (size_t)BUFFER_SIZE, format, args);
  va_end(args);

  return str;
}

string format_string (char* format, ...) { // #sprint
  constexpr s64 BUFFER_SIZE = 4096;
  
  string str = {};
  
  str.data = NewArray<u8>(BUFFER_SIZE);
  
  va_list args;
  va_start(args, format);
  // Note that this *is* null-terminated for compatibility.
  str.count = (s64)vsnprintf((char*)str.data, (size_t)BUFFER_SIZE, format, args);
  va_end(args);

  return str;
}

// #MemoryLeak #NoContext - memory will leak from this operation. 
string format_string_no_context (char* format, ...) {
  constexpr s64 BUFFER_SIZE = 4096;
  
  string str = {};
  
  str.data = (u8*)default_allocator_new(BUFFER_SIZE);
  
  va_list args;
  va_start(args, format);
  // Note that this *is* null-terminated for compatibility.
  str.count = (s64)vsnprintf((char*)str.data, (size_t)BUFFER_SIZE, format, args);
  va_end(args);

  return str;
}

force_inline String_Builder* new_string_builder (Arena_Reserve new_reserve) {
  return arena_array_new<u8>(1, new_reserve);
}

force_inline void append (String_Builder* sb, string s) {
  array_add(*sb, ArrayView<u8>(s.count, s.data));  
}

void append (String_Builder* sb, ArrayView<string> strings) {
  s64 combined_length = 0;
  for (s64 i = 0; i < strings.count; i += 1) {
    combined_length += strings[i].count;
  }
  
  s64 final_length = sb->count + combined_length;
  
  if (sb->allocated < final_length) {
    array_reserve(*sb, final_length);
  }
  
  for (s64 i = 0; i < strings.count; i += 1) {
    string s = strings[i];
    array_add(*sb, ArrayView<u8>(s.count, s.data));
  }
}

force_inline void append_no_add (String_Builder* sb, string s) {
  array_add(*sb, ArrayView<u8>(s.count, s.data));  
  sb->count -= s.count;
}

// Unfortunately this follows the printf format, which is annoying.
// I'd rather have something like fmt:: 
void print_to_builder_internal (String_Builder* sb, string format, va_list args) {
  s64 expected_final_count = max_array_size(*sb);// amount to reserve
  
  if (sb->allocated < expected_final_count) {
    array_reserve(*sb, expected_final_count);
  }

  s64 buffer_size = sb->allocated - sb->count; // available space
  u8* current_point = &sb->data[sb->count];  
  
  s64 print_count = (s64)vsnprintf((char*)current_point, (size_t)buffer_size, (char*)format.data, args);
  
  sb->count += print_count;
}

void print_to_builder (String_Builder* sb, string format, ...) {
  s64 expected_final_count = max_array_size(*sb);
  
  if (sb->allocated < expected_final_count) {
    array_reserve(*sb, expected_final_count);
  }

  s64 buffer_size = sb->allocated - sb->count; // available space
  u8* current_point = &sb->data[sb->count];  
  
  va_list args;
  va_start(args, format);
  s64 print_count = (s64)vsnprintf((char*)current_point, (size_t)buffer_size, (char*)format.data, args);
  va_end(args);
  
  sb->count += print_count;
}

string string_view (String_Builder* sb) {
  // should probably ensure final byte is null terminated...
  append_no_add(sb, "\0"); // doesn't increment sb.count
  return to_string(to_view(*sb)); 
}

// for when we want to keep the string builder around and recycle the memory.
internal force_inline void reset_string_builder (String_Builder* sb, bool keep_memory) {
  array_poison_range(*sb, 0, sb->count);
  if (keep_memory) {
    reset_keeping_memory(*sb);
  } else {
    array_reset(*sb);
  }
}

force_inline string builder_to_string (String_Builder* sb) {
  string final_string = copy_string(to_string(to_view(*sb)));
  
  // reset_string_builder(sb, false); // maybe this shouldn't be here! Too magical...
  
  return final_string;
}

internal force_inline void free_string_builder (String_Builder* sb) {
  arena_array_free(*sb);
}

char to_lower_ascii(char c) {
  if (c >= 'A' && c <= 'Z')
      c = c + ('a' - 'A');   // or c += 32;
  return c;
}

char to_upper_ascii(char c) {
  if (c >= 'a' && c <= 'z')
      c = c - ('a' - 'A');   // or c -= 32;
  return c;
}

// string to_lower_in_place (string s) { }
// Input must be ascii or utf8!
string to_lower_copy (string s_orig) {
  string s = copy_string(s_orig);
  for (s64 i = 0; i < s.count; i += 1) {
    s.data[i] = to_lower_ascii(s.data[i]);
  }
  
  return s;
}

#define format_cstring(fmt, ...) \
  (char*)format_string(fmt, ##__VA_ARGS__).data

bool is_any (u8 c, string chars) {
  for_each(i, chars) {
    if (chars.data[i] == c) return true;
  }
  
  return false;
}

string trim_right (string s, string chars, bool replace_with_zeros) {
  s64 count = s.count;
  
  for_each_reverse(i, s) {
    if (is_any(s.data[i], chars)) {
      if (replace_with_zeros) {
        s.data[i] = 0;
      }
      count -= 1;
    } else {
      break;
    }
  }

  return string_view(s, 0, count);
}
