/*
 * This file is part of GtkSourceView
 *
 * Copyright (C) 2013 - Paolo Borelli
 *
 * GtkSourceView 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.
 *
 * GtkSourceView 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, see <http://www.gnu.org/licenses/>.
 */

#include <gtk/gtk.h>
#include <gtksourceview/gtksource.h>
#include "gtksourceview/gtksourceregex-private.h"

#include "gtksourceview/implregex-private.h"

static void
test_slash_c_pattern (void)
{
	GtkSourceRegex *regex;
	GError *error = NULL;

	regex = _gtk_source_regex_new ("\\C", 0, &error);
	g_assert_error (error, G_REGEX_ERROR, G_REGEX_ERROR_COMPILE);
	g_assert_null (regex);
}

static void
assert_iterations (GMatchInfo    *mi1,
                   ImplMatchInfo *mi2)
{
  gboolean r1;
  gboolean r2;
  gboolean next1;
  gboolean next2;
  GError *err1 = NULL;
  GError *err2 = NULL;

  for (;;)
    {
      gboolean matches1 = g_match_info_matches (mi1);
      gboolean matches2 = impl_match_info_matches (mi2);
      int count1, count2;

      g_assert_cmpint (matches1, ==, matches2);

      if (!matches1)
        break;

      count1 = g_match_info_get_match_count (mi1);
      count2 = impl_match_info_get_match_count (mi2);
      g_assert_cmpint (count1, ==, count2);

      /* Check past boundaries for correctness */
      for (int i = 0; i < count1 + 2; i++)
        {
          int p1_begin = -123, p2_begin = -123;
          int p1_end = -123, p2_end = -123;
          char *str1, *str2;

          r1 = g_match_info_fetch_pos (mi1, i, &p1_begin, &p1_end);
          r2 = impl_match_info_fetch_pos (mi2, i, &p2_begin, &p2_end);
          g_assert_cmpint (r1, ==, r2);
          g_assert_cmpint (p1_begin, ==, p2_begin);
          g_assert_cmpint (p1_end, ==, p2_end);

          str1 = g_match_info_fetch (mi1, i);
          str2 = impl_match_info_fetch (mi2, i);
          g_assert_cmpstr (str1, ==, str2);

          g_free (str1);
          g_free (str2);
        }

      g_assert_cmpint (g_match_info_is_partial_match (mi1),
                       ==,
                       impl_match_info_is_partial_match (mi2));

      next1 = g_match_info_next (mi1, &err1);
      next2 = impl_match_info_next (mi2, &err2);
      g_assert_cmpint (next1, ==, next2);
      g_assert_true (err1 == NULL || err2 != NULL);
    }

  g_assert_false (g_match_info_matches (mi1));
  g_assert_false (impl_match_info_matches (mi2));
}

static void
compare_impl_regex_to_g_regex (const char         *subject,
                               const char         *pattern,
                               GRegexCompileFlags  compile_flags,
                               GRegexMatchFlags    match_flags)
{
  GError *err1 = NULL;
  GError *err2 = NULL;
  GRegex *reg1 = g_regex_new (pattern, compile_flags, 0, &err1);
  /* Disable JIT for ImplRegex, as it is not as flexible for search */
  ImplRegex *reg2 = impl_regex_new (pattern, compile_flags & ~G_REGEX_OPTIMIZE, 0, &err2);
  GMatchInfo *mi1 = NULL;
  ImplMatchInfo *mi2 = NULL;
  gboolean r1, r2;
  int subject_len = strlen (subject);

  g_assert_true ((reg1 == NULL && reg2 == NULL) ||
                 (reg1 != NULL && reg2 != NULL));
  g_assert_cmpstr (g_regex_get_pattern (reg1),
                   ==,
                   impl_regex_get_pattern (reg2));
  g_assert_cmpint (g_regex_get_max_lookbehind (reg1),
                   ==,
                   impl_regex_get_max_lookbehind (reg2));

  r1 = g_regex_match (reg1, subject, match_flags, &mi1);
  r2 = impl_regex_match (reg2, subject, match_flags, &mi2);
  g_assert_cmpint (r1, ==, r2);
  g_assert_true (err1 == NULL || err2 != NULL);
  assert_iterations (mi1, mi2);
  g_clear_pointer (&mi1, g_match_info_free);
  g_clear_pointer (&mi2, impl_match_info_free);

  for (int i = 0; i <= subject_len; i++)
    {
      r1 = g_regex_match_full (reg1, subject, subject_len, i, match_flags, &mi1, &err1);
      r2 = impl_regex_match_full (reg2, subject, subject_len, i, match_flags, &mi2, &err2);
      g_assert_cmpint (r1, ==, r2);
      g_assert_true (err1 == NULL || err2 != NULL);
      assert_iterations (mi1, mi2);
      g_clear_pointer (&mi1, g_match_info_free);
      g_clear_pointer (&mi2, impl_match_info_free);
    }

  g_clear_pointer (&reg1, g_regex_unref);
  g_clear_pointer (&reg2, impl_regex_unref);
}

static void
test_compare (void)
{
  /* Flags match what we see from search context */
  GRegexCompileFlags compile = 0x2003;
  GRegexMatchFlags match = 0x400;

  compare_impl_regex_to_g_regex ("aaa\n", "aa", compile, match);
  compare_impl_regex_to_g_regex ("aaa\n", "aa", compile, match);

  compare_impl_regex_to_g_regex ("aaaa", "aa", compile, match);
  compare_impl_regex_to_g_regex ("aaaa", "aa", compile, match);

  compare_impl_regex_to_g_regex ("aaaa\n", "aa", compile, match);
  compare_impl_regex_to_g_regex ("aaaa\n", "aa", compile, match);

  compare_impl_regex_to_g_regex ("", "aa", compile, match);
  compare_impl_regex_to_g_regex ("", "aa", compile, match);

  compare_impl_regex_to_g_regex ("hello\n", "\\w+", compile, match);
  compare_impl_regex_to_g_regex ("hello\n", "\\w+", compile, match);

  compare_impl_regex_to_g_regex ("hello\nworld\n", "\\w+", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "\\w+", compile, match);

  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*)*", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*)*", compile, match);

  compare_impl_regex_to_g_regex ("hello\nworld\n", "\\w+", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "\\w+", compile, match);

  compare_impl_regex_to_g_regex ("hello\nworld\n", "\\w+", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "\\w+", compile, match);

  compare_impl_regex_to_g_regex ("aa#bb", "(\\w+)#(\\w+)", compile, match);
  compare_impl_regex_to_g_regex ("aa#bb", "(\\w+)#(\\w+)", compile, match);

  compare_impl_regex_to_g_regex ("aa#bb cc#dd", "(\\w+)#(\\w+)", compile, match);
  compare_impl_regex_to_g_regex ("aa#bb cc#dd", "(\\w+)#(\\w+)", compile, match);
  compare_impl_regex_to_g_regex ("aa#bb cc#dd", "(\\w+)#(\\w+)", compile, match);

  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*\n)*", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*\n)*", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*\n)*", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*\n)*", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*\\n)*", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*\\n)*", compile, match);

  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*\n)*", compile, match);
  compare_impl_regex_to_g_regex ("hello\nworld\n", "(.*\\n)*", compile, match);

  compare_impl_regex_to_g_regex ("&aa", "\\baa\\b", compile, match);
  compare_impl_regex_to_g_regex ("\342\200\223aa", "\\baa\\b", compile, match);

  compare_impl_regex_to_g_regex ("12\n", "(?<=1)23", compile, match);
  compare_impl_regex_to_g_regex ("\n23\n", "(?<=1)23", compile, match);
  compare_impl_regex_to_g_regex ("\n123\n", "(?<=1)23", compile, match);
  compare_impl_regex_to_g_regex ("\n23\n", "(?<=1)23", compile, match);
  compare_impl_regex_to_g_regex ("\n12", "(?<=1)23", compile, match);
  compare_impl_regex_to_g_regex ("3", "(?<=1)23", compile, match);
  compare_impl_regex_to_g_regex ("\n123 123\n", "(?<=1)23", compile, match);

  compare_impl_regex_to_g_regex ("12\n", "12(?=3)", compile, match);
  compare_impl_regex_to_g_regex ("123\n", "12(?=3)", compile, match);
  compare_impl_regex_to_g_regex ("\n123", "12(?=3)", compile, match);
  compare_impl_regex_to_g_regex ("\n123 123\n", "12(?=3)", compile, match);
}

static void
test_issue_198 (void)
{
  GError *error = NULL;
  ImplRegex *re = impl_regex_new ("(a)*", 0, 0, &error);
  ImplMatchInfo *mi = NULL;
  char *aaa = g_malloc (8192);
  gboolean r;

  g_assert_no_error (error);
  g_assert_nonnull (re);

  memset (aaa, 'a', 8191);
  aaa[8191] = 0;
  r = impl_regex_match_full (re, aaa, 8191, 0, 0, &mi, &error);
  g_assert_no_error (error);
  g_assert_nonnull (mi);
  g_assert_true (r);

  g_free (aaa);
  g_clear_pointer (&mi, impl_match_info_free);
  g_clear_pointer (&re, impl_regex_unref);
}

int
main (int argc, char** argv)
{
	gtk_test_init (&argc, &argv);

	g_test_add_func ("/Regex/slash-c", test_slash_c_pattern);
	g_test_add_func ("/Regex/compare-g-regex", test_compare);
	g_test_add_func ("/Regex/issue_198", test_issue_198);

	return g_test_run();
}
