/* 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-builtins.h"
#include "ecma-exceptions.h"
#include "ecma-gc.h"
#include "ecma-globals.h"
#include "ecma-helpers.h"
#include "ecma-iterator-object.h"

#include "jcontext.h"
#include "opcodes.h"
#include "vm-defines.h"

#if JERRY_ESNEXT

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

/**
 * This object has a custom dispatch function.
 */
#define BUILTIN_CUSTOM_DISPATCH

/**
 * List of built-in routine identifiers.
 */
enum
{
  ECMA_GENERATOR_PROTOTYPE_ROUTINE_START = 0,
  ECMA_GENERATOR_PROTOTYPE_ROUTINE_NEXT,
  ECMA_GENERATOR_PROTOTYPE_ROUTINE_THROW,
  ECMA_GENERATOR_PROTOTYPE_ROUTINE_RETURN
} ecma_generator_operation_type_t;

#define BUILTIN_INC_HEADER_NAME "ecma-builtin-generator-prototype.inc.h"
#define BUILTIN_UNDERSCORED_ID  generator_prototype
#include "ecma-builtin-internal-routines-template.inc.h"

/** \addtogroup ecma ECMA
 * @{
 *
 * \addtogroup ecmabuiltins
 * @{
 *
 * \addtogroup generator ECMA Generator.prototype object built-in
 * @{
 */

/**
 * Convert routine type to operation type..
 */
#define ECMA_GENERATOR_ROUTINE_TO_OPERATION(type) \
  ((ecma_iterator_command_type_t) ((type) -ECMA_GENERATOR_PROTOTYPE_ROUTINE_NEXT))

JERRY_STATIC_ASSERT (ECMA_GENERATOR_ROUTINE_TO_OPERATION (ECMA_GENERATOR_PROTOTYPE_ROUTINE_NEXT) == ECMA_ITERATOR_NEXT,
                     convert_ecma_generator_routine_next_to_ecma_iterator_next_failed);

JERRY_STATIC_ASSERT (ECMA_GENERATOR_ROUTINE_TO_OPERATION (ECMA_GENERATOR_PROTOTYPE_ROUTINE_THROW)
                       == ECMA_ITERATOR_THROW,
                     convert_ecma_generator_routine_throw_to_ecma_iterator_throw_failed);

JERRY_STATIC_ASSERT (ECMA_GENERATOR_ROUTINE_TO_OPERATION (ECMA_GENERATOR_PROTOTYPE_ROUTINE_RETURN)
                       == ECMA_ITERATOR_RETURN,
                     convert_ecma_generator_routine_return_to_ecma_iterator_return_failed);

/**
 * Helper function for next / return / throw
 *
 * @return ecma value
 *         Returned value must be freed with ecma_free_value.
 */
static ecma_value_t
ecma_builtin_generator_prototype_object_do (vm_executable_object_t *generator_object_p, /**< generator object */
                                            ecma_value_t arg, /**< argument */
                                            ecma_iterator_command_type_t resume_mode) /**< resume mode */
{
  arg = ecma_copy_value (arg);

  while (true)
  {
    if (generator_object_p->extended_object.u.cls.u2.executable_obj_flags & ECMA_EXECUTABLE_OBJECT_DO_AWAIT_OR_YIELD)
    {
      ecma_value_t iterator = generator_object_p->iterator;
      ecma_value_t next_method = generator_object_p->frame_ctx.stack_top_p[-1];

      bool done = false;
      ecma_value_t result = ecma_op_iterator_do (resume_mode, iterator, next_method, arg, &done);
      ecma_free_value (arg);

      if (ECMA_IS_VALUE_ERROR (result))
      {
        arg = result;
      }
      else if (done)
      {
        arg = ecma_op_iterator_value (result);
        ecma_free_value (result);

        if (resume_mode == ECMA_ITERATOR_THROW)
        {
          resume_mode = ECMA_ITERATOR_NEXT;
        }
      }
      else
      {
        return result;
      }

      ECMA_EXECUTABLE_OBJECT_RESUME_EXEC (generator_object_p);
      generator_object_p->iterator = ECMA_VALUE_UNDEFINED;

      JERRY_ASSERT (generator_object_p->frame_ctx.stack_top_p[-1] == ECMA_VALUE_UNDEFINED
                    || ecma_is_value_object (generator_object_p->frame_ctx.stack_top_p[-1]));
      generator_object_p->frame_ctx.stack_top_p--;

      if (ECMA_IS_VALUE_ERROR (arg))
      {
        arg = jcontext_take_exception ();
        resume_mode = ECMA_ITERATOR_THROW;
      }
    }

    if (resume_mode == ECMA_ITERATOR_RETURN)
    {
      generator_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_return;
    }
    else if (resume_mode == ECMA_ITERATOR_THROW)
    {
      generator_object_p->frame_ctx.byte_code_p = opfunc_resume_executable_object_with_throw;
    }

    ecma_value_t value = opfunc_resume_executable_object (generator_object_p, arg);

    if (ECMA_IS_VALUE_ERROR (value))
    {
      return value;
    }

    bool done;
    done = (generator_object_p->extended_object.u.cls.u2.executable_obj_flags & ECMA_EXECUTABLE_OBJECT_COMPLETED);

    if (!done)
    {
      const uint8_t *byte_code_p = generator_object_p->frame_ctx.byte_code_p;

      JERRY_ASSERT (byte_code_p[-2] == CBC_EXT_OPCODE
                    && (byte_code_p[-1] == CBC_EXT_YIELD || byte_code_p[-1] == CBC_EXT_YIELD_ITERATOR));

      if (byte_code_p[-1] == CBC_EXT_YIELD_ITERATOR)
      {
        ecma_value_t iterator =
          ecma_op_get_iterator (value, ECMA_VALUE_SYNC_ITERATOR, generator_object_p->frame_ctx.stack_top_p);
        ecma_free_value (value);

        if (ECMA_IS_VALUE_ERROR (iterator))
        {
          resume_mode = ECMA_ITERATOR_THROW;
          arg = jcontext_take_exception ();
          continue;
        }

        ecma_deref_object (ecma_get_object_from_value (iterator));
        generator_object_p->extended_object.u.cls.u2.executable_obj_flags |= ECMA_EXECUTABLE_OBJECT_DO_AWAIT_OR_YIELD;
        generator_object_p->iterator = iterator;

        if (generator_object_p->frame_ctx.stack_top_p[0] != ECMA_VALUE_UNDEFINED)
        {
          ecma_deref_object (ecma_get_object_from_value (generator_object_p->frame_ctx.stack_top_p[0]));
        }

        generator_object_p->frame_ctx.stack_top_p++;
        arg = ECMA_VALUE_UNDEFINED;
        continue;
      }
    }

    ecma_value_t result = ecma_create_iter_result_object (value, ecma_make_boolean_value (done));
    ecma_fast_free_value (value);
    return result;
  }
} /* ecma_builtin_generator_prototype_object_do */

/**
 * Dispatcher of the Generator built-in's routines
 *
 * @return ecma value
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_builtin_generator_prototype_dispatch_routine (uint8_t builtin_routine_id, /**< built-in wide routine
                                                                                *   identifier */
                                                   ecma_value_t this_arg, /**< 'this' argument value */
                                                   const ecma_value_t arguments_list_p[], /**< list of arguments
                                                                                           *   passed to routine */
                                                   uint32_t arguments_number) /**< length of arguments' list */
{
  JERRY_UNUSED (arguments_number);

  vm_executable_object_t *executable_object_p = NULL;

  if (ecma_is_value_object (this_arg))
  {
    ecma_object_t *object_p = ecma_get_object_from_value (this_arg);

    if (ecma_object_class_is (object_p, ECMA_OBJECT_CLASS_GENERATOR))
    {
      executable_object_p = (vm_executable_object_t *) object_p;
    }
  }

  if (executable_object_p == NULL)
  {
    return ecma_raise_type_error (ECMA_ERR_MSG ("Argument 'this' is not a generator object"));
  }

  if (executable_object_p->extended_object.u.cls.u2.executable_obj_flags & ECMA_EXECUTABLE_OBJECT_RUNNING)
  {
    return ecma_raise_type_error (ECMA_ERR_MSG ("Generator is currently under execution"));
  }

  if (executable_object_p->extended_object.u.cls.u2.executable_obj_flags & ECMA_EXECUTABLE_OBJECT_COMPLETED)
  {
    if (builtin_routine_id != ECMA_GENERATOR_PROTOTYPE_ROUTINE_THROW)
    {
      return ecma_create_iter_result_object (ECMA_VALUE_UNDEFINED, ECMA_VALUE_TRUE);
    }

    jcontext_raise_exception (ecma_copy_value (arguments_list_p[0]));
    return ECMA_VALUE_ERROR;
  }

  return ecma_builtin_generator_prototype_object_do (executable_object_p,
                                                     arguments_list_p[0],
                                                     ECMA_GENERATOR_ROUTINE_TO_OPERATION (builtin_routine_id));
} /* ecma_builtin_generator_prototype_dispatch_routine */

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

#endif /* JERRY_ESNEXT */
