/* Copyright 2015 Samsung Electronics Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>

#include "ecma-alloc.h"
#include "ecma-builtins.h"
#include "ecma-exceptions.h"
#include "ecma-eval.h"
#include "ecma-function-object.h"
#include "ecma-gc.h"
#include "ecma-helpers.h"
#include "ecma-init-finalize.h"
#include "ecma-objects.h"
#include "ecma-objects-general.h"
#include "parser.h"
#include "serializer.h"

#define JERRY_INTERNAL
#include "jerry-internal.h"

/**
 * Jerry engine build date
 */
const char *jerry_build_date = JERRY_BUILD_DATE;

/**
 * Jerry engine build commit hash
 */
const char *jerry_commit_hash = JERRY_COMMIT_HASH;

/**
 * Jerry engine build branch name
 */
const char *jerry_branch_name = JERRY_BRANCH_NAME;

/**
 * Jerry run-time configuration flags
 */
static jerry_flag_t jerry_flags;

/**
 * Jerry API availability flag
 */
static bool jerry_api_available;

/** \addtogroup jerry_extension Jerry engine extension interface
 * @{
 */

#ifdef JERRY_ENABLE_LOG
/**
 * TODO:
 *      Move logging-related functionality to separate module, like jerry-log.cpp
 */

/**
 * Verbosity level of logging
 */
int jerry_debug_level = 0;

/**
 * File, used for logging
 */
FILE *jerry_log_file = NULL;
#endif /* JERRY_ENABLE_LOG */

/**
 * Assert that it is correct to call API in current state.
 *
 * Note:
 *         By convention, there can be some states when API could not be invoked.
 *
 *         While, API can be invoked jerry_api_available flag is set,
 *         and while it is incorrect to invoke API - it is not set.
 *
 *         The procedure checks that it is correct to invoke API in current state.
 *         If it is correct, procedure just returns; otherwise - engine is stopped.
 *
 * Note:
 *         API could not be invoked in the following cases:
 *           - between enter to and return from native free callback.
 */
static void
jerry_assert_api_available (void)
{
  if (!jerry_api_available)
  {
    JERRY_UNREACHABLE ();
  }
} /* jerry_assert_api_available */

/**
 * Turn on API availability
 */
static void
jerry_make_api_available (void)
{
  jerry_api_available = true;
} /* jerry_make_api_available */

/**
 * Turn off API availability
 */
static void
jerry_make_api_unavailable (void)
{
  jerry_api_available = false;
} /* jerry_make_api_unavailable */

/**
 * Convert ecma-value to Jerry API value representation
 *
 * Note:
 *      if the output value contains string / object, it should be freed
 *      with jerry_api_release_string / jerry_api_release_object,
 *      just when it becomes unnecessary.
 */
static void
jerry_api_convert_ecma_value_to_api_value (jerry_api_value_t *out_value_p, /**< out: api value */
                                           ecma_value_t value) /**< ecma-value (undefined,
                                                                *   null, boolean, number,
                                                                *   string or object */
{
  jerry_assert_api_available ();

  JERRY_ASSERT (out_value_p != NULL);

  if (ecma_is_value_undefined (value))
  {
    out_value_p->type = JERRY_API_DATA_TYPE_UNDEFINED;
  }
  else if (ecma_is_value_null (value))
  {
    out_value_p->type = JERRY_API_DATA_TYPE_NULL;
  }
  else if (ecma_is_value_boolean (value))
  {
    out_value_p->type = JERRY_API_DATA_TYPE_BOOLEAN;
    out_value_p->v_bool = ecma_is_value_true (value);
  }
  else if (ecma_is_value_number (value))
  {
    ecma_number_t *num = ecma_get_number_from_value (value);

#if CONFIG_ECMA_NUMBER_TYPE == CONFIG_ECMA_NUMBER_FLOAT32
    out_value_p->type = JERRY_API_DATA_TYPE_FLOAT32;
    out_value_p->v_float32 = *num;
#elif CONFIG_ECMA_NUMBER_TYPE == CONFIG_ECMA_NUMBER_FLOAT64
    out_value_p->type = JERRY_API_DATA_TYPE_FLOAT64;
    out_value_p->v_float64 = *num;
#endif /* CONFIG_ECMA_NUMBER_TYPE == CONFIG_ECMA_NUMBER_FLOAT64 */
  }
  else if (ecma_is_value_string (value))
  {
    ecma_string_t *str = ecma_get_string_from_value (value);

    out_value_p->type = JERRY_API_DATA_TYPE_STRING;
    out_value_p->v_string = ecma_copy_or_ref_ecma_string (str);
  }
  else if (ecma_is_value_object (value))
  {
    ecma_object_t *obj = ecma_get_object_from_value (value);
    ecma_ref_object (obj);

    out_value_p->type = JERRY_API_DATA_TYPE_OBJECT;
    out_value_p->v_object = obj;
  }
  else
  {
    /* Impossible type of conversion from ecma_value to api_value */
    JERRY_UNREACHABLE ();
  }
} /* jerry_api_convert_ecma_value_to_api_value */

/**
 * Convert value, represented in Jerry API format, to ecma-value.
 *
 * Note:
 *      the output ecma-value should be freed with ecma_free_value when it becomes unnecessary.
 */
static void
jerry_api_convert_api_value_to_ecma_value (ecma_value_t *out_value_p, /**< out: ecma-value */
                                           const jerry_api_value_t* api_value_p) /**< value in Jerry API format */
{
  switch (api_value_p->type)
  {
    case JERRY_API_DATA_TYPE_UNDEFINED:
    {
      *out_value_p = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED);

      break;
    }
    case JERRY_API_DATA_TYPE_NULL:
    {
      *out_value_p = ecma_make_simple_value (ECMA_SIMPLE_VALUE_NULL);

      break;
    }
    case JERRY_API_DATA_TYPE_BOOLEAN:
    {
      *out_value_p = ecma_make_simple_value (api_value_p->v_bool ? ECMA_SIMPLE_VALUE_TRUE : ECMA_SIMPLE_VALUE_FALSE);

      break;
    }
    case JERRY_API_DATA_TYPE_FLOAT32:
    {
      ecma_number_t *num = ecma_alloc_number ();
      *num = static_cast<ecma_number_t> (api_value_p->v_float32);

      *out_value_p = ecma_make_number_value (num);

      break;
    }
    case JERRY_API_DATA_TYPE_FLOAT64:
    {
      ecma_number_t *num = ecma_alloc_number ();
      *num = static_cast<ecma_number_t> (api_value_p->v_float64);

      *out_value_p = ecma_make_number_value (num);

      break;
    }
    case JERRY_API_DATA_TYPE_UINT32:
    {
      ecma_number_t *num = ecma_alloc_number ();
      *num = static_cast<ecma_number_t> (api_value_p->v_uint32);

      *out_value_p = ecma_make_number_value (num);

      break;
    }
    case JERRY_API_DATA_TYPE_STRING:
    {
      ecma_string_t *str_p = ecma_copy_or_ref_ecma_string (api_value_p->v_string);

      *out_value_p = ecma_make_string_value (str_p);

      break;
    }
    case JERRY_API_DATA_TYPE_OBJECT:
    {
      ecma_object_t *obj_p = api_value_p->v_object;

      ecma_ref_object (obj_p);

      *out_value_p = ecma_make_object_value (obj_p);

      break;
    }
    default:
    {
      JERRY_UNREACHABLE ();
    }
  }
} /* jerry_api_convert_api_value_to_ecma_value */


/**
 * @}
 */

/**
 * Copy string characters to specified buffer, append zero character at end of the buffer.
 *
 * @return number of bytes, actually copied to the buffer - if string's content was copied successfully;
 *         otherwise (in case size of buffer is insuficcient) - negative number, which is calculated
 *         as negation of buffer size, that is required to hold the string's content.
 */
ssize_t
jerry_api_string_to_char_buffer (const jerry_api_string_t *string_p, /**< string descriptor */
                             char *buffer_p, /**< output characters buffer */
                             ssize_t buffer_size) /**< size of output buffer */
{
  jerry_assert_api_available ();

  return ecma_string_to_zt_string (string_p, (ecma_char_t*) buffer_p, buffer_size);
} /* jerry_api_string_to_char_buffer */

/**
 * Acquire string pointer for usage outside of the engine
 * from string retrieved in extension routine call from engine.
 *
 * Warning:
 *         acquired pointer should be released with jerry_api_release_string
 *
 * @return pointer that may be used outside of the engine
 */
jerry_api_string_t*
jerry_api_acquire_string (jerry_api_string_t *string_p) /**< pointer passed to function */
{
  jerry_assert_api_available ();

  return ecma_copy_or_ref_ecma_string (string_p);
} /* jerry_api_acquire_string */

/**
 * Release string pointer
 *
 * See also:
 *          jerry_api_acquire_string
 *          jerry_api_call_function
 *
 */
void
jerry_api_release_string (jerry_api_string_t *string_p) /**< pointer acquired through jerry_api_acquire_string */
{
  jerry_assert_api_available ();

  ecma_deref_ecma_string (string_p);
} /* jerry_api_release_string */

/**
 * Acquire object pointer for usage outside of the engine
 * from object retrieved in extension routine call from engine.
 *
 * Warning:
 *         acquired pointer should be released with jerry_api_release_object
 *
 * @return pointer that may be used outside of the engine
 */
jerry_api_object_t*
jerry_api_acquire_object (jerry_api_object_t *object_p) /**< pointer passed to function */
{
  jerry_assert_api_available ();

  ecma_ref_object (object_p);

  return object_p;
} /* jerry_api_acquire_object */

/**
 * Release object pointer
 *
 * See also:
 *          jerry_api_acquire_object
 *          jerry_api_call_function
 *          jerry_api_get_object_field_value
 */
void
jerry_api_release_object (jerry_api_object_t *object_p) /**< pointer acquired through jerry_api_acquire_object */
{
  jerry_assert_api_available ();

  ecma_deref_object (object_p);
} /* jerry_api_release_object */

/**
 * Release specified Jerry API value
 */
void
jerry_api_release_value (jerry_api_value_t *value_p) /**< API value */
{
  jerry_assert_api_available ();

  if (value_p->type == JERRY_API_DATA_TYPE_STRING)
  {
    jerry_api_release_string (value_p->v_string);
  }
  else if (value_p->type == JERRY_API_DATA_TYPE_OBJECT)
  {
    jerry_api_release_object (value_p->v_object);
  }
} /* jerry_api_release_value */

/**
 * Create a string
 *
 * Note:
 *      caller should release the string with jerry_api_release_string, just when the value becomes unnecessary.
 *
 * @return pointer to created string
 */
jerry_api_string_t*
jerry_api_create_string (const char *v) /**< string value */
{
  jerry_assert_api_available ();

  return ecma_new_ecma_string ((const ecma_char_t*) v);
} /* jerry_api_create_string */

/**
 * Create an object
 *
 * Note:
 *      caller should release the object with jerry_api_release_object, just when the value becomes unnecessary.
 *
 * @return pointer to created object
 */
jerry_api_object_t*
jerry_api_create_object (void)
{
  jerry_assert_api_available ();

  return ecma_op_create_object_object_noarg ();
} /* jerry_api_create_object */

/**
 * Create an error object
 *
 * Note:
 *      caller should release the object with jerry_api_release_object, just when the value becomes unnecessary.
 *
 * @return pointer to created error object
 */
jerry_api_object_t*
jerry_api_create_error (jerry_api_error_t error_type, /**< type of error */
                        const char *message_p) /**< value of 'message' property
                                                *   of constructed error object */
{
  jerry_assert_api_available ();

  ecma_standard_error_t standard_error_type = ECMA_ERROR_COMMON;

  switch (error_type)
  {
    case JERRY_API_ERROR_COMMON:
    {
      standard_error_type = ECMA_ERROR_COMMON;
      break;
    }
    case JERRY_API_ERROR_EVAL:
    {
      standard_error_type = ECMA_ERROR_EVAL;
      break;
    }
    case JERRY_API_ERROR_RANGE:
    {
      standard_error_type = ECMA_ERROR_RANGE;
      break;
    }
    case JERRY_API_ERROR_REFERENCE:
    {
      standard_error_type = ECMA_ERROR_REFERENCE;
      break;
    }
    case JERRY_API_ERROR_SYNTAX:
    {
      standard_error_type = ECMA_ERROR_SYNTAX;
      break;
    }
    case JERRY_API_ERROR_TYPE:
    {
      standard_error_type = ECMA_ERROR_TYPE;
      break;
    }
    case JERRY_API_ERROR_URI:
    {
      standard_error_type = ECMA_ERROR_URI;
      break;
    }
    default:
    {
      JERRY_UNREACHABLE ();
    }
  }

  if (message_p == NULL)
  {
    return ecma_new_standard_error (standard_error_type);
  }
  else
  {
    ecma_string_t* message_string_p = ecma_new_ecma_string ((const ecma_char_t*) message_p);

    ecma_object_t* error_object_p = ecma_new_standard_error_with_message (standard_error_type, message_string_p);

    ecma_deref_ecma_string (message_string_p);

    return error_object_p;
  }
} /* jerry_api_create_error */

/**
 * Create an external function object
 *
 * Note:
 *      caller should release the object with jerry_api_release_object, just when the value becomes unnecessary.
 *
 * @return pointer to created external function object
 */
jerry_api_object_t*
jerry_api_create_external_function (jerry_external_handler_t handler_p) /**< pointer to native handler
                                                                         *   for the function */
{
  jerry_assert_api_available ();

  return ecma_op_create_external_function_object ((ecma_external_pointer_t) handler_p);
} /* jerry_api_create_external_function */

/**
 * Dispatch call to specified external function using the native handler
 *
 * Note:
 *       if called native handler returns true, then dispatcher just returns value received
 *       through 'return value' output argument, otherwise - throws the value as an exception.
 *
 * @return completion value
 *         Returned value must be freed with ecma_free_completion_value
 */
ecma_completion_value_t
jerry_dispatch_external_function (ecma_object_t *function_object_p, /**< external function object */
                                  ecma_external_pointer_t handler_p, /**< pointer to the function's native handler */
                                  ecma_value_t this_arg_value, /**< 'this' argument */
                                  const ecma_value_t args_p[], /**< arguments list */
                                  ecma_length_t args_count) /**< number of arguments */
{
  jerry_assert_api_available ();

  JERRY_STATIC_ASSERT (sizeof (args_count) == sizeof (uint16_t));

  ecma_completion_value_t completion_value;

  MEM_DEFINE_LOCAL_ARRAY (api_arg_values, args_count, jerry_api_value_t);

  for (uint32_t i = 0; i < args_count; ++i)
  {
    jerry_api_convert_ecma_value_to_api_value (&api_arg_values[i], args_p[i]);
  }

  jerry_api_value_t api_this_arg_value, api_ret_value;
  jerry_api_convert_ecma_value_to_api_value (&api_this_arg_value, this_arg_value);

  // default return value
  jerry_api_convert_ecma_value_to_api_value (&api_ret_value,
                                             ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED));

  bool is_successful = ((jerry_external_handler_t) handler_p) (function_object_p,
                                                               &api_this_arg_value,
                                                               &api_ret_value,
                                                               api_arg_values,
                                                               args_count);

  ecma_value_t ret_value;
  jerry_api_convert_api_value_to_ecma_value (&ret_value, &api_ret_value);

  if (is_successful)
  {
    completion_value = ecma_make_normal_completion_value (ret_value);
  }
  else
  {
    completion_value = ecma_make_throw_completion_value (ret_value);
  }

  jerry_api_release_value (&api_ret_value);
  jerry_api_release_value (&api_this_arg_value);
  for (uint32_t i = 0; i < args_count; i++)
  {
    jerry_api_release_value (&api_arg_values[i]);
  }

  MEM_FINALIZE_LOCAL_ARRAY (api_arg_values);

  return completion_value;
} /* jerry_dispatch_external_function */

/**
 * Dispatch call to object's native free callback function
 *
 * Note:
 *       the callback is called during critical GC phase,
 *       so, should not perform any requests to engine
 */
void
jerry_dispatch_object_free_callback (ecma_external_pointer_t freecb_p, /**< pointer to free callback handler */
                                     ecma_external_pointer_t native_p) /**< native handle, associated
                                                                        *   with freed object */
{
  jerry_make_api_unavailable ();

  ((jerry_object_free_callback_t) freecb_p) ((uintptr_t) native_p);

  jerry_make_api_available ();
} /* jerry_dispatch_object_free_callback */

/**
 * Check if the specified object is a function object.
 *
 * @return true - if the specified object is a function object,
 *         false - otherwise.
 */
bool
jerry_api_is_function (const jerry_api_object_t* object_p) /**< an object */
{
  jerry_assert_api_available ();

  JERRY_ASSERT (object_p != NULL);

  ecma_value_t obj_val = ecma_make_object_value (object_p);

  return ecma_op_is_callable (obj_val);
} /* jerry_api_is_function */

/**
 * Check if the specified object is a constructor function object.
 *
 * @return true - if the specified object is a function object that implements [[Construct]],
 *         false - otherwise.
 */
bool
jerry_api_is_constructor (const jerry_api_object_t* object_p) /**< an object */
{
  jerry_assert_api_available ();

  JERRY_ASSERT (object_p != NULL);

  ecma_value_t obj_val = ecma_make_object_value (object_p);

  return ecma_is_constructor (obj_val);
} /* jerry_api_is_constructor */

/**
 * Create field (named data property) in the specified object
 *
 * @return true, if field was created successfully, i.e. upon the call:
 *                - there is no field with same name in the object;
 *                - the object is extensible;
 *         false - otherwise.
 */
bool
jerry_api_add_object_field (jerry_api_object_t *object_p, /**< object to add field at */
                            const char *field_name_p, /**< name of the field */
                            const jerry_api_value_t *field_value_p, /**< value of the field */
                            bool is_writable) /**< flag indicating whether the created field should be writable */
{
  jerry_assert_api_available ();

  bool is_successful = false;

  if (ecma_get_object_extensible (object_p))
  {
    ecma_string_t* field_name_str_p = ecma_new_ecma_string ((const ecma_char_t*) field_name_p);

    ecma_property_t *prop_p = ecma_op_object_get_own_property (object_p, field_name_str_p);

    if (prop_p == NULL)
    {
      is_successful = true;

      ecma_value_t value_to_put;
      jerry_api_convert_api_value_to_ecma_value (&value_to_put, field_value_p);

      prop_p = ecma_create_named_data_property (object_p,
                                                field_name_str_p,
                                                is_writable,
                                                true,
                                                true);
      ecma_named_data_property_assign_value (object_p, prop_p, value_to_put);

      ecma_free_value (value_to_put, true);
    }

    ecma_deref_ecma_string (field_name_str_p);
  }

  return is_successful;
} /* jerry_api_add_object_field */

/**
 * Delete field in the specified object
 *
 * @return true, if field was deleted successfully, i.e. upon the call:
 *                - there is field with specified name in the object;
 *         false - otherwise.
 */
bool
jerry_api_delete_object_field (jerry_api_object_t *object_p, /**< object to delete field at */
                               const char *field_name_p) /**< name of the field */
{
  jerry_assert_api_available ();

  bool is_successful = true;

  ecma_string_t* field_name_str_p = ecma_new_ecma_string ((const ecma_char_t*) field_name_p);

  ecma_completion_value_t delete_completion = ecma_op_object_delete (object_p,
                                                                     field_name_str_p,
                                                                     true);

  if (!ecma_is_completion_value_normal (delete_completion))
  {
    JERRY_ASSERT (ecma_is_completion_value_throw (delete_completion));

    is_successful = false;
  }

  ecma_free_completion_value (delete_completion);

  ecma_deref_ecma_string (field_name_str_p);

  return is_successful;
} /* jerry_api_delete_object_field */

/**
 * Get value of field in the specified object
 *
 * Note:
 *      if value was retrieved successfully, it should be freed
 *      with jerry_api_release_value just when it becomes unnecessary.
 *
 * @return true, if field value was retrieved successfully, i.e. upon the call:
 *                - there is field with specified name in the object;
 *         false - otherwise.
 */
bool
jerry_api_get_object_field_value (jerry_api_object_t *object_p, /**< object */
                                  const char *field_name_p, /**< name of the field */
                                  jerry_api_value_t *field_value_p) /**< out: field value, if retrieved successfully */
{
  jerry_assert_api_available ();

  bool is_successful = true;

  ecma_string_t* field_name_str_p = ecma_new_ecma_string ((const ecma_char_t*) field_name_p);

  ecma_completion_value_t get_completion = ecma_op_object_get (object_p,
                                                               field_name_str_p);

  if (ecma_is_completion_value_normal (get_completion))
  {
    ecma_value_t val = ecma_get_completion_value_value (get_completion);

    jerry_api_convert_ecma_value_to_api_value (field_value_p, val);
  }
  else
  {
    JERRY_ASSERT (ecma_is_completion_value_throw (get_completion));

    is_successful = false;
  }

  ecma_free_completion_value (get_completion);

  ecma_deref_ecma_string (field_name_str_p);

  return is_successful;
} /* jerry_api_get_object_field_value */

/**
 * Set value of field in the specified object
 *
 * @return true, if field value was set successfully, i.e. upon the call:
 *                - field value is writable;
 *         false - otherwise.
 */
bool
jerry_api_set_object_field_value (jerry_api_object_t *object_p, /**< object */
                                  const char *field_name_p, /**< name of the field */
                                  const jerry_api_value_t *field_value_p) /**< field value to set */
{
  jerry_assert_api_available ();

  bool is_successful = true;

  ecma_string_t* field_name_str_p = ecma_new_ecma_string ((const ecma_char_t*) field_name_p);

  ecma_value_t value_to_put;
  jerry_api_convert_api_value_to_ecma_value (&value_to_put, field_value_p);

  ecma_completion_value_t set_completion = ecma_op_object_put (object_p,
                                                               field_name_str_p,
                                                               value_to_put,
                                                               true);

  if (!ecma_is_completion_value_normal (set_completion))
  {
    JERRY_ASSERT (ecma_is_completion_value_throw (set_completion));

    is_successful = false;
  }

  ecma_free_completion_value (set_completion);

  ecma_free_value (value_to_put, true);
  ecma_deref_ecma_string (field_name_str_p);

  return is_successful;
} /* jerry_api_set_object_field_value */

/**
 * Get native handle, associated with specified object
 *
 * @return true - if there is associated handle (handle is returned through out_handle_p),
 *         false - otherwise.
 */
bool
jerry_api_get_object_native_handle (jerry_api_object_t *object_p, /**< object to get handle from */
                                    uintptr_t* out_handle_p) /**< out: handle value */
{
  jerry_assert_api_available ();

  uintptr_t handle_value;

  bool does_exist = ecma_get_external_pointer_value (object_p,
                                                     ECMA_INTERNAL_PROPERTY_NATIVE_HANDLE,
                                                     &handle_value);

  if (does_exist)
  {
    *out_handle_p = handle_value;
  }

  return does_exist;
} /* jerry_api_get_object_native_handle */

/**
 * Set native handle and, optionally, free callback for the specified object
 *
 * Note:
 *      If native handle was already set for the object, its value is updated.
 *
 * Note:
 *      If free callback is specified, it is set to be called upon specified JS-object is freed (by GC).
 *
 *      Otherwise, if NULL is specified for free callback pointer, free callback is not created
 *      and, if a free callback was added earlier for the object, it is removed.
 */
void
jerry_api_set_object_native_handle (jerry_api_object_t *object_p, /**< object to set handle in */
                                    uintptr_t handle, /**< handle value */
                                    jerry_object_free_callback_t freecb_p) /**< object free callback or NULL */
{
  jerry_assert_api_available ();

  ecma_create_external_pointer_property (object_p,
                                         ECMA_INTERNAL_PROPERTY_NATIVE_HANDLE,
                                         handle);
  if (freecb_p != NULL)
  {
    ecma_create_external_pointer_property (object_p,
                                           ECMA_INTERNAL_PROPERTY_FREE_CALLBACK,
                                           (uintptr_t) freecb_p);
  }
  else
  {
    ecma_property_t *prop_p = ecma_find_internal_property (object_p,
                                                           ECMA_INTERNAL_PROPERTY_FREE_CALLBACK);
    if (prop_p != NULL)
    {
      ecma_delete_property (object_p, prop_p);
    }
  }
} /* jerry_api_set_object_native_handle */

/**
 * Invoke function specified by a function object
 *
 * Note:
 *      returned value should be freed with jerry_api_release_value
 *      just when the value becomes unnecessary.
 *
 * Note:
 *      If function is invoked as constructor, it should support [[Construct]] method,
 *      otherwise, if function is simply called - it should support [[Call]] method.
 *
 * @return true, if invocation was performed successfully, i.e.:
 *                - no unhandled exceptions were thrown in connection with the call;
 *         false - otherwise.
 */
static bool
jerry_api_invoke_function (bool is_invoke_as_constructor, /**< true - invoke function as constructor
                                                           *          (this_arg_p should be NULL, as it is ignored),
                                                           *   false - perform function call */
                           jerry_api_object_t *function_object_p, /**< function object to call */
                           jerry_api_object_t *this_arg_p, /**< object for 'this' binding
                                                            *   or NULL (set 'this' binding to newly constructed object,
                                                            *            if function is invoked as constructor;
                                                            *            in case of simple function call set 'this'
                                                            *            binding to the global object) */
                           jerry_api_value_t *retval_p, /**< pointer to place for function's
                                                         *   return value / thrown exception value
                                                         *   or NULL (to ignore the values) */
                           const jerry_api_value_t args_p[], /**< function's call arguments
                                                              *   (NULL if arguments number is zero) */
                           uint16_t args_count) /**< number of the arguments */
{
  JERRY_ASSERT (args_count == 0 || args_p != NULL);
  JERRY_STATIC_ASSERT (sizeof (args_count) == sizeof (ecma_length_t));

  bool is_successful = true;

  MEM_DEFINE_LOCAL_ARRAY (arg_values, args_count, ecma_value_t);

  for (uint32_t i = 0; i < args_count; ++i)
  {
    jerry_api_convert_api_value_to_ecma_value (&arg_values[i], &args_p[i]);
  }

  ecma_completion_value_t call_completion;

  if (is_invoke_as_constructor)
  {
    JERRY_ASSERT (this_arg_p == NULL);
    JERRY_ASSERT (jerry_api_is_constructor (function_object_p));

    call_completion = ecma_op_function_construct (function_object_p,
                                                  arg_values,
                                                  args_count);
  }
  else
  {
    JERRY_ASSERT (jerry_api_is_function (function_object_p));

    ecma_value_t this_arg_val;

    if (this_arg_p == NULL)
    {
      this_arg_val = ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED);
    }
    else
    {
      this_arg_val = ecma_make_object_value (this_arg_p);
    }

    call_completion = ecma_op_function_call (function_object_p,
                                             this_arg_val,
                                             arg_values,
                                             args_count);
  }

  if (!ecma_is_completion_value_normal (call_completion))
  {
    /* unhandled exception during the function call */
    JERRY_ASSERT (ecma_is_completion_value_throw (call_completion));

    is_successful = false;
  }

  if (retval_p != NULL)
  {
    jerry_api_convert_ecma_value_to_api_value (retval_p,
                                               ecma_get_completion_value_value (call_completion));
  }

  ecma_free_completion_value (call_completion);

  for (uint32_t i = 0; i < args_count; i++)
  {
    ecma_free_value (arg_values[i], true);
  }

  MEM_FINALIZE_LOCAL_ARRAY (arg_values);

  return is_successful;
} /* jerry_api_invoke_function */

/**
 * Construct new TypeError object
 */
static void
jerry_api_construct_type_error (jerry_api_value_t *retval_p) /**< out: value with constructed
                                                              *        TypeError object */
{
  ecma_object_t *type_error_obj_p = ecma_new_standard_error (ECMA_ERROR_TYPE);
  ecma_value_t type_error_value = ecma_make_object_value (type_error_obj_p);

  jerry_api_convert_ecma_value_to_api_value (retval_p, type_error_value);

  ecma_deref_object (type_error_obj_p);
} /* jerry_api_construct_type_error */

/**
 * Call function specified by a function object
 *
 * Note:
 *      returned value should be freed with jerry_api_release_value
 *      just when the value becomes unnecessary.
 *
 * @return true, if call was performed successfully, i.e.:
 *                - specified object is a function object (see also jerry_api_is_function);
 *                - no unhandled exceptions were thrown in connection with the call;
 *         false - otherwise, 'retval_p' contains thrown exception:
 *                  if called object is not function object - a TypeError instance;
 *                  else - exception, thrown during the function call.
 */
bool
jerry_api_call_function (jerry_api_object_t *function_object_p, /**< function object to call */
                         jerry_api_object_t *this_arg_p, /**< object for 'this' binding
                                                          *   or NULL (set 'this' binding to the global object) */
                         jerry_api_value_t *retval_p, /**< pointer to place for function's
                                                       *   return value / thrown exception value
                                                       *   or NULL (to ignore the values) */
                         const jerry_api_value_t args_p[], /**< function's call arguments
                                                            *   (NULL if arguments number is zero) */
                         uint16_t args_count) /**< number of the arguments */
{
  jerry_assert_api_available ();

  if (jerry_api_is_function (function_object_p))
  {
    return jerry_api_invoke_function (false, function_object_p, this_arg_p, retval_p, args_p, args_count);
  }
  else
  {
    if (retval_p != NULL)
    {
      jerry_api_construct_type_error (retval_p);
    }

    return false;
  }
} /* jerry_api_call_function */

/**
 * Construct object invoking specified function object as a constructor
 *
 * Note:
 *      returned value should be freed with jerry_api_release_value
 *      just when the value becomes unnecessary.
 *
 * @return true, if construction was performed successfully, i.e.:
 *                - specified object is a constructor function object (see also jerry_api_is_constructor);
 *                - no unhandled exceptions were thrown in connection with the invocation;
 *         false - otherwise, 'retval_p' contains thrown exception:
 *                if  specified object is not a constructor function object - a TypeError instance;
 *                else - exception, thrown during the invocation.
 */
bool
jerry_api_construct_object (jerry_api_object_t *function_object_p, /**< function object to call */
                            jerry_api_value_t *retval_p, /**< pointer to place for function's
                                                          *   return value / thrown exception value
                                                          *   or NULL (to ignore the values) */
                            const jerry_api_value_t args_p[], /**< function's call arguments
                                                               *   (NULL if arguments number is zero) */
                            uint16_t args_count) /**< number of the arguments */
{
  jerry_assert_api_available ();

  if (jerry_api_is_constructor (function_object_p))
  {
    return jerry_api_invoke_function (true, function_object_p, NULL, retval_p, args_p, args_count);
  }
  else
  {
    if (retval_p != NULL)
    {
      jerry_api_construct_type_error (retval_p);
    }

    return false;
  }
} /* jerry_api_construct_object */

/**
 * Get global object
 *
 * Note:
 *       caller should release the object with jerry_api_release_object, just when the value becomes unnecessary.
 *
 * @return pointer to the global object
 */
jerry_api_object_t*
jerry_api_get_global (void)
{
  jerry_assert_api_available ();

  return ecma_builtin_get (ECMA_BUILTIN_ID_GLOBAL);
} /* jerry_api_get_global */

/**
 * Perform eval
 *
 * Note:
 *      If current code is executed on top of interpreter, using is_direct argument,
 *      caller can enable direct eval mode that is equivalent to calling eval from
 *      within of current JS execution context.
 *
 * @return completion status
 */
jerry_completion_code_t
jerry_api_eval (const char *source_p, /**< source code */
                size_t source_size, /**< length of source code */
                bool is_direct, /**< perform eval invocation in direct mode */
                bool is_strict, /**< perform eval as it is called from strict mode code */
                jerry_api_value_t *retval_p) /**< out: returned value */
{
  jerry_assert_api_available ();

  jerry_completion_code_t status;

  ecma_completion_value_t completion = ecma_op_eval_chars_buffer ((const ecma_char_t*) source_p,
                                                                  source_size,
                                                                  is_direct,
                                                                  is_strict);

  if (ecma_is_completion_value_normal (completion))
  {
    status = JERRY_COMPLETION_CODE_OK;

    jerry_api_convert_ecma_value_to_api_value (retval_p,
                                               ecma_get_completion_value_value (completion));
  }
  else
  {
    jerry_api_convert_ecma_value_to_api_value (retval_p, ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED));

    if (ecma_is_completion_value_throw (completion))
    {
      status = JERRY_COMPLETION_CODE_UNHANDLED_EXCEPTION;
    }
    else
    {
      JERRY_ASSERT (ecma_is_completion_value_exit (completion));

      if (ecma_is_value_true (ecma_get_completion_value_value (completion)))
      {
        status = JERRY_COMPLETION_CODE_OK;
      }
      else
      {
        status = JERRY_COMPLETION_CODE_FAILED_ASSERTION_IN_SCRIPT;
      }
    }
  }

  ecma_free_completion_value (completion);

  return status;
} /* jerry_api_eval */

/**
 * Jerry engine initialization
 */
void
jerry_init (jerry_flag_t flags) /**< combination of Jerry flags */
{
  if (flags & (JERRY_FLAG_ENABLE_LOG))
  {
#ifndef JERRY_ENABLE_LOG
    JERRY_WARNING_MSG ("Ignoring log options because of '!JERRY_ENABLE_LOG' build configuration.\n");
#endif /* !JERRY_ENABLE_LOG */
  }

  if (flags & (JERRY_FLAG_MEM_STATS))
  {
#ifndef MEM_STATS
    flags &= ~(JERRY_FLAG_MEM_STATS
               | JERRY_FLAG_MEM_STATS_PER_OPCODE
               | JERRY_FLAG_MEM_STATS_SEPARATE);

    JERRY_WARNING_MSG ("Ignoring memory statistics option because of '!MEM_STATS' build configuration.\n");
#endif /* !MEM_STATS */
  }
  else if (flags & (JERRY_FLAG_MEM_STATS_PER_OPCODE | JERRY_FLAG_MEM_STATS_SEPARATE))
  {
    flags &= ~(JERRY_FLAG_MEM_STATS_PER_OPCODE | JERRY_FLAG_MEM_STATS_SEPARATE);

    JERRY_WARNING_MSG (
      "Ignoring detailed memory statistics options because memory statistics dump mode is not enabled.\n");
  }

  jerry_flags = flags;

  jerry_make_api_available ();

  mem_init ();
  serializer_init ();
  ecma_init ();
} /* jerry_init */

/**
 * Terminate Jerry engine
 */
void
jerry_cleanup (void)
{
  jerry_assert_api_available ();

  bool is_show_mem_stats = ((jerry_flags & JERRY_FLAG_MEM_STATS) != 0);

  ecma_finalize ();
  serializer_free ();
  mem_finalize (is_show_mem_stats);
  vm_finalize ();
} /* jerry_cleanup */

/**
 * Get Jerry configured memory limits
 */
void
jerry_get_memory_limits (size_t *out_data_bss_brk_limit_p, /**< out: Jerry's maximum usage of
                                                            *        data + bss + brk sections */
                         size_t *out_stack_limit_p) /**< out: Jerry's maximum usage of stack */
{
  *out_data_bss_brk_limit_p = CONFIG_MEM_HEAP_AREA_SIZE + CONFIG_MEM_DATA_LIMIT_MINUS_HEAP_SIZE;
  *out_stack_limit_p = CONFIG_MEM_STACK_LIMIT;
} /* jerry_get_memory_limits */

/**
 * Check whether 'abort' should be called instead of 'exit' upon exiting with non-zero exit code.
 *
 * @return true - if 'abort on fail' flag is set,
 *         false - otherwise.
 */
bool
jerry_is_abort_on_fail (void)
{
  return ((jerry_flags & JERRY_FLAG_ABORT_ON_FAIL) != 0);
} /* jerry_is_abort_on_fail */

/**
 * Register Jerry's fatal error callback
 */
void
jerry_reg_err_callback (jerry_error_callback_t callback) /**< pointer to callback function */
{
  jerry_assert_api_available ();

  JERRY_UNIMPLEMENTED_REF_UNUSED_VARS ("Error callback is not implemented", callback);
} /* jerry_reg_err_callback */

/**
 * Parse script for specified context
 *
 * @return true - if script was parsed successfully,
 *         false - otherwise (SyntaxError was raised).
 */
bool
jerry_parse (const char* source_p, /**< script source */
             size_t source_size) /**< script source size */
{
  jerry_assert_api_available ();

  bool is_show_opcodes = ((jerry_flags & JERRY_FLAG_SHOW_OPCODES) != 0);

  parser_set_show_opcodes (is_show_opcodes);

  const opcode_t *opcodes_p;
  bool is_syntax_correct;

  is_syntax_correct = parser_parse_script (source_p,
                                           source_size,
                                           &opcodes_p);

  if (!is_syntax_correct)
  {
    return false;
  }

#ifdef MEM_STATS
  if (jerry_flags & JERRY_FLAG_MEM_STATS_SEPARATE)
  {
    mem_stats_print ();
    mem_stats_reset_peak ();
  }
#endif /* MEM_STATS */

  bool is_show_mem_stats_per_opcode = ((jerry_flags & JERRY_FLAG_MEM_STATS_PER_OPCODE) != 0);

  vm_init (opcodes_p, is_show_mem_stats_per_opcode);

  return true;
} /* jerry_parse */

/**
 * Run Jerry in specified run context
 *
 * @return completion status
 */
jerry_completion_code_t
jerry_run (void)
{
  jerry_assert_api_available ();

  return vm_run_global ();
} /* jerry_run */
/**
 * Simple jerry runner
 *
 * @return completion status
 */
jerry_completion_code_t
jerry_run_simple (const char *script_source, /**< script source */
                  size_t script_source_size, /**< script source size */
                  jerry_flag_t flags) /**< combination of Jerry flags */
{
  jerry_init (flags);

  jerry_completion_code_t ret_code = JERRY_COMPLETION_CODE_OK;

  if (!jerry_parse (script_source, script_source_size))
  {
    /* unhandled SyntaxError */
    ret_code = JERRY_COMPLETION_CODE_UNHANDLED_EXCEPTION;
  }
  else
  {
    if ((flags & JERRY_FLAG_PARSE_ONLY) == 0)
    {
      ret_code = jerry_run ();
    }
  }

  jerry_cleanup ();

  return ret_code;
} /* jerry_run_simple */

#ifdef CONFIG_JERRY_ENABLE_CONTEXTS
/**
 * Allocate new run context
 *
 * @return run context
 */
jerry_ctx_t*
jerry_new_ctx (void)
{
  jerry_assert_api_available ();

  JERRY_UNIMPLEMENTED ("Run contexts are not implemented");
} /* jerry_new_ctx */

/**
 * Cleanup resources associated with specified run context
 */
void
jerry_cleanup_ctx (jerry_ctx_t* ctx_p) /**< run context */
{
  jerry_assert_api_available ();

  JERRY_UNIMPLEMENTED_REF_UNUSED_VARS ("Run contexts are not implemented", ctx_p);
} /* jerry_cleanup_ctx */

/**
 * Activate context and push it to contexts' stack
 */
void
jerry_push_ctx (jerry_ctx_t *ctx_p) /**< run context */
{
  jerry_assert_api_available ();

  JERRY_UNIMPLEMENTED_REF_UNUSED_VARS ("Run contexts are not implemented", ctx_p);
} /* jerry_push_ctx */

/**
 * Pop from contexts' stack and activate new stack's top
 *
 * Note:
 *      default context (most placed on bottom of stack) cannot be popped
 */
void
jerry_pop_ctx (void)
{
  jerry_assert_api_available ();

  JERRY_UNIMPLEMENTED ("Run contexts are not implemented");
} /* jerry_pop_ctx */
#endif /* CONFIG_JERRY_ENABLE_CONTEXTS */


/**
 * Register external magic string array
 */
void
jerry_register_external_magic_strings (const jerry_api_char_ptr_t* ex_str_items, /**< character arrays, representing
                                                                                  * external magic strings' contents */
                                       uint32_t count,                           /**< number of the strings */
                                       const jerry_api_length_t* str_lengths)    /**< lengths of the strings */
{
  ecma_strings_ex_set ((const ecma_char_ptr_t*)ex_str_items, count, (const ecma_length_t*)str_lengths);
} /* jerry_register_external_magic_strings */
