/* 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 "jerryscript.h"
#include "jerryscript-port.h"
#include "jerryscript-port-default.h"
#include "test-common.h"
#include <gtest/gtest.h>

#define T(op, lhs, rhs, res) \
  { op, lhs, rhs, res }

#define T_NAN(op, lhs, rhs) \
  { op, lhs, rhs }

#define T_ERR(op, lsh, rhs) \
  T_NAN (op, lsh, rhs)

#define T_ARI(lhs, rhs) \
  T_NAN (JERRY_BIN_OP_SUB, lhs, rhs), \
  T_NAN (JERRY_BIN_OP_MUL, lhs, rhs), \
  T_NAN (JERRY_BIN_OP_DIV, lhs, rhs), \
  T_NAN (JERRY_BIN_OP_REM, lhs, rhs)

typedef struct
{
  jerry_binary_operation_t op;
  jerry_value_t lhs;
  jerry_value_t rhs;
  jerry_value_t expected;
} test_entry_t;

typedef struct
{
  jerry_binary_operation_t op;
  jerry_value_t lhs;
  jerry_value_t rhs;
} test_nan_entry_t;

typedef test_nan_entry_t test_error_entry_t;
class ApiBinaryOperationsArithmeticsTest : public testing::Test{
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "ApiBinaryOperationsArithmeticsTest SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "ApiBinaryOperationsArithmeticsTest TearDownTestCase";
    }

    void SetUp() override {}
    void TearDown() override {}

};

static constexpr size_t JERRY_SCRIPT_MEM_SIZE = 50 * 1024 * 1024;
static void* context_alloc_fn(size_t size, void* cb_data)
{
    (void)cb_data;
    size_t newSize = size > JERRY_SCRIPT_MEM_SIZE ? JERRY_SCRIPT_MEM_SIZE : size;
    return malloc(newSize);
}
HWTEST_F(ApiBinaryOperationsArithmeticsTest, Test001, testing::ext::TestSize.Level1)
{
  jerry_context_t* ctx_p = jerry_create_context(1024 * 1024 * 50, context_alloc_fn, NULL);
  jerry_port_default_set_current_context(ctx_p);
  TEST_INIT ();

  jerry_init (JERRY_INIT_EMPTY);

  jerry_value_t obj1 = jerry_eval ((const jerry_char_t *) "o={x:1};o", 9, JERRY_PARSE_NO_OPTS);
  jerry_value_t obj2 = jerry_eval ((const jerry_char_t *) "o={x:1};o", 9, JERRY_PARSE_NO_OPTS);
  jerry_value_t err1 = jerry_create_error (JERRY_ERROR_SYNTAX, (const jerry_char_t *) "error");

  test_nan_entry_t test_nans[] =
  {
    /* Testing addition (+) */
    T_NAN (JERRY_BIN_OP_ADD, jerry_create_number (3.1), jerry_create_undefined ()),
    T_NAN (JERRY_BIN_OP_ADD, jerry_create_undefined (), jerry_create_undefined ()),
    T_NAN (JERRY_BIN_OP_ADD, jerry_create_undefined (), jerry_create_null ()),

    /* Testing subtraction (-), multiplication (*), division (/), remainder (%) */
    T_ARI (jerry_create_number (3.1), jerry_create_undefined ()),
    T_ARI (jerry_create_string ((const jerry_char_t *) "foo"), jerry_create_string ((const jerry_char_t *) "bar")),
    T_ARI (jerry_create_string ((const jerry_char_t *) "foo"), jerry_create_undefined ()),
    T_ARI (jerry_create_string ((const jerry_char_t *) "foo"), jerry_create_null ()),
    T_ARI (jerry_create_string ((const jerry_char_t *) "foo"), jerry_create_number (5.0)),
    T_ARI (jerry_create_undefined (), jerry_create_string ((const jerry_char_t *) "foo")),
    T_ARI (jerry_create_null (), jerry_create_string ((const jerry_char_t *) "foo")),
    T_ARI (jerry_create_number (5.0), jerry_create_string ((const jerry_char_t *) "foo")),
    T_ARI (jerry_create_undefined (), jerry_create_undefined ()),
    T_ARI (jerry_create_undefined (), jerry_create_null ()),
    T_ARI (jerry_create_null (), jerry_create_undefined ()),
    T_ARI (jerry_acquire_value (obj1), jerry_acquire_value (obj1)),
    T_ARI (jerry_acquire_value (obj1), jerry_acquire_value (obj2)),
    T_ARI (jerry_acquire_value (obj2), jerry_acquire_value (obj1)),
    T_ARI (jerry_acquire_value (obj2), jerry_create_undefined ()),
    T_ARI (jerry_acquire_value (obj1), jerry_create_string ((const jerry_char_t *) "foo")),
    T_ARI (jerry_acquire_value (obj1), jerry_create_null ()),
    T_ARI (jerry_acquire_value (obj1), jerry_create_boolean (true)),
    T_ARI (jerry_acquire_value (obj1), jerry_create_boolean (false)),
    T_ARI (jerry_acquire_value (obj1), jerry_create_number (5.0)),

    /* Testing division (/) */
    T_NAN (JERRY_BIN_OP_DIV, jerry_create_boolean (false), jerry_create_boolean (false)),
    T_NAN (JERRY_BIN_OP_DIV, jerry_create_number (0.0), jerry_create_number (0.0)),
    T_NAN (JERRY_BIN_OP_DIV, jerry_create_null (), jerry_create_null ()),

    /* Testing remainder (%) */
    T_NAN (JERRY_BIN_OP_REM, jerry_create_boolean (true), jerry_create_boolean (false)),
    T_NAN (JERRY_BIN_OP_REM, jerry_create_boolean (false), jerry_create_boolean (false)),
    T_NAN (JERRY_BIN_OP_REM, jerry_create_number (0.0), jerry_create_number (0.0)),
    T_NAN (JERRY_BIN_OP_REM, jerry_create_null (), jerry_create_null ()),
  };

  for (uint32_t idx = 0; idx < sizeof (test_nans) / sizeof (test_nan_entry_t); idx++)
  {
    jerry_value_t result = jerry_binary_operation (test_nans[idx].op, test_nans[idx].lhs, test_nans[idx].rhs);
    TEST_ASSERT (jerry_value_is_number (result));

    double num = jerry_get_number_value (result);

    TEST_ASSERT (num != num);

    jerry_release_value (test_nans[idx].lhs);
    jerry_release_value (test_nans[idx].rhs);
    jerry_release_value (result);
  }

  test_entry_t tests[] =
  {
    /* Testing addition (+) */
    T (JERRY_BIN_OP_ADD, jerry_create_number (5.0), jerry_create_number (5.0), jerry_create_number (10.0)),
    T (JERRY_BIN_OP_ADD, jerry_create_number (3.1), jerry_create_number (10), jerry_create_number (13.1)),
    T (JERRY_BIN_OP_ADD, jerry_create_number (3.1), jerry_create_boolean (true), jerry_create_number (4.1)),
    T (JERRY_BIN_OP_ADD,
       jerry_create_string ((const jerry_char_t *) "foo"),
       jerry_create_string ((const jerry_char_t *) "bar"),
       jerry_create_string ((const jerry_char_t *) "foobar")),
    T (JERRY_BIN_OP_ADD,
       jerry_create_string ((const jerry_char_t *) "foo"),
       jerry_create_undefined (),
       jerry_create_string ((const jerry_char_t *) "fooundefined")),
    T (JERRY_BIN_OP_ADD,
       jerry_create_string ((const jerry_char_t *) "foo"),
       jerry_create_null (),
       jerry_create_string ((const jerry_char_t *) "foonull")),
    T (JERRY_BIN_OP_ADD,
       jerry_create_string ((const jerry_char_t *) "foo"),
       jerry_create_number (5.0),
       jerry_create_string ((const jerry_char_t *) "foo5")),

    T (JERRY_BIN_OP_ADD, jerry_create_null (), jerry_create_null (), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_ADD, jerry_create_boolean (true), jerry_create_boolean (true), jerry_create_number (2.0)),
    T (JERRY_BIN_OP_ADD, jerry_create_boolean (true), jerry_create_boolean (false), jerry_create_number (1.0)),
    T (JERRY_BIN_OP_ADD, jerry_create_boolean (false), jerry_create_boolean (true), jerry_create_number (1.0)),
    T (JERRY_BIN_OP_ADD, jerry_create_boolean (false), jerry_create_boolean (false), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj1),
       jerry_acquire_value (obj1),
       jerry_create_string ((const jerry_char_t *) "[object Object][object Object]")),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj1),
       jerry_acquire_value (obj2),
       jerry_create_string ((const jerry_char_t *) "[object Object][object Object]")),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj2),
       jerry_acquire_value (obj1),
       jerry_create_string ((const jerry_char_t *) "[object Object][object Object]")),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj1),
       jerry_create_null (),
       jerry_create_string ((const jerry_char_t *) "[object Object]null")),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj1),
       jerry_create_undefined (),
       jerry_create_string ((const jerry_char_t *) "[object Object]undefined")),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj1),
       jerry_create_boolean (true),
       jerry_create_string ((const jerry_char_t *) "[object Object]true")),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj1),
       jerry_create_boolean (false),
       jerry_create_string ((const jerry_char_t *) "[object Object]false")),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj1),
       jerry_create_number (5.0),
       jerry_create_string ((const jerry_char_t *) "[object Object]5")),
    T (JERRY_BIN_OP_ADD,
       jerry_acquire_value (obj1),
       jerry_create_string ((const jerry_char_t *) "foo"),
       jerry_create_string ((const jerry_char_t *) "[object Object]foo")),

    /* Testing subtraction (-) */
    T (JERRY_BIN_OP_SUB, jerry_create_number (5.0), jerry_create_number (5.0), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_SUB, jerry_create_number (3.1), jerry_create_number (10), jerry_create_number (-6.9)),
    T (JERRY_BIN_OP_SUB, jerry_create_number (3.1), jerry_create_boolean (true), jerry_create_number (2.1)),
    T (JERRY_BIN_OP_SUB, jerry_create_boolean (true), jerry_create_boolean (true), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_SUB, jerry_create_boolean (true), jerry_create_boolean (false), jerry_create_number (1.0)),
    T (JERRY_BIN_OP_SUB, jerry_create_boolean (false), jerry_create_boolean (true), jerry_create_number (-1.0)),
    T (JERRY_BIN_OP_SUB, jerry_create_boolean (false), jerry_create_boolean (false), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_SUB, jerry_create_null (), jerry_create_null (), jerry_create_number (-0.0)),

    /* Testing multiplication (*) */
    T (JERRY_BIN_OP_MUL, jerry_create_number (5.0), jerry_create_number (5.0), jerry_create_number (25.0)),
    T (JERRY_BIN_OP_MUL, jerry_create_number (3.1), jerry_create_number (10), jerry_create_number (31)),
    T (JERRY_BIN_OP_MUL, jerry_create_number (3.1), jerry_create_boolean (true), jerry_create_number (3.1)),
    T (JERRY_BIN_OP_MUL, jerry_create_boolean (true), jerry_create_boolean (true), jerry_create_number (1.0)),
    T (JERRY_BIN_OP_MUL, jerry_create_boolean (true), jerry_create_boolean (false), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_MUL, jerry_create_boolean (false), jerry_create_boolean (true), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_MUL, jerry_create_boolean (false), jerry_create_boolean (false), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_MUL, jerry_create_null (), jerry_create_null (), jerry_create_number (0.0)),

    /* Testing division (/) */
    T (JERRY_BIN_OP_DIV, jerry_create_number (5.0), jerry_create_number (5.0), jerry_create_number (1.0)),
    T (JERRY_BIN_OP_DIV, jerry_create_number (3.1), jerry_create_number (10), jerry_create_number (0.31)),
    T (JERRY_BIN_OP_DIV, jerry_create_number (3.1), jerry_create_boolean (true), jerry_create_number (3.1)),
    T (JERRY_BIN_OP_DIV, jerry_create_boolean (true), jerry_create_boolean (true), jerry_create_number (1.0)),
    T (JERRY_BIN_OP_DIV,
       jerry_create_boolean (true),
       jerry_create_boolean (false),
       jerry_create_number_infinity (false)),
    T (JERRY_BIN_OP_DIV, jerry_create_boolean (false), jerry_create_boolean (true), jerry_create_number (0.0)),

    /* Testing remainder (%) */
    T (JERRY_BIN_OP_REM, jerry_create_number (5.0), jerry_create_number (5.0), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_REM, jerry_create_number (5.0), jerry_create_number (2.0), jerry_create_number (1.0)),
    T (JERRY_BIN_OP_REM, jerry_create_number (3.1), jerry_create_number (10), jerry_create_number (3.1)),
    T (JERRY_BIN_OP_REM,
       jerry_create_number (3.1),
       jerry_create_boolean (true),
       jerry_create_number (0.10000000000000009)),
    T (JERRY_BIN_OP_REM, jerry_create_boolean (true), jerry_create_boolean (true), jerry_create_number (0.0)),
    T (JERRY_BIN_OP_REM, jerry_create_boolean (false), jerry_create_boolean (true), jerry_create_number (0.0)),

  };

  for (uint32_t idx = 0; idx < sizeof (tests) / sizeof (test_entry_t); idx++)
  {
    jerry_value_t result = jerry_binary_operation (tests[idx].op, tests[idx].lhs, tests[idx].rhs);
    TEST_ASSERT (!jerry_value_is_error (result));

    jerry_value_t equals = jerry_binary_operation (JERRY_BIN_OP_STRICT_EQUAL, result, tests[idx].expected);
    TEST_ASSERT (jerry_value_is_boolean (equals) && jerry_get_boolean_value (equals));
    jerry_release_value (equals);

    jerry_release_value (tests[idx].lhs);
    jerry_release_value (tests[idx].rhs);
    jerry_release_value (tests[idx].expected);
    jerry_release_value (result);
  }

  jerry_value_t obj3 = jerry_eval ((const jerry_char_t *) "o={valueOf:function(){throw 5}};o", 33, JERRY_PARSE_NO_OPTS);

  test_error_entry_t error_tests[] =
  {
    /* Testing addition (+) */
    T_ERR (JERRY_BIN_OP_ADD, jerry_acquire_value (err1), jerry_acquire_value (err1)),
    T_ERR (JERRY_BIN_OP_ADD, jerry_acquire_value (err1), jerry_create_undefined ()),
    T_ERR (JERRY_BIN_OP_ADD, jerry_create_undefined (), jerry_acquire_value (err1)),

    /* Testing subtraction (-), multiplication (*), division (/), remainder (%) */
    T_ARI (jerry_acquire_value (err1), jerry_acquire_value (err1)),
    T_ARI (jerry_acquire_value (err1), jerry_create_undefined ()),
    T_ARI (jerry_create_undefined (), jerry_acquire_value (err1)),

    /* Testing addition (+) */
    T_ERR (JERRY_BIN_OP_ADD, jerry_acquire_value (obj3), jerry_create_undefined ()),
    T_ERR (JERRY_BIN_OP_ADD, jerry_acquire_value (obj3), jerry_create_null ()),
    T_ERR (JERRY_BIN_OP_ADD, jerry_acquire_value (obj3), jerry_create_boolean (true)),
    T_ERR (JERRY_BIN_OP_ADD, jerry_acquire_value (obj3), jerry_create_boolean (false)),
    T_ERR (JERRY_BIN_OP_ADD, jerry_acquire_value (obj3), jerry_acquire_value (obj2)),
    T_ERR (JERRY_BIN_OP_ADD, jerry_acquire_value (obj3), jerry_create_string ((const jerry_char_t *) "foo")),

    /* Testing subtraction (-), multiplication (*), division (/), remainder (%) */
    T_ARI (jerry_acquire_value (obj3), jerry_create_undefined ()),
    T_ARI (jerry_acquire_value (obj3), jerry_create_null ()),
    T_ARI (jerry_acquire_value (obj3), jerry_create_boolean (true)),
    T_ARI (jerry_acquire_value (obj3), jerry_create_boolean (false)),
    T_ARI (jerry_acquire_value (obj3), jerry_acquire_value (obj2)),
    T_ARI (jerry_acquire_value (obj3), jerry_create_string ((const jerry_char_t *) "foo")),
  };

  for (uint32_t idx = 0; idx < sizeof (error_tests) / sizeof (test_error_entry_t); idx++)
  {
    jerry_value_t result = jerry_binary_operation (tests[idx].op, error_tests[idx].lhs, error_tests[idx].rhs);
    TEST_ASSERT (jerry_value_is_error (result));
    jerry_release_value (error_tests[idx].lhs);
    jerry_release_value (error_tests[idx].rhs);
    jerry_release_value (result);
  }

  jerry_release_value (obj1);
  jerry_release_value (obj2);
  jerry_release_value (obj3);
  jerry_release_value (err1);

  jerry_cleanup ();
  free (ctx_p);
}