/*
 * Copyright (c) 2023 shifter <shifter@axoflow.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * As an additional exemption you are allowed to compile & link against the
 * OpenSSL libraries as published by the OpenSSL project. See the file
 * COPYING for details.
 *
 */

#include <criterion/criterion.h>
#include "libtest/cr_template.h"

#include "filterx/filterx-object.h"
#include "filterx/object-primitive.h"
#include "filterx/expr-comparison.h"
#include "filterx/expr-condition.h"
#include "filterx/filterx-expr.h"
#include "filterx/expr-literal.h"
#include "filterx/object-string.h"
#include "filterx/object-null.h"
#include "filterx/object-datetime.h"
#include "filterx/object-message-value.h"
#include "filterx/expr-assign.h"
#include "filterx/expr-template.h"
#include "filterx/expr-message-ref.h"

#include "apphook.h"
#include "scratch-buffers.h"


FilterXExpr *
_string_to_filterXExpr(const gchar *str)
{
  return filterx_literal_new(filterx_string_new(str, -1));
}

gint
_assert_cmp_string_to_filterx_object(const char *str, FilterXObject *obj)
{
  cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(string)));
  gsize string_len;
  const gchar *string = filterx_string_get_value(obj, &string_len);
  return strcmp(string, str);
}

FilterXExpr *
_assert_assign_var(const char *var_name, FilterXExpr *value)
{
  FilterXExpr *control_variable = filterx_message_ref_expr_new(log_msg_get_value_handle(var_name));
  cr_assert(control_variable != NULL);

  return filterx_assign_new(control_variable, value);
}

void
_assert_set_test_variable(const char *var_name, FilterXExpr *expr)
{
  FilterXExpr *assign = _assert_assign_var(var_name, expr);
  cr_assert(assign != NULL);

  FilterXObject *assign_eval_res = filterx_expr_eval(assign);
  cr_assert(assign_eval_res != NULL);
  cr_assert(filterx_object_truthy(assign_eval_res));

  filterx_expr_unref(assign);
  filterx_object_unref(assign_eval_res);
}

FilterXObject *
_assert_get_test_variable(const char *var_name)
{
  FilterXExpr *control_variable = filterx_message_ref_expr_new(log_msg_get_value_handle(var_name));
  cr_assert(control_variable != NULL);
  FilterXObject *result = filterx_expr_eval(control_variable);
  filterx_expr_unref(control_variable);
  return result;
}

typedef struct _TestEnv
{
  LogMessage *msg;
  FilterXScope *scope;
  FilterXEvalContext context;
} TestEnv;

void
init_test(TestEnv *env)
{

  cr_assert(env != NULL);
  env->msg = create_sample_message();
  env->scope = filterx_scope_new();

  env->context = (FilterXEvalContext)
  {
    .msgs = &env->msg,
    .num_msg = 1,
    .template_eval_options = &DEFAULT_TEMPLATE_EVAL_OPTIONS,
    .scope = env->scope,
  };
  filterx_eval_set_context(&env->context);

}

void
deinit_test(const TestEnv *env)
{
  cr_assert(env != NULL);
  log_msg_unref(env->msg);
  filterx_scope_unref(env->scope);
  filterx_eval_set_context(NULL);
}

//// Actual tests

Test(expr_condition, test_control_variable_set_get)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  FilterXObject *control_value = _assert_get_test_variable("$control-value");

  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("default", control_value));

  filterx_object_unref(control_value);

  deinit_test(&env);

}


Test(expr_condition, test_condition_with_null_expr_must_evaluated)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));

  FilterXExpr *cond = filterx_conditional_new_codeblock(stmts);
  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  cr_assert(filterx_object_truthy(cond_eval) == TRUE);

  FilterXObject *control_value = _assert_get_test_variable("$control-value");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("matching", control_value));

  filterx_expr_unref(cond);
  filterx_object_unref(control_value);
  filterx_object_unref(cond_eval);
  ////

  deinit_test(&env);
}

Test(expr_condition, test_condition_matching_expression)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(true)),
                      stmts);
  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  cr_assert(filterx_object_truthy(cond_eval) == TRUE);

  FilterXObject *control_value = _assert_get_test_variable("$control-value");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("matching", control_value));

  filterx_expr_unref(cond);
  filterx_object_unref(control_value);
  filterx_object_unref(cond_eval);
  ////

  deinit_test(&env);
}


Test(expr_condition, test_condition_non_matching_expression)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(false)),
                      stmts);
  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  cr_assert(filterx_object_truthy(cond_eval) == TRUE);

  FilterXObject *control_value = _assert_get_test_variable("$control-value");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("default", control_value));

  filterx_expr_unref(cond);
  filterx_object_unref(control_value);
  filterx_object_unref(cond_eval);
  ////

  deinit_test(&env);
}


Test(expr_condition, test_condition_matching_elif_expression)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));
  GList *elif_stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("elif-matching")));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(false)),
                      stmts);
  cond = filterx_conditional_add_false_branch((FilterXConditional *)cond,
                                              (FilterXConditional *)filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(true)),
                                                  elif_stmts));

  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  cr_assert(filterx_object_truthy(cond_eval) == TRUE);

  FilterXObject *control_value = _assert_get_test_variable("$control-value");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("elif-matching", control_value));

  filterx_expr_unref(cond);
  filterx_object_unref(control_value);
  filterx_object_unref(cond_eval);
  ////

  deinit_test(&env);
}


Test(expr_condition, test_condition_non_matching_elif_expression)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));
  GList *elif_stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("elif-matching")));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(false)),
                      stmts);
  cond = filterx_conditional_add_false_branch((FilterXConditional *)cond,
                                              (FilterXConditional *)filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(false)),
                                                  elif_stmts));

  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  cr_assert(filterx_object_truthy(cond_eval) == TRUE);

  FilterXObject *control_value = _assert_get_test_variable("$control-value");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("default", control_value));

  filterx_expr_unref(cond);
  filterx_object_unref(control_value);
  filterx_object_unref(cond_eval);
  ////

  deinit_test(&env);
}

Test(expr_condition, test_condition_matching_else_expression)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));
  GList *elif_stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("elif-matching")));
  GList *else_stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("else-matching")));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(false)),
                      stmts);
  cond = filterx_conditional_add_false_branch((FilterXConditional *)cond,
                                              (FilterXConditional *)filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(false)),
                                                  elif_stmts));
  cond = filterx_conditional_add_false_branch((FilterXConditional *)cond,
                                              (FilterXConditional *)filterx_conditional_new_conditional_codeblock(NULL, else_stmts));


  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  cr_assert(filterx_object_truthy(cond_eval) == TRUE);

  FilterXObject *control_value = _assert_get_test_variable("$control-value");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("else-matching", control_value));

  filterx_expr_unref(cond);
  filterx_object_unref(control_value);
  filterx_object_unref(cond_eval);
  ////

  deinit_test(&env);
}

Test(expr_condition, test_condition_subsequent_conditions_must_create_nested_condition)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));
  GList *elif_stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("elif-matching")));
  GList *elif2_stmts = g_list_append(NULL, _assert_assign_var("$control-value",
                                                              _string_to_filterXExpr("elif2-matching")));
  GList *else_stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("else-matching")));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(false)),
                      stmts);
  FilterXConditional *cond2 = (FilterXConditional *)filterx_conditional_new_conditional_codeblock(filterx_literal_new(
                                filterx_boolean_new(false)),
                              elif_stmts);
  FilterXConditional *cond3 = (FilterXConditional *)filterx_conditional_new_conditional_codeblock(filterx_literal_new(
                                filterx_boolean_new(true)),
                              elif2_stmts);
  FilterXConditional *cond4 = (FilterXConditional *)filterx_conditional_new_conditional_codeblock(NULL, else_stmts);
  cond = filterx_conditional_add_false_branch((FilterXConditional *)cond, cond2);
  cond = filterx_conditional_add_false_branch((FilterXConditional *)cond, cond3);
  cond = filterx_conditional_add_false_branch((FilterXConditional *)cond, cond4);

  cr_assert(((FilterXConditional *)cond)->false_branch == cond2);
  cr_assert(((FilterXConditional *)cond2)->false_branch == cond3);
  cr_assert(((FilterXConditional *)cond3)->false_branch == cond4);
  cr_assert(((FilterXConditional *)cond4)->false_branch == NULL);

  filterx_expr_unref(cond);

  ////

  deinit_test(&env);
}

Test(expr_condition, test_condition_all_the_statements_must_executed)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));
  stmts = g_list_append(stmts, _assert_assign_var("$control-value2", _string_to_filterXExpr("matching2")));
  stmts = g_list_append(stmts, _assert_assign_var("$control-value3", _string_to_filterXExpr("matching3")));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(true)),
                      stmts);

  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  cr_assert(filterx_object_truthy(cond_eval) == TRUE);

  FilterXObject *control_value = _assert_get_test_variable("$control-value");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("matching", control_value));
  filterx_object_unref(control_value);
  control_value = _assert_get_test_variable("$control-value2");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("matching2", control_value));
  filterx_object_unref(control_value);
  control_value = _assert_get_test_variable("$control-value3");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("matching3", control_value));
  filterx_object_unref(control_value);

  filterx_expr_unref(cond);
  filterx_object_unref(cond_eval);

  ////

  deinit_test(&env);
}


Test(expr_condition, test_condition_must_return_last_expression_from_evaluated_codeblock)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));
  stmts = g_list_append(stmts, _assert_assign_var("$control-value2", _string_to_filterXExpr("matching2")));
  stmts = g_list_append(stmts, _string_to_filterXExpr("must be returned"));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(true)),
                      stmts);

  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  _assert_cmp_string_to_filterx_object("must be returned", cond_eval);

  filterx_expr_unref(cond);
  filterx_object_unref(cond_eval);
  ////

  deinit_test(&env);
}


Test(expr_condition, test_condition_falsey_expression_must_interrupt_sequential_code_execution)
{
  TestEnv env;
  init_test(&env);

  _assert_set_test_variable("$control-value", _string_to_filterXExpr("default"));
  _assert_set_test_variable("$control-value3", _string_to_filterXExpr("default3"));

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));
  stmts = g_list_append(stmts, filterx_literal_new(filterx_boolean_new(false)));
  stmts = g_list_append(stmts, _assert_assign_var("$control-value3", _string_to_filterXExpr("matching3")));

  FilterXExpr *cond = filterx_conditional_new_conditional_codeblock(filterx_literal_new(filterx_boolean_new(true)),
                      stmts);

  FilterXObject *cond_eval = filterx_expr_eval(cond);
  cr_assert(cond_eval != NULL);
  cr_assert(filterx_object_truthy(cond_eval) == FALSE);

  FilterXObject *control_value = _assert_get_test_variable("$control-value");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("matching", control_value));
  filterx_object_unref(control_value);
  control_value = _assert_get_test_variable("$control-value3");
  cr_assert_eq(0, _assert_cmp_string_to_filterx_object("default3", control_value));
  filterx_object_unref(control_value);


  filterx_expr_unref(cond);
  filterx_object_unref(cond_eval);
  ////

  deinit_test(&env);
}


Test(expr_condition, test_condition_do_not_allow_to_add_else_into_else, .signal=SIGABRT)
{
  TestEnv env;
  init_test(&env);

  /////

  GList *stmts = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));
  GList *stmts2 = g_list_append(NULL, _assert_assign_var("$control-value", _string_to_filterXExpr("matching")));

  FilterXExpr *cond = filterx_conditional_new_codeblock(stmts);
  cond = filterx_conditional_add_false_branch((FilterXConditional *) cond,
                                              (FilterXConditional *) filterx_conditional_new_codeblock(stmts2));

  filterx_expr_unref(cond);
  ////

  deinit_test(&env);
}

static void
setup(void)
{
  app_startup();
}

static void
teardown(void)
{
  scratch_buffers_explicit_gc();
  app_shutdown();
}

TestSuite(expr_condition, .init = setup, .fini = teardown);
