#include "dowa.h"

#ifndef MAX_STR_BUFFER
  #define MAX_STR_BUFFER 1024
#endif

char *Dowa_String_Slice(char *from, size_t start, size_t end, Dowa_Arena *p_arena)
{
  char *buffer = p_arena == NULL ?
    malloc(sizeof(char) * end-start+1) : Dowa_Arena_Allocate(p_arena, end-start+1);
  if (!buffer)
    return NULL;

  for (int32 i = 0; i < (end - start); i++)
    buffer[i] = from[start + i];

  buffer[end - start] = '\0';
  return buffer;
}

char **Dowa_String_Split(char *from, char *token, int32 from_length, int32 token_length, Dowa_Arena *p_arena)
{ 
  if (!from)
    return NULL;

  int32 *token_pos_arr = NULL;
  for (int32 i = 0; i < from_length; i++)
  {
    if (from[i] == token[0])
    {
      int32 curr_token_pointer = 0;
      while (curr_token_pointer < token_length && (i + curr_token_pointer) < from_length)
      {
        if (from[i + curr_token_pointer] != token[curr_token_pointer])
          break;
        curr_token_pointer++;
      }

      if (curr_token_pointer == token_length)
        Dowa_Array_Push(token_pos_arr, i);
    }
  }

  char **splitted_strings = NULL;
  int32 start = 0;
  int32 num_tokens = Dowa_Array_Length(token_pos_arr);

  for (int32 i = 0; i <= num_tokens; i++)
  {
    int32 end = (i < num_tokens) ? token_pos_arr[i] : from_length;
    
    char *val = Dowa_String_Slice(from, start, end, p_arena); 
    
    if (p_arena)
      Dowa_Array_Push_Arena(splitted_strings, val, p_arena);
    else
      Dowa_Array_Push(splitted_strings, val);
    
    if (i < num_tokens)
      start = token_pos_arr[i] + token_length;
  }

  Dowa_Array_Free(token_pos_arr);
  return splitted_strings;
}

int32 Dowa_String_Pos_Find(const char *from, const char *value, const size_t from_length, const size_t value_length)
{
  if (value == NULL || from == NULL)
    return -1;

  for (int32 i = 0; i < from_length - value_length; i++)
  {
    if (from[i] == value[0])
    {
      int32 j = 0; 
      while (j < value_length && value[j] == from[i+j])
        j++;

      if (j == value_length)
        return i;
    }
  }
  return -1;
}

char *Dowa_String_Find(const char *from, const char *value, const size_t from_length, const size_t value_length)
{
  if (value == NULL || from == NULL)
    return NULL;

  for (int32 i = 0; i < from_length - value_length; i++)
  {
    if (from[i] == value[0])
    {
      int32 j = 0; 
      while (j < value_length && value[j] == from[i+j])
        j++;

      if (j == value_length)
        return &from[i];
    }
  }
  return NULL;
}

int32 Dowa_String_Pos_Find_Char(const char *from, int c, int32 from_length)
{
  if (!from || from_length == 0)
    return -1;

  for (int32 i = 0; i < from_length; i++)
  {
    if ((int)from[i] == c)
      return i;
  }
  return -1;
}

char *Dowa_String_Find_Char(const char *from, int c, int32 from_length)
{
  if (!from || from_length == 0)
    return NULL;

  for (int32 i = 0; i < from_length; i++)
  {
    if ((int)from[i] == c)
      return &from[i];
  }
  return NULL ;
}

char *Dowa_String_Copy_Arena(char *from, Dowa_Arena *p_arena)
{
  char *buffer = Dowa_Arena_Allocate(p_arena, sizeof(char*) * strlen(from) + 1);
  if (buffer)
    memcpy(buffer, from, strlen(from));
  buffer[strlen(from)] = '\0';
  return buffer;
}

char *Dowa_String_UUID(uint32 seed, void *buffer)
{
  char *res = buffer ? buffer : malloc(sizeof(char)*37);
  if (!res)
    return NULL;

  const char *POOL = "abcdef0123456789";
  int32 i = 0;
  while (i < 37)
  {
    if (i == 8 || i == 13 || i == 18 || i == 23)
    {
      res[i++] = '-';
      continue;
    }
    seed = Dowa_Math_Random_Uint32(seed);
    res[i++] = POOL[seed % 16];
  }
  res[36] = '\0';
  return res;
}

// --- JSON Parser --- //

static void json_skip_ws(const char *json, int32 *pos, int32 len)
{
  while (*pos < len && (json[*pos] == ' ' || json[*pos] == '\t' ||
         json[*pos] == '\n' || json[*pos] == '\r'))
    (*pos)++;
}

static char *json_parse_str(const char *json, int32 *pos, int32 len, Dowa_Arena *arena)
{
  if (*pos >= len || json[*pos] != '"')
    return NULL;

  (*pos)++;
  int32 start = *pos;

  while (*pos < len && json[*pos] != '"')
    (*pos)++;

  int32 slen = *pos - start;
  char *str = arena ? Dowa_Arena_Allocate(arena, slen + 1) : malloc(slen + 1);
  if (!str) return NULL;

  int32 j = 0;
  for (int32 i = start; i < *pos; i++)
  {
    if (json[i] == '\\' && i + 1 < *pos)
    {
      i++;
      switch (json[i])
      {
        case 'n':  str[j++] = '\n'; break;
        case 't':  str[j++] = '\t'; break;
        case 'r':  str[j++] = '\r'; break;
        case '\\': str[j++] = '\\'; break;
        case '"':  str[j++] = '"';  break;
        default:   str[j++] = json[i]; break;
      }
    }
    else
      str[j++] = json[i];
  }
  str[j] = '\0';

  if (*pos < len && json[*pos] == '"')
    (*pos)++;

  return str;
}

// Forward declaration for recursion
static Dowa_JSON_Value json_parse_val(const char *json, int32 *pos, int32 len, Dowa_Arena *arena);

static Dowa_JSON_Entry *json_parse_obj(const char *json, int32 *pos, int32 len, Dowa_Arena *arena)
{
  if (*pos >= len || json[*pos] != '{')
    return NULL;

  (*pos)++;
  Dowa_JSON_Entry *map = NULL;

  while (*pos < len)
  {
    json_skip_ws(json, pos, len);

    if (*pos >= len) break;
    if (json[*pos] == '}') { (*pos)++; break; }
    if (json[*pos] == ',') { (*pos)++; continue; }

    char *key = json_parse_str(json, pos, len, arena);
    if (!key) break;

    json_skip_ws(json, pos, len);
    if (*pos >= len || json[*pos] != ':') break;
    (*pos)++;

    Dowa_JSON_Value val = json_parse_val(json, pos, len, arena);

    if (arena)
      Dowa_HashMap_Push_Arena(map, key, val, arena);
    else
      Dowa_HashMap_Push(map, key, val);
  }

  return map;
}

static Dowa_JSON_Value *json_parse_arr(const char *json, int32 *pos, int32 len, Dowa_Arena *arena)
{
  if (*pos >= len || json[*pos] != '[')
    return NULL;

  (*pos)++;
  Dowa_JSON_Value *arr = NULL;

  while (*pos < len)
  {
    json_skip_ws(json, pos, len);

    if (*pos >= len) break;
    if (json[*pos] == ']') { (*pos)++; break; }
    if (json[*pos] == ',') { (*pos)++; continue; }

    Dowa_JSON_Value val = json_parse_val(json, pos, len, arena);

    if (arena)
      Dowa_Array_Push_Arena(arr, val, arena);
    else
      Dowa_Array_Push(arr, val);
  }

  return arr;
}

static Dowa_JSON_Value json_parse_val(const char *json, int32 *pos, int32 len, Dowa_Arena *arena)
{
  Dowa_JSON_Value val = {0};
  json_skip_ws(json, pos, len);

  if (*pos >= len)
  {
    val.type = DOWA_JSON_NULL;
    return val;
  }

  char c = json[*pos];

  // String
  if (c == '"')
  {
    val.type = DOWA_JSON_STRING;
    val.str_val = json_parse_str(json, pos, len, arena);
    return val;
  }

  // Object
  if (c == '{')
  {
    val.type = DOWA_JSON_OBJECT;
    val.object_val = json_parse_obj(json, pos, len, arena);
    return val;
  }

  // Array
  if (c == '[')
  {
    val.type = DOWA_JSON_ARRAY;
    val.array_val = json_parse_arr(json, pos, len, arena);
    return val;
  }

  // Number
  if (c == '-' || (c >= '0' && c <= '9'))
  {
    val.type = DOWA_JSON_NUMBER;
    int32 start = *pos;
    while (*pos < len && (json[*pos] == '-' || json[*pos] == '+' ||
           json[*pos] == '.' || json[*pos] == 'e' || json[*pos] == 'E' ||
           (json[*pos] >= '0' && json[*pos] <= '9')))
      (*pos)++;

    char tmp[64];
    int32 nlen = *pos - start;
    if (nlen >= 64) nlen = 63;
    memcpy(tmp, &json[start], nlen);
    tmp[nlen] = '\0';
    val.num_val = atof(tmp);
    return val;
  }

  // true
  if (c == 't' && *pos + 4 <= len && memcmp(&json[*pos], "true", 4) == 0)
  {
    val.type = DOWA_JSON_BOOL;
    val.bool_val = TRUE;
    *pos += 4;
    return val;
  }

  // false
  if (c == 'f' && *pos + 5 <= len && memcmp(&json[*pos], "false", 5) == 0)
  {
    val.type = DOWA_JSON_BOOL;
    val.bool_val = FALSE;
    *pos += 5;
    return val;
  }

  // null
  if (c == 'n' && *pos + 4 <= len && memcmp(&json[*pos], "null", 4) == 0)
  {
    val.type = DOWA_JSON_NULL;
    *pos += 4;
    return val;
  }

  val.type = DOWA_JSON_NULL;
  return val;
}

Dowa_JSON_Value Dowa_JSON_Parse(const char *json, int32 length, Dowa_Arena *p_arena)
{
  Dowa_JSON_Value val = {0};
  if (!json || length <= 0)
  {
    val.type = DOWA_JSON_NULL;
    return val;
  }

  int32 pos = 0;
  return json_parse_val(json, &pos, length, p_arena);
}

Dowa_JSON_Value *Dowa_JSON_Get(Dowa_JSON_Entry *map, const char *key)
{
  if (!map || !key)
    return NULL;

  void *kv = Dowa_HashMap_Get_Ptr(map, key);
  if (!kv)
    return NULL;

  return &((Dowa_JSON_Entry *)kv)->value;
}

char *Dowa_JSON_Get_String(Dowa_JSON_Entry *map, const char *key)
{
  Dowa_JSON_Value *val = Dowa_JSON_Get(map, key);
  if (!val || val->type != DOWA_JSON_STRING)
    return NULL;
  return val->str_val;
}

double Dowa_JSON_Get_Number(Dowa_JSON_Entry *map, const char *key)
{
  Dowa_JSON_Value *val = Dowa_JSON_Get(map, key);
  if (!val || val->type != DOWA_JSON_NUMBER)
    return 0.0;
  return val->num_val;
}

boolean Dowa_JSON_Get_Bool(Dowa_JSON_Entry *map, const char *key)
{
  Dowa_JSON_Value *val = Dowa_JSON_Get(map, key);
  if (!val || val->type != DOWA_JSON_BOOL)
    return FALSE;
  return val->bool_val;
}
