/*
Copyright (c) 2014. The YARA Authors. All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yara/error.h>
#include <yara/exec.h>
#include <yara/globals.h>
#include <yara/mem.h>
#include <yara/object.h>
#include <yara/strutils.h>
#include <yara/utils.h>

////////////////////////////////////////////////////////////////////////////////
// Creates a new object with the given type and identifier. If a parent is
// specified the new object is owned by the parent and it will be destroyed when
// the parent is destroyed. You must not call yr_object_destroy on an objected
// that has a parent, you should destroy the parent instead.
//
int yr_object_create(
    int8_t type,
    const char* identifier,
    YR_OBJECT* parent,
    YR_OBJECT** object)
{
  YR_OBJECT* obj;
  size_t object_size = 0;

  assert(parent != NULL || object != NULL);
  assert(identifier != NULL);

  switch (type)
  {
  case OBJECT_TYPE_STRUCTURE:
    object_size = sizeof(YR_OBJECT_STRUCTURE);
    break;
  case OBJECT_TYPE_ARRAY:
    object_size = sizeof(YR_OBJECT_ARRAY);
    break;
  case OBJECT_TYPE_DICTIONARY:
    object_size = sizeof(YR_OBJECT_DICTIONARY);
    break;
  case OBJECT_TYPE_INTEGER:
    object_size = sizeof(YR_OBJECT);
    break;
  case OBJECT_TYPE_FLOAT:
    object_size = sizeof(YR_OBJECT);
    break;
  case OBJECT_TYPE_STRING:
    object_size = sizeof(YR_OBJECT);
    break;
  case OBJECT_TYPE_FUNCTION:
    object_size = sizeof(YR_OBJECT_FUNCTION);
    break;
  default:
    assert(false);
  }

  obj = (YR_OBJECT*) yr_malloc(object_size);

  if (obj == NULL)
    return ERROR_INSUFFICIENT_MEMORY;

  obj->type = type;
  obj->identifier = yr_strdup(identifier);
  obj->parent = parent;
  obj->data = NULL;

  switch (type)
  {
  case OBJECT_TYPE_INTEGER:
    obj->value.i = YR_UNDEFINED;
    break;
  case OBJECT_TYPE_FLOAT:
    obj->value.d = NAN;
    break;
  case OBJECT_TYPE_STRING:
    obj->value.ss = NULL;
    break;
  case OBJECT_TYPE_STRUCTURE:
    object_as_structure(obj)->members = NULL;
    break;
  case OBJECT_TYPE_ARRAY:
    object_as_array(obj)->items = NULL;
    object_as_array(obj)->prototype_item = NULL;
    break;
  case OBJECT_TYPE_DICTIONARY:
    object_as_dictionary(obj)->items = NULL;
    object_as_dictionary(obj)->prototype_item = NULL;
    break;
  case OBJECT_TYPE_FUNCTION:
    object_as_function(obj)->return_obj = NULL;
    for (int i = 0; i < YR_MAX_OVERLOADED_FUNCTIONS; i++)
    {
      object_as_function(obj)->prototypes[i].arguments_fmt = NULL;
      object_as_function(obj)->prototypes[i].code = NULL;
    }
    break;
  }

  if (obj->identifier == NULL)
  {
    yr_free(obj);
    return ERROR_INSUFFICIENT_MEMORY;
  }

  if (parent != NULL)
  {
    assert(
        parent->type == OBJECT_TYPE_STRUCTURE ||
        parent->type == OBJECT_TYPE_ARRAY ||
        parent->type == OBJECT_TYPE_DICTIONARY ||
        parent->type == OBJECT_TYPE_FUNCTION);

    // Objects with a parent take the canary from it.
    obj->canary = parent->canary;

    switch (parent->type)
    {
    case OBJECT_TYPE_STRUCTURE:
      FAIL_ON_ERROR_WITH_CLEANUP(yr_object_structure_set_member(parent, obj), {
        yr_free((void*) obj->identifier);
        yr_free(obj);
      });
      break;

    case OBJECT_TYPE_ARRAY:
      object_as_array(parent)->prototype_item = obj;
      break;

    case OBJECT_TYPE_DICTIONARY:
      object_as_dictionary(parent)->prototype_item = obj;
      break;

    case OBJECT_TYPE_FUNCTION:
      object_as_function(parent)->return_obj = obj;
      break;
    }
  }

  if (object != NULL)
    *object = obj;

  return ERROR_SUCCESS;
}

void yr_object_set_canary(YR_OBJECT* object, int canary)
{
  object->canary = canary;
}

int yr_object_function_create(
    const char* identifier,
    const char* arguments_fmt,
    const char* return_fmt,
    YR_MODULE_FUNC code,
    YR_OBJECT* parent,
    YR_OBJECT** function)
{
  YR_OBJECT* return_obj;
  YR_OBJECT* o = NULL;
  YR_OBJECT_FUNCTION* f = NULL;

  int8_t return_type;

  // The parent of a function must be a structure.
  assert(parent != NULL && parent->type == OBJECT_TYPE_STRUCTURE);

  switch (*return_fmt)
  {
  case 'i':
    return_type = OBJECT_TYPE_INTEGER;
    break;
  case 's':
    return_type = OBJECT_TYPE_STRING;
    break;
  case 'f':
    return_type = OBJECT_TYPE_FLOAT;
    break;
  default:
    return ERROR_INVALID_FORMAT;
  }

  // Try to find if the structure already has a function
  // with that name. In that case this is a function overload.
  f = object_as_function(yr_object_lookup_field(parent, identifier));

  // Overloaded functions must have the same return type.
  if (f != NULL && return_type != f->return_obj->type)
    return ERROR_WRONG_RETURN_TYPE;

  if (f == NULL)  // Function doesn't exist yet
  {
    FAIL_ON_ERROR(
        yr_object_create(OBJECT_TYPE_FUNCTION, identifier, parent, &o));

    // In case of failure while creating return_obj we don't need to free the
    // previously created "o" object, as it is already associated with its
    // parent and will be destroyed when the parent is destroyed.
    FAIL_ON_ERROR(yr_object_create(return_type, "result", o, &return_obj));

    f = object_as_function(o);
  }

  for (int i = 0; i < YR_MAX_OVERLOADED_FUNCTIONS; i++)
  {
    if (f->prototypes[i].arguments_fmt == NULL)
    {
      f->prototypes[i].arguments_fmt = arguments_fmt;
      f->prototypes[i].code = code;

      break;
    }
  }

  if (function != NULL)
    *function = (YR_OBJECT*) f;

  return ERROR_SUCCESS;
}

int yr_object_from_external_variable(
    YR_EXTERNAL_VARIABLE* external,
    YR_OBJECT** object)
{
  YR_OBJECT* obj;
  int result;
  uint8_t obj_type = 0;

  switch (external->type)
  {
  case EXTERNAL_VARIABLE_TYPE_INTEGER:
  case EXTERNAL_VARIABLE_TYPE_BOOLEAN:
    obj_type = OBJECT_TYPE_INTEGER;
    break;

  case EXTERNAL_VARIABLE_TYPE_FLOAT:
    obj_type = OBJECT_TYPE_FLOAT;
    break;

  case EXTERNAL_VARIABLE_TYPE_STRING:
  case EXTERNAL_VARIABLE_TYPE_MALLOC_STRING:
    obj_type = OBJECT_TYPE_STRING;
    break;

  default:
    assert(false);
  }

  result = yr_object_create(obj_type, external->identifier, NULL, &obj);

  if (result == ERROR_SUCCESS)
  {
    switch (external->type)
    {
    case EXTERNAL_VARIABLE_TYPE_INTEGER:
    case EXTERNAL_VARIABLE_TYPE_BOOLEAN:
      result = yr_object_set_integer(external->value.i, obj, NULL);
      break;

    case EXTERNAL_VARIABLE_TYPE_FLOAT:
      result = yr_object_set_float(external->value.f, obj, NULL);
      break;

    case EXTERNAL_VARIABLE_TYPE_STRING:
    case EXTERNAL_VARIABLE_TYPE_MALLOC_STRING:
      result = yr_object_set_string(
          external->value.s, strlen(external->value.s), obj, NULL);
      break;
    }

    if (result == ERROR_SUCCESS)
    {
      *object = obj;
    }
    else
    {
      yr_object_destroy(obj);
    }
  }

  return result;
}

////////////////////////////////////////////////////////////////////////////////
// Destroy an objects, and any other object that is a child of it. For example,
// destroying a struct will destroy all its members.
//
void yr_object_destroy(YR_OBJECT* object)
{
  YR_STRUCTURE_MEMBER* member;
  YR_STRUCTURE_MEMBER* next_member;
  YR_ARRAY_ITEMS* array_items;
  YR_DICTIONARY_ITEMS* dict_items;

  if (object == NULL)
    return;

  switch (object->type)
  {
  case OBJECT_TYPE_STRUCTURE:
    member = object_as_structure(object)->members;

    while (member != NULL)
    {
      next_member = member->next;
      yr_object_destroy(member->object);
      yr_free(member);
      member = next_member;
    }
    break;

  case OBJECT_TYPE_STRING:
    if (object->value.ss != NULL)
      yr_free(object->value.ss);
    break;

  case OBJECT_TYPE_ARRAY:
    if (object_as_array(object)->prototype_item != NULL)
      yr_object_destroy(object_as_array(object)->prototype_item);

    array_items = object_as_array(object)->items;

    if (array_items != NULL)
    {
      for (int i = 0; i < array_items->length; i++)
        if (array_items->objects[i] != NULL)
          yr_object_destroy(array_items->objects[i]);
    }

    yr_free(array_items);
    break;

  case OBJECT_TYPE_DICTIONARY:
    if (object_as_dictionary(object)->prototype_item != NULL)
      yr_object_destroy(object_as_dictionary(object)->prototype_item);

    dict_items = object_as_dictionary(object)->items;

    if (dict_items != NULL)
    {
      for (int i = 0; i < dict_items->used; i++)
      {
        if (dict_items->objects[i].key != NULL)
          yr_free(dict_items->objects[i].key);

        if (dict_items->objects[i].obj != NULL)
          yr_object_destroy(dict_items->objects[i].obj);
      }
    }

    yr_free(dict_items);
    break;

  case OBJECT_TYPE_FUNCTION:
    yr_object_destroy(object_as_function(object)->return_obj);
    break;
  }

  yr_free((void*) object->identifier);
  yr_free(object);
}

YR_OBJECT* yr_object_lookup_field(YR_OBJECT* object, const char* field_name)
{
  YR_STRUCTURE_MEMBER* member;

  assert(object != NULL);
  assert(object->type == OBJECT_TYPE_STRUCTURE);

  member = object_as_structure(object)->members;

  while (member != NULL)
  {
    if (strcmp(member->object->identifier, field_name) == 0)
      return member->object;

    member = member->next;
  }

  return NULL;
}

static YR_OBJECT* _yr_object_lookup(
    YR_OBJECT* object,
    int flags,
    const char* pattern,
    va_list args)
{
  YR_OBJECT* obj = object;

  const char* p = pattern;
  const char* key = NULL;

  char str[256];

  int i;
  int index = -1;

  while (obj != NULL)
  {
    i = 0;

    while (*p != '\0' && *p != '.' && *p != '[' && i < sizeof(str) - 1)
    {
      str[i++] = *p++;
    }

    str[i] = '\0';

    if (obj->type != OBJECT_TYPE_STRUCTURE)
      return NULL;

    obj = yr_object_lookup_field(obj, str);

    if (obj == NULL)
      return NULL;

    if (*p == '[')
    {
      p++;

      if (*p == '%')
      {
        p++;

        switch (*p++)
        {
        case 'i':
          index = va_arg(args, int);
          break;
        case 's':
          key = va_arg(args, const char*);
          break;

        default:
          return NULL;
        }
      }
      else if (*p >= '0' && *p <= '9')
      {
        index = (int) strtol(p, (char**) &p, 10);
      }
      else if (*p == '"')
      {
        i = 0;
        p++;  // skip the opening quotation mark

        while (*p != '"' && *p != '\0' && i < sizeof(str) - 1) str[i++] = *p++;

        str[i] = '\0';
        p++;  // skip the closing quotation mark
        key = str;
      }
      else
      {
        return NULL;
      }

      assert(*p == ']');
      p++;
      assert(*p == '.' || *p == '\0');

      switch (obj->type)
      {
      case OBJECT_TYPE_ARRAY:
        assert(index != -1);
        obj = yr_object_array_get_item(obj, flags, index);
        break;

      case OBJECT_TYPE_DICTIONARY:
        assert(key != NULL);
        obj = yr_object_dict_get_item(obj, flags, key);
        break;
      }
    }

    if (*p == '\0')
      break;

    p++;
  }

  return obj;
}

YR_OBJECT* yr_object_lookup(
    YR_OBJECT* object,
    int flags,
    const char* pattern,
    ...)
{
  YR_OBJECT* result;

  va_list args;
  va_start(args, pattern);

  result = _yr_object_lookup(object, flags, pattern, args);

  va_end(args);

  return result;
}

int yr_object_copy(YR_OBJECT* object, YR_OBJECT** object_copy)
{
  YR_OBJECT* copy;
  YR_OBJECT* o;

  YR_STRUCTURE_MEMBER* structure_member;

  *object_copy = NULL;

  FAIL_ON_ERROR(
      yr_object_create(object->type, object->identifier, NULL, &copy));

  copy->canary = object->canary;

  switch (object->type)
  {
  case OBJECT_TYPE_INTEGER:
    copy->value.i = object->value.i;
    break;

  case OBJECT_TYPE_FLOAT:
    copy->value.d = object->value.d;
    break;

  case OBJECT_TYPE_STRING:

    if (object->value.ss != NULL)
      copy->value.ss = ss_dup(object->value.ss);
    else
      copy->value.ss = NULL;

    break;

  case OBJECT_TYPE_FUNCTION:

    FAIL_ON_ERROR_WITH_CLEANUP(
        yr_object_copy(
            object_as_function(object)->return_obj,
            &object_as_function(copy)->return_obj),
        // cleanup
        yr_object_destroy(copy));

    for (int i = 0; i < YR_MAX_OVERLOADED_FUNCTIONS; i++)
      object_as_function(copy)->prototypes[i] =
          object_as_function(object)->prototypes[i];

    break;

  case OBJECT_TYPE_STRUCTURE:

    structure_member = object_as_structure(object)->members;

    while (structure_member != NULL)
    {
      FAIL_ON_ERROR_WITH_CLEANUP(
          yr_object_copy(structure_member->object, &o),
          yr_object_destroy(copy));

      FAIL_ON_ERROR_WITH_CLEANUP(yr_object_structure_set_member(copy, o),
                                 // cleanup
                                 yr_free(o);
                                 yr_object_destroy(copy));

      structure_member = structure_member->next;
    }

    break;

  case OBJECT_TYPE_ARRAY:

    FAIL_ON_ERROR_WITH_CLEANUP(
        yr_object_copy(object_as_array(object)->prototype_item, &o),
        yr_object_destroy(copy));

    object_as_array(copy)->prototype_item = o;

    break;

  case OBJECT_TYPE_DICTIONARY:

    FAIL_ON_ERROR_WITH_CLEANUP(
        yr_object_copy(object_as_dictionary(object)->prototype_item, &o),
        yr_object_destroy(copy));

    object_as_dictionary(copy)->prototype_item = o;

    break;

  default:
    assert(false);
  }

  *object_copy = copy;

  return ERROR_SUCCESS;
}

int yr_object_structure_set_member(YR_OBJECT* object, YR_OBJECT* member)
{
  YR_STRUCTURE_MEMBER* sm;

  assert(object->type == OBJECT_TYPE_STRUCTURE);

  // Check if the object already have a member with the same identifier

  if (yr_object_lookup_field(object, member->identifier) != NULL)
    return ERROR_DUPLICATED_STRUCTURE_MEMBER;

  sm = (YR_STRUCTURE_MEMBER*) yr_malloc(sizeof(YR_STRUCTURE_MEMBER));

  if (sm == NULL)
    return ERROR_INSUFFICIENT_MEMORY;

  member->parent = object;
  sm->object = member;
  sm->next = object_as_structure(object)->members;

  object_as_structure(object)->members = sm;

  return ERROR_SUCCESS;
}

YR_API int yr_object_array_length(YR_OBJECT* object)
{
  YR_OBJECT_ARRAY* array;

  assert(object->type == OBJECT_TYPE_ARRAY);
  array = object_as_array(object);

  if (array->items == NULL)
    return 0;

  return array->items->length;
}

YR_API YR_OBJECT* yr_object_array_get_item(YR_OBJECT* object, int flags,
                                           int index)
{
  YR_OBJECT* result = NULL;
  YR_OBJECT_ARRAY* array;

  assert(object->type == OBJECT_TYPE_ARRAY);

  if (index < 0)
    return NULL;

  array = object_as_array(object);

  if (array->items != NULL && array->items->capacity > index)
    result = array->items->objects[index];

  if (result == NULL && flags & OBJECT_CREATE)
  {
    yr_object_copy(array->prototype_item, &result);

    if (result != NULL)
      yr_object_array_set_item(object, result, index);
  }

  return result;
}

int yr_object_array_set_item(YR_OBJECT* object, YR_OBJECT* item, int index)
{
  YR_OBJECT_ARRAY* array;

  int capacity;

  assert(index >= 0);
  assert(object->type == OBJECT_TYPE_ARRAY);

  array = object_as_array(object);

  if (array->items == NULL)
  {
    capacity = 64;

    while (capacity <= index) capacity *= 2;

    array->items = (YR_ARRAY_ITEMS*) yr_malloc(
        sizeof(YR_ARRAY_ITEMS) + capacity * sizeof(YR_OBJECT*));

    if (array->items == NULL)
      return ERROR_INSUFFICIENT_MEMORY;

    memset(array->items->objects, 0, capacity * sizeof(YR_OBJECT*));

    array->items->capacity = capacity;
    array->items->length = 0;
  }
  else if (index >= array->items->capacity)
  {
    capacity = array->items->capacity * 2;

    while (capacity <= index) capacity *= 2;

    array->items = (YR_ARRAY_ITEMS*) yr_realloc(
        array->items, sizeof(YR_ARRAY_ITEMS) + capacity * sizeof(YR_OBJECT*));

    if (array->items == NULL)
      return ERROR_INSUFFICIENT_MEMORY;

    for (int i = array->items->capacity; i < capacity; i++)
      array->items->objects[i] = NULL;

    array->items->capacity = capacity;
  }

  item->parent = object;
  array->items->objects[index] = item;

  if (index >= array->items->length)
    array->items->length = index + 1;

  return ERROR_SUCCESS;
}

YR_OBJECT* yr_object_dict_get_item(
    YR_OBJECT* object,
    int flags,
    const char* key)
{
  YR_OBJECT* result = NULL;
  YR_OBJECT_DICTIONARY* dict;

  assert(object->type == OBJECT_TYPE_DICTIONARY);

  dict = object_as_dictionary(object);

  if (dict->items != NULL)
  {
    for (int i = 0; i < dict->items->used; i++)
    {
      if (strcmp(dict->items->objects[i].key->c_string, key) == 0)
        result = dict->items->objects[i].obj;
    }
  }

  if (result == NULL && flags & OBJECT_CREATE)
  {
    yr_object_copy(dict->prototype_item, &result);

    if (result != NULL)
      yr_object_dict_set_item(object, result, key);
  }

  return result;
}

int yr_object_dict_set_item(YR_OBJECT* object, YR_OBJECT* item, const char* key)
{
  YR_OBJECT_DICTIONARY* dict;

  int count;

  assert(object->type == OBJECT_TYPE_DICTIONARY);

  dict = object_as_dictionary(object);

  if (dict->items == NULL)
  {
    count = 64;

    dict->items = (YR_DICTIONARY_ITEMS*) yr_malloc(
        sizeof(YR_DICTIONARY_ITEMS) + count * sizeof(dict->items->objects[0]));

    if (dict->items == NULL)
      return ERROR_INSUFFICIENT_MEMORY;

    memset(dict->items->objects, 0, count * sizeof(dict->items->objects[0]));

    dict->items->free = count;
    dict->items->used = 0;
  }
  else if (dict->items->free == 0)
  {
    count = dict->items->used * 2;
    dict->items = (YR_DICTIONARY_ITEMS*) yr_realloc(
        dict->items,
        sizeof(YR_DICTIONARY_ITEMS) + count * sizeof(dict->items->objects[0]));

    if (dict->items == NULL)
      return ERROR_INSUFFICIENT_MEMORY;

    for (int i = dict->items->used; i < count; i++)
    {
      dict->items->objects[i].key = NULL;
      dict->items->objects[i].obj = NULL;
    }

    dict->items->free = dict->items->used;
  }

  item->parent = object;

  dict->items->objects[dict->items->used].key = ss_new(key);
  dict->items->objects[dict->items->used].obj = item;

  dict->items->used++;
  dict->items->free--;

  return ERROR_SUCCESS;
}

bool yr_object_has_undefined_value(YR_OBJECT* object, const char* field, ...)
{
  YR_OBJECT* field_obj;

  va_list args;
  va_start(args, field);

  if (field != NULL)
    field_obj = _yr_object_lookup(object, 0, field, args);
  else
    field_obj = object;

  va_end(args);

  if (field_obj == NULL)
    return true;

  switch (field_obj->type)
  {
  case OBJECT_TYPE_FLOAT:
    return yr_isnan(field_obj->value.d);
  case OBJECT_TYPE_STRING:
    return field_obj->value.ss == NULL;
  case OBJECT_TYPE_INTEGER:
    return field_obj->value.i == YR_UNDEFINED;
  }

  return false;
}

int64_t yr_object_get_integer(YR_OBJECT* object, const char* field, ...)
{
  YR_OBJECT* integer_obj;

  va_list args;
  va_start(args, field);

  if (field != NULL)
    integer_obj = _yr_object_lookup(object, 0, field, args);
  else
    integer_obj = object;

  va_end(args);

  if (integer_obj == NULL)
    return YR_UNDEFINED;

  assertf(
      integer_obj->type == OBJECT_TYPE_INTEGER,
      "type of \"%s\" is not integer\n",
      field);

  return integer_obj->value.i;
}

double yr_object_get_float(YR_OBJECT* object, const char* field, ...)
{
  YR_OBJECT* double_obj;

  va_list args;
  va_start(args, field);

  if (field != NULL)
    double_obj = _yr_object_lookup(object, 0, field, args);
  else
    double_obj = object;

  va_end(args);

  if (double_obj == NULL)
    return NAN;

  assertf(
      double_obj->type == OBJECT_TYPE_FLOAT,
      "type of \"%s\" is not double\n",
      field);

  return double_obj->value.d;
}

SIZED_STRING* yr_object_get_string(YR_OBJECT* object, const char* field, ...)
{
  YR_OBJECT* string_obj;

  va_list args;
  va_start(args, field);

  if (field != NULL)
    string_obj = _yr_object_lookup(object, 0, field, args);
  else
    string_obj = object;

  va_end(args);

  if (string_obj == NULL)
    return NULL;

  assertf(
      string_obj->type == OBJECT_TYPE_STRING,
      "type of \"%s\" is not string\n",
      field);

  return string_obj->value.ss;
}

int yr_object_set_integer(
    int64_t value,
    YR_OBJECT* object,
    const char* field,
    ...)
{
  YR_OBJECT* integer_obj;

  va_list args;
  va_start(args, field);

  if (field != NULL)
    integer_obj = _yr_object_lookup(object, OBJECT_CREATE, field, args);
  else
    integer_obj = object;

  va_end(args);

  if (integer_obj == NULL)
  {
    if (field != NULL)
      return ERROR_INSUFFICIENT_MEMORY;
    else
      return ERROR_INVALID_ARGUMENT;
  }

  assert(integer_obj->type == OBJECT_TYPE_INTEGER);

  integer_obj->value.i = value;

  return ERROR_SUCCESS;
}

int yr_object_set_float(double value, YR_OBJECT* object, const char* field, ...)
{
  YR_OBJECT* double_obj;

  va_list args;
  va_start(args, field);

  if (field != NULL)
    double_obj = _yr_object_lookup(object, OBJECT_CREATE, field, args);
  else
    double_obj = object;

  va_end(args);

  if (double_obj == NULL)
  {
    if (field != NULL)
      return ERROR_INSUFFICIENT_MEMORY;
    else
      return ERROR_INVALID_ARGUMENT;
  }

  assert(double_obj->type == OBJECT_TYPE_FLOAT);

  double_obj->value.d = value;

  return ERROR_SUCCESS;
}

int yr_object_set_string(
    const char* value,
    size_t len,
    YR_OBJECT* object,
    const char* field,
    ...)
{
  YR_OBJECT* string_obj;

  va_list args;
  va_start(args, field);

  if (field != NULL)
    string_obj = _yr_object_lookup(object, OBJECT_CREATE, field, args);
  else
    string_obj = object;

  va_end(args);

  if (string_obj == NULL)
  {
    if (field != NULL)
      return ERROR_INSUFFICIENT_MEMORY;
    else
      return ERROR_INVALID_ARGUMENT;
  }

  assert(string_obj->type == OBJECT_TYPE_STRING);

  if (string_obj->value.ss != NULL)
    yr_free(string_obj->value.ss);

  if (value != NULL)
  {
    string_obj->value.ss = (SIZED_STRING*) yr_malloc(
        len + sizeof(SIZED_STRING));

    if (string_obj->value.ss == NULL)
      return ERROR_INSUFFICIENT_MEMORY;

    string_obj->value.ss->length = (uint32_t) len;
    string_obj->value.ss->flags = 0;

    memcpy(string_obj->value.ss->c_string, value, len);
    string_obj->value.ss->c_string[len] = '\0';
  }
  else
  {
    string_obj->value.ss = NULL;
  }

  return ERROR_SUCCESS;
}

YR_OBJECT* yr_object_get_root(YR_OBJECT* object)
{
  YR_OBJECT* o = object;

  while (o->parent != NULL) o = o->parent;

  return o;
}

YR_API void yr_object_print_data(
    YR_OBJECT* object,
    int indent,
    int print_identifier)
{
  YR_DICTIONARY_ITEMS* dict_items;
  YR_STRUCTURE_MEMBER* member;

  char indent_spaces[32];

  indent = yr_min(indent, sizeof(indent_spaces) - 1);

  memset(indent_spaces, '\t', indent);
  indent_spaces[indent] = '\0';

  if (print_identifier && object->type != OBJECT_TYPE_FUNCTION)
    printf("%s%s", indent_spaces, object->identifier);

  switch (object->type)
  {
  case OBJECT_TYPE_FLOAT:
    if (object->value.i != YR_UNDEFINED)
      printf(" = %f", object->value.d);
    else
      printf(" = YR_UNDEFINED");

    break;

  case OBJECT_TYPE_INTEGER:

    if (object->value.i != YR_UNDEFINED)
      printf(" = %" PRId64, object->value.i);
    else
      printf(" = YR_UNDEFINED");

    break;

  case OBJECT_TYPE_STRING:

    if (object->value.ss != NULL)
    {
      printf(" = \"");

      for (size_t l = 0; l < object->value.ss->length; l++)
      {
        char c = object->value.ss->c_string[l];

        if (isprint((unsigned char) c))
          printf("%c", c);
        else
          printf("\\x%02x", (unsigned char) c);
      }

      printf("\"");
    }
    else
    {
      printf(" = YR_UNDEFINED");
    }

    break;

  case OBJECT_TYPE_STRUCTURE:

    member = object_as_structure(object)->members;

    while (member != NULL)
    {
      if (member->object->type != OBJECT_TYPE_FUNCTION)
      {
        printf("\n");
        yr_object_print_data(member->object, indent + 1, 1);
      }
      member = member->next;
    }

    break;

  case OBJECT_TYPE_ARRAY:
    for (int i = 0; i < yr_object_array_length(object); i++)
    {
      YR_OBJECT* o = yr_object_array_get_item(object, 0, i);

      if (o != NULL)
      {
        printf("\n%s\t[%d]", indent_spaces, i);
        yr_object_print_data(o, indent + 1, 0);
      }
    }
    break;

  case OBJECT_TYPE_DICTIONARY:

    dict_items = object_as_dictionary(object)->items;

    if (dict_items != NULL)
    {
      for (int i = 0; i < dict_items->used; i++)
      {
        printf("\n%s\t%s", indent_spaces, dict_items->objects[i].key->c_string);

        yr_object_print_data(dict_items->objects[i].obj, indent + 1, 0);
      }
    }

    break;
  }
}
