/* Copyright JS Foundation and other contributors, http://js.foundation
 *
 * 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 "ecma-alloc.h"
#include "ecma-conversion.h"
#include "ecma-exceptions.h"
#include "ecma-eval.h"
#include "ecma-gc.h"
#include "ecma-function-object.h"
#include "ecma-lex-env.h"
#include "ecma-try-catch-macro.h"
#include "lit-magic-strings.h"
#include "js-parser.h"

#define ECMA_BUILTINS_INTERNAL
#include "ecma-builtins-internal.h"

#define BUILTIN_INC_HEADER_NAME "ecma-builtin-function.inc.h"
#define BUILTIN_UNDERSCORED_ID function
#include "ecma-builtin-internal-routines-template.inc.h"

/** \addtogroup ecma ECMA
 * @{
 *
 * \addtogroup ecmabuiltins
 * @{
 *
 * \addtogroup function ECMA Function object built-in
 * @{
 */

/**
 * Handle calling [[Call]] of built-in Function object
 *
 * @return ecma value
 */
ecma_value_t
ecma_builtin_function_dispatch_call (const ecma_value_t *arguments_list_p, /**< arguments list */
                                     ecma_length_t arguments_list_len) /**< number of arguments */
{
  JERRY_ASSERT (arguments_list_len == 0 || arguments_list_p != NULL);

  return ecma_builtin_function_dispatch_construct (arguments_list_p, arguments_list_len);
} /* ecma_builtin_function_dispatch_call */

/**
 * Helper method to count and convert the arguments for the Function constructor call.
 *
 * Performs the operation described in ECMA 262 v5.1 15.3.2.1 steps 5.a-d
 *
 *
 * @return ecma value - concatenated arguments as a string.
 *         Returned value must be freed with ecma_free_value.
 */
static ecma_value_t
ecma_builtin_function_helper_get_function_expression (const ecma_value_t *arguments_list_p, /**< arguments list */
                                                      ecma_length_t arguments_list_len) /**< number of arguments */
{
  JERRY_ASSERT (arguments_list_len == 0 || arguments_list_p != NULL);

  ecma_value_t ret_value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_EMPTY);

  ecma_string_t *left_parenthesis_str_p, *right_parenthesis_str_p;
  ecma_string_t *left_brace_str_p, *right_brace_str_p;
  ecma_string_t *comma_str_p;
  ecma_string_t *function_kw_str_p, *empty_str_p;
  ecma_string_t *expr_str_p, *concated_str_p;

  left_parenthesis_str_p = ecma_new_ecma_string_from_magic_string_id (LIT_MAGIC_STRING_LEFT_PARENTHESIS_CHAR);
  right_parenthesis_str_p = ecma_new_ecma_string_from_magic_string_id (LIT_MAGIC_STRING_RIGHT_PARENTHESIS_CHAR);

  left_brace_str_p = ecma_new_ecma_string_from_magic_string_id (LIT_MAGIC_STRING_LEFT_BRACE_CHAR);
  right_brace_str_p = ecma_new_ecma_string_from_magic_string_id (LIT_MAGIC_STRING_RIGHT_BRACE_CHAR);

  comma_str_p = ecma_get_magic_string (LIT_MAGIC_STRING_COMMA_CHAR);

  function_kw_str_p = ecma_new_ecma_string_from_magic_string_id (LIT_MAGIC_STRING_FUNCTION);
  empty_str_p = ecma_new_ecma_string_from_magic_string_id (LIT_MAGIC_STRING__EMPTY);

  /* First, we only process the function arguments skipping the function body */
  ecma_length_t number_of_function_args = (arguments_list_len == 0 ? 0 : arguments_list_len - 1);

  expr_str_p = ecma_concat_ecma_strings (left_parenthesis_str_p, function_kw_str_p);

  concated_str_p = ecma_concat_ecma_strings (expr_str_p, left_parenthesis_str_p);
  ecma_deref_ecma_string (expr_str_p);
  expr_str_p = concated_str_p;

  for (ecma_length_t idx = 0;
       idx < number_of_function_args && ecma_is_value_empty (ret_value);
       idx++)
  {
    ECMA_TRY_CATCH (str_arg_value,
                    ecma_op_to_string (arguments_list_p[idx]),
                    ret_value);

    ecma_string_t *str_p = ecma_get_string_from_value (str_arg_value);

    concated_str_p = ecma_concat_ecma_strings (expr_str_p, str_p);
    ecma_deref_ecma_string (expr_str_p);
    expr_str_p = concated_str_p;

    if (idx < number_of_function_args - 1)
    {
      concated_str_p = ecma_concat_ecma_strings (expr_str_p, comma_str_p);
      ecma_deref_ecma_string (expr_str_p);
      expr_str_p = concated_str_p;
    }

    ECMA_FINALIZE (str_arg_value);
  }

  if (ecma_is_value_empty (ret_value))
  {
    concated_str_p = ecma_concat_ecma_strings (expr_str_p, right_parenthesis_str_p);
    ecma_deref_ecma_string (expr_str_p);
    expr_str_p = concated_str_p;

    concated_str_p = ecma_concat_ecma_strings (expr_str_p, left_brace_str_p);
    ecma_deref_ecma_string (expr_str_p);
    expr_str_p = concated_str_p;

    if (arguments_list_len != 0)
    {
      ECMA_TRY_CATCH (str_arg_value,
                      ecma_op_to_string (arguments_list_p[arguments_list_len - 1]),
                      ret_value);

      ecma_string_t *body_str_p = ecma_get_string_from_value (str_arg_value);

      concated_str_p = ecma_concat_ecma_strings (expr_str_p, body_str_p);
      ecma_deref_ecma_string (expr_str_p);
      expr_str_p = concated_str_p;

      ECMA_FINALIZE (str_arg_value);
    }

    concated_str_p = ecma_concat_ecma_strings (expr_str_p, right_brace_str_p);
    ecma_deref_ecma_string (expr_str_p);
    expr_str_p = concated_str_p;

    concated_str_p = ecma_concat_ecma_strings (expr_str_p, right_parenthesis_str_p);
    ecma_deref_ecma_string (expr_str_p);
    expr_str_p = concated_str_p;
  }

  ecma_deref_ecma_string (left_parenthesis_str_p);
  ecma_deref_ecma_string (right_parenthesis_str_p);
  ecma_deref_ecma_string (left_brace_str_p);
  ecma_deref_ecma_string (right_brace_str_p);
  ecma_deref_ecma_string (comma_str_p);
  ecma_deref_ecma_string (function_kw_str_p);
  ecma_deref_ecma_string (empty_str_p);

  if (ecma_is_value_empty (ret_value))
  {
    ret_value = ecma_make_string_value (expr_str_p);
  }
  else
  {
    ecma_deref_ecma_string (expr_str_p);
  }

  return ret_value;
} /* ecma_builtin_function_helper_get_function_expression */

/**
 * Handle calling [[Construct]] of built-in Function object
 *
 * See also:
 *          ECMA-262 v5, 15.3.
 *
 * @return ecma value
 */
ecma_value_t
ecma_builtin_function_dispatch_construct (const ecma_value_t *arguments_list_p, /**< arguments list */
                                          ecma_length_t arguments_list_len) /**< number of arguments */
{
  JERRY_ASSERT (arguments_list_len == 0 || arguments_list_p != NULL);

  ecma_value_t ret_value = ecma_make_simple_value (ECMA_SIMPLE_VALUE_EMPTY);

  ECMA_TRY_CATCH (arguments_value,
                  ecma_builtin_function_helper_get_function_expression (arguments_list_p,
                                                                        arguments_list_len),
                  ret_value);

  ecma_string_t *function_expression_p = ecma_get_string_from_value (arguments_value);

  ret_value = ecma_op_eval (function_expression_p, false, false);

  ECMA_FINALIZE (arguments_value);

  return ret_value;
} /* ecma_builtin_function_dispatch_construct */

/**
 * @}
 * @}
 * @}
 */
