#include <stdarg.h>

//~ Helpers 
root_function U64
calculate_string_C_string_length(char *cstr) {
  /*U64 length = 0;
    for(char* p = cstr; p != '\0'; p += 1) {
        length += 1;
    }
    return length;*/
  // A cool way to write this is this while loop
  U64 length = 0;
  for (/* empty here means just while loop*/;
    /* While we're not at null terminator */ cstr[length];
    /* Increment */ length += 1);
    
  // Then we're actually done and just return length;
  return length;
}


//~ Constructors 
root_function String8
str8(U8 *str, U64 size) {
  String8 string;
  string.str = str;
  string.size = size;
  return string;
}

root_function String8
str8_range(U8 *first, U8 *one_past_last) {
  String8 string;
  string.str = first;
  string.size = (U64)(one_past_last - first);
  return string;
}

//~ Substrings 
//- String8 
root_function String8
str8_substr(String8 string, U64 first, U64 one_past_last) {
  // We get a substring from the range one_past_last - first
  U64 min = first;
  U64 max = one_past_last;
  // Logic to prepare for swithing input to a range instead of first/one_past_last
  if (max > string.size) {
    max = string.size;
  }
  if (min > string.size) {
    min = string.size;
  }
  if (min > max) {
    U64 swap = min;
    min = max;
    max = swap;
  }
  string.size = max - min;
  string.str += min; // Increment the pointer of the String8 to the min.
  return string;
}

root_function String8 str8_prefix(String8 string, U64 size) { return str8_substr(string, 0, size); }
root_function String8 str8_chop(String8 string, U64 amount) { return str8_substr(string, 0, string.size-amount); }
root_function String8 str8_suffix(String8 string, U64 size) { return str8_substr(string, string.size-size, string.size); }
root_function String8 str8_skip(String8 string, U64 amount) { return str8_substr(string, amount, string.size); }

// String16
// String32

//~ Lists 
//- String8 
root_function void
str8_list_push_node(String8List *list, String8Node *n) {
  QueuePush(list->first, list->last, n);
  list->node_count += 1;
  list->total_size += n->string.size;
}

root_function void
str8_list_push_node_front(String8List *list, String8Node *n) {
  QueuePushFront(list->first, list->last, n);
  list->node_count += 1;
  list->total_size += n->string.size;
}

// Wrapper that pushes the memory for a node onto the arena, and then puts the node in the linked list (in the back).
root_function void
str8_list_push(Arena *arena, String8List *list, String8 string) {
  String8Node *n = PushArray(arena, String8Node, 1);
  n->string = string;
  str8_list_push_node(list, n);
}

// Wrapper that pushes the memory for a node onto the arena, and then puts the node in the linked list (in the front).
root_function void
str8_list_push_front(Arena *arena, String8List *list, String8 string) {
  String8Node *n = PushArray(arena, String8Node, 1);
  n->string = string;
  str8_list_push_node_front(list, n);
}

root_function void
str8_list_concat(String8List *list, String8List *to_push) {
  // If to_push is a non-zero length String8List,
  // we add it to the input list.
  if (to_push->first) {
    list->node_count += to_push->node_count;
    list->total_size += to_push->total_size;
    // If the input list's last element is null
    // we had a zero length input list, and we just set the input list equal to to_push
    if (list->last == 0) {
      *list = *to_push;
    } else {
      // Else we append the to_push list to the input list.
      list->last->next = to_push->first;
      list->last = to_push->last;
    }
  }
  // TODO(anton): Why are we zeroing the memory here?
  MemoryZero(to_push, sizeof(*to_push));
  //LOG_NOT_IMPLEMENTED;
}

// TODO(anton): Understand this function and write comments about the logic.
root_function String8List
str8_split(Arena *arena, String8 string, int split_count, String8 *splits) {
    
  String8List list = { 0 };
    
  U64 split_start = 0;
  for (U64 i = 0; i < string.size; i += 1)
  {
    B32 was_split = 0;
    for (int split_idx = 0; split_idx < split_count; split_idx += 1)
    {
      B32 match = 0;
      if (i + splits[split_idx].size <= string.size)
      {
        match = 1;
        for (U64 split_i = 0; split_i < splits[split_idx].size && i + split_i < string.size; split_i += 1)
        {
          if (splits[split_idx].str[split_i] != string.str[i + split_i])
          {
            match = 0;
            break;
                        
          }
        }
      }
      if (match)
      {
        String8 split_string = str8(string.str + split_start, i - split_start);
        str8_list_push(arena, &list, split_string);
        split_start = i + splits[split_idx].size;
        i += splits[split_idx].size - 1;
        was_split = 1;
        break;
      }
    }
        
    if (was_split == 0 && i == string.size - 1)
    {
      String8 split_string = str8(string.str + split_start, i + 1 - split_start);
      str8_list_push(arena, &list, split_string);
      break;
    }
  }
    
  return list;
}

// TODO(anton): Understand this function and write good comments explaining.
root_function String8
str8_list_join(Arena *arena, String8List list, StringJoin *optional_params) {
  // rjf: setup join parameters
  StringJoin join = { 0 };
  if (optional_params != 0)
  {
    MemoryCopy(&join, optional_params, sizeof(join));
  }
    
  // rjf: calculate size & allocate
  U64 sep_count = 0;
  if (list.node_count > 1)
  {
    sep_count = list.node_count - 1;
  }
  String8 result = { 0 };
  result.size = (list.total_size + join.pre.size +
    sep_count*join.sep.size + join.post.size);
  result.str = PushArray(arena, U8, result.size + 1);
    
  // rjf: fill
  U8 *ptr = result.str;
  MemoryCopy(ptr, join.pre.str, join.pre.size);
  ptr += join.pre.size;
  for (String8Node *node = list.first; node; node = node->next)
  {
    MemoryCopy(ptr, node->string.str, node->string.size);
    ptr += node->string.size;
    if (node != list.last)
    {
      MemoryCopy(ptr, join.sep.str, join.sep.size);
      ptr += join.sep.size;
    }
  }
  MemoryCopy(ptr, join.post.str, join.post.size);
  ptr += join.post.size;
    
  // rjf: add null
  result.str[result.size] = 0;
    
  return result;
}

//~ Allocation and format strings
root_function String8
str8_copy(Arena *arena, String8 string) {
  String8 result;
  result.size = string.size;
  result.str = PushArray(arena, U8, string.size + 1);
  MemoryCopy(result.str, string.str, string.size);
  result.str[string.size] = 0; // TODO(anton): What is this?
  return result;
}

root_function String8
str8_pushfv(Arena *arena, char *fmt, va_list args) {
    
  // Might need to try a second time so copy args
  va_list args2;
  va_copy(args2, args);
    
  // Try to build string using 1024 bytes
  U64 buffer_size = 1024;
  U8 *buffer = PushArray(arena, U8, buffer_size);
  // The vsnprintf takes the bundled arguments list args and puts the format strings it into buffer.
  U64 actual_size = vsnprintf((char*)buffer, buffer_size, fmt, args);
    
  String8 result = { 0 };
  if (actual_size < buffer_size) {
    // The first try worked and we can pop whatever wasn't used from the buffer
    // and get our resulting string.
    m_arena_pop(arena, buffer_size - actual_size - 1); // -1 because of null terminated in char *fmt?
    result = str8(buffer, actual_size);
  } else {
    // If first try failed we try again with better size
    m_arena_pop(arena, buffer_size);
    U8 *fixed_buffer = PushArray(arena, U8, actual_size + 1);
    U64 final_size = vsnprintf((char*)fixed_buffer, actual_size + 1, fmt, args2);
    result = str8(fixed_buffer, final_size);
  }
    
  // va_end to help compiler do its thing.
  va_end(args2);
    
  return result;
}

root_function String8
str8_pushf(Arena *arena, char*fmt, ...) {
  String8 result = { 0 };
  va_list args;
  va_start(args, fmt);
  result = str8_pushfv(arena, fmt, args);
  va_end(args);
  return result;
}

root_function void
str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  String8 string = str8_pushfv(arena, fmt, args);
  va_end(args);
  str8_list_push(arena, list, string);
}

//~ Unicode conversions

#define bitmask1 0x01 // Mask first bit
#define bitmask2 0x03 // Mask 2 bits,   3 = 0x03 = 0000 0011 = 2^2 - 1
#define bitmask3 0x07 // Mask 3 bits,   7 = 0x07 = 0000 0111 = 2^3 - 1
#define bitmask4 0x0F // Mask 4 bits,  15 = 0x0F = 0000 1111 = 2^4 - 1
#define bitmask5 0x1F // Mask 5 bits,  31 = 0x1F = 0001 1111 = 2^5 - 1
#define bitmask6 0x3F // Mask 6 bits,  63 = 0x3F = 0011 1111 = 2^6 - 1
#define bitmask7 0x7F // Mask 7 bits, 127 = 0x7F = 0111 1111 = 2^7 - 1
#define bitmask8 0xFF // Mask 8 bits, 255 = 0xFF = 1111 1111 = 2^8 - 1

// Note that we're only decoding valid cases and not handling invalid/errors
root_function DecodeCodepoint
decode_from_utf8(U8 *str, U64 max) {
  // This table will give us wheter or not we have a codepoint encoded by one, two, three or four bytes.
  local_persist U8 utf8_class[] = {
    1, 1, 1, 1,
    1, 1, 1, 1,
    1, 1, 1, 1,
    1, 1, 1, 1,
    0, 0, 0, 0,
    0, 0, 0, 0,
    2, 2, 2, 2,
    3, 3,
    4,
    5 // error
  };
    
  DecodeCodepoint result = { ~((U32)0), 1 };
  // We'll shift out the lowest 3 bits since those are not important in the decoding.
  // This is the byte >> 3 into the static array.
  U8 byte = str[0];
  U8 byte_class = utf8_class[byte >> 3];
    
  switch (byte_class) {
    case 1: {
      // Just a single byte encoding.
      result.codepoint = byte; // Actually the 8th bit must be zero for valid UTF encoding.
    } break;
        
    case 2: {
      if (2 <= max) {
        U8 cont_byte = str[1];
        // Check the second byte
        if (utf8_class[cont_byte >> 3] == 0) {
          // codepoint is 32-bits
          // The case with two bytes has byte1 110xxxxx, ie encoded in the last 5 bits.
          // and byte2 is 10xxxxxx, encoded in the last 6 bits. So we use mask5 on first byte, shift by 6,
          // and mask6 on second byte.
          result.codepoint = (byte & bitmask5) << 6;
          result.codepoint |= (cont_byte & bitmask6);
          result.advance = 2;
        }
      }
    } break;
        
    case 3: {
      if (3 <= max) {
        // encoded by 3 bytes, so we have two more cont_bytes.
        U8 cont_byte[2] = { str[1], str[2] };
        if (utf8_class[cont_byte[0] >> 3] == 0 && utf8_class[cont_byte[1] >> 3] == 0) {
          // For this case the first byte is encoded in the last 4 bits, 1110xxxx
          // The second and third is 10xxxxxx (last 6 bits)
          result.codepoint = (byte & bitmask4) << 12;
          result.codepoint |= (cont_byte[0] & bitmask6) << 6;
          result.codepoint |= (cont_byte[1] & bitmask6);
          result.advance = 3;
        }
      }
    } break;
        
    case 4: {
      if (4 <= max) {
        U8 cont_byte[3] = { str[1], str[2], str[3] };
        if (utf8_class[cont_byte[0] >> 3] == 0 &&
          utf8_class[cont_byte[1] >> 3] == 0 &&
          utf8_class[cont_byte[2] >> 3] == 0) {
          // Here first byte is encoded in last 3 bits, and byte 2,3,4 are encoded in last 6 bits.
          // Thus we shift the first byte by 3*6 = 18 bits into the 32 bit codepoint;
          result.codepoint = (byte & bitmask3) << 18;
          result.codepoint |= (cont_byte[0] & bitmask6) << 12;
          result.codepoint |= (cont_byte[1] & bitmask6) << 6;
          result.codepoint |= (cont_byte[2] & bitmask6);
          result.advance = 4;
        }
      }
    } break;
  }
    
  return result;
}

// Encode function
root_function U32
utf8_from_codepoint(U8* out, U32 codepoint) {
  U8 bit8 = 0x80;
  U32 advance = 0;
    
  if (codepoint <= bitmask7 /* 0111 1111 */) {
    // We know that the whole encoding is in the last 7 bits, so it's a 1 byte encoding
    out[0] = (U8)codepoint;
    advance = 1;
        
  } else if (codepoint <= 0x07FF /*0000 0111 1111 1111*/) {
    // The case with two bytes has byte1 110xxxxx, ie encoded in the last 5 bits.
    // and byte2 is 10xxxxxx, encoded in the last 6 bits. 
    out[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5);
    out[1] = bit8 | (codepoint & bitmask6);
    advance = 2;
        
  } else if (codepoint <= 0xFFFF /* 1111 1111 1111 1111 */) {
    // For this case the first byte is encoded in the last 4 bits, 1110xxxx
    // The second and third is 10xxxxxx (last 6 bits)
    out[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4);
    out[1] = bit8 | ((codepoint >> 6) & bitmask6);
    out[2] = bit8 | ((codepoint) & bitmask6);
    advance = 3;
        
  } else if (codepoint <= 0x10FFFF /*0001 0000 1111 1111 1111 1111 */) {
    // Here first byte is encoded in last 3 bits, and byte 2,3,4 are encoded in last 6 bits.
    // Thus we shift the first byte by 3*6 = 18 bits into the 32 bit codepoint;
    out[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3);
    out[1] = bit8 | ((codepoint >> 12) & bitmask6);
    out[2] = bit8 | ((codepoint >> 6) & bitmask6);
    out[3] = bit8 | ((codepoint) & bitmask6);
    advance = 4;
  } else {
    out[0] = '?'; // ERrror?
    advance = 1;
  }
    
  return advance;
}

root_function DecodeCodepoint decode_from_utf16(U16 *str, U64 max) {
  DecodeCodepoint result = { ~((U32)0), 1 };
  result.codepoint = str[0];
  result.advance = 1;
  // Usually codepoints fit into a single 16 bit chunk.
  // But when we're not in the ranges 0x0000 to 0xD7FF and 0xE000 to 0xFFFF,
  // we need two 16 bit stores.
  // So what we have in str[0] = W1 is the "high surrogate", and
  // str[1] = W2 is the "low surrogate". We then get the codepoint U = U' + 0x10000,
  // where U' is a 20-bit number with the 10 lower bits from W1 in the high bits, and 10 lower bits of W2 in the lower.
  if (max > 1) {
    U16 w1 = str[0];
    U16 w2 = str[1];
    if (0xD800 <= w1 && w1 < 0xDC00 && 0xDC00 <= w2 && w2 < 0xE000) {
      // Get W1 ten bits
      U16 y = w1 - 0xD800;
      U16 x = w2 - 0xDC00;
      U32 uprim = (y << 10) | x;
      result.codepoint = uprim + 0x10000;
      result.advance = 2;
    }
  }
  return result;
}

root_function U32 utf16_from_codepoint(U16* out, U32 codepoint) {
    
  U32 advance = 1;
  if (codepoint == ~((U32)0)) {
    // Error?
    out[0] = (U16)'?';
  } else if (codepoint < 0x10000) {
    // single 16 bit code unit
    out[0] = (U16)codepoint;
  } else {
    // store 20 bits in uprim
    U32 uprim = codepoint - 0x10000;
    // create W1
    out[0] = 0xD800 + (uprim >> 10);
    // 0x03FF = bitmask for 10 lowest bits
    // create W2
    out[1] = 0xDC00 + (uprim & 0x03FF);
    advance = 2;
  }
  return advance;
}

// TODO(anton): understand this and write comments on steps
root_function String8
str8_from16(Arena *arena, String16 string) {
  U64 cap = string.size*3;
  U8 *str = PushArray(arena, U8, cap + 1);
  U16 *ptr = string.str;
  U16 *one_past_last = ptr + string.size;
  U64 size = 0;
  DecodeCodepoint consume;
  for (; ptr < one_past_last;) {
    consume = decode_from_utf16(ptr, one_past_last - ptr);
    ptr += consume.advance;
    size += utf8_from_codepoint(str + size, consume.codepoint);
  }
  str[size] = 0;
  m_arena_pop(arena, cap - size);
  return str8(str, size);
}

root_function String8
str8_from32(Arena *arena, String32 string) {
    
}

// TODO(anton): understand this and write comments on steps
root_function String16
str16_from8(Arena* arena, String8 string) {
  U64 cap = string.size*2;
  U16 *str = PushArray(arena, U16, cap + 1);
  U8 *ptr = string.str;
  U8 *one_past_last = ptr + string.size;
  U64 size = 0;
  DecodeCodepoint consume;
  for (; ptr < one_past_last;) {
    consume = decode_from_utf8(ptr, one_past_last - ptr);
    ptr += consume.advance;
    size += utf16_from_codepoint(str + size, consume.codepoint);
  }
  str[size] = 0;
  m_arena_pop(arena, 2*(cap - size));
  String16 result;
  result.str = str;
  result.size = size;
  return result;
}

root_function String32
str32_from8(Arena *arena, String8 string) {
    
}
