#include "regex_yaml.h"
#include "stream.h"
#include "gtest/gtest.h"

using YAML::RegEx;
using YAML::Stream;

namespace {
const auto MIN_CHAR = Stream::eof() + 1;

TEST(RegExTest, Empty) {
  RegEx empty;
  EXPECT_TRUE(empty.Matches(std::string()));
  EXPECT_EQ(0, empty.Match(std::string()));
  for (int i = MIN_CHAR; i < 128; ++i) {
    auto str = std::string(1, char(i));
    EXPECT_FALSE(empty.Matches(str));
    EXPECT_EQ(-1, empty.Match(str));
  }
}

TEST(RegExTest, Range) {
  for (int i = MIN_CHAR; i < 128; ++i) {
    for (int j = MIN_CHAR; j < 128; ++j) {
      RegEx ex((char)i, (char)j);
      for (int k = MIN_CHAR; k < 128; ++k) {
        auto str = std::string(1, char(k));
        if (i <= k && k <= j) {
          EXPECT_TRUE(ex.Matches(str));
          EXPECT_EQ(1, ex.Match(str));
        } else {
          EXPECT_FALSE(ex.Matches(str));
          EXPECT_EQ(-1, ex.Match(str));
        }
      }
    }
  }
}

TEST(RegExTest, EmptyString) {
  RegEx ex = RegEx(std::string());
  EXPECT_TRUE(ex.Matches(std::string()));
  EXPECT_EQ(0, ex.Match(std::string()));

  // Matches anything, unlike RegEx()!
  EXPECT_TRUE(ex.Matches(std::string("hello")));
  EXPECT_EQ(0, ex.Match(std::string("hello")));
}

TEST(RegExTest, SingleCharacterString) {
  for (int i = MIN_CHAR; i < 128; ++i) {
    RegEx ex(std::string(1, (char)i));
    for (int j = MIN_CHAR; j < 128; ++j) {
      auto str = std::string(1, char(j));
      if (j == i) {
        EXPECT_TRUE(ex.Matches(str));
        EXPECT_EQ(1, ex.Match(str));
        // Match at start of string only!
        std::string prefixed =
            std::string(1, i + 1) + std::string("prefix: ") + str;
        EXPECT_FALSE(ex.Matches(prefixed));
        EXPECT_EQ(-1, ex.Match(prefixed));
      } else {
        EXPECT_FALSE(ex.Matches(str));
        EXPECT_EQ(-1, ex.Match(str));
      }
    }
  }
}

TEST(RegExTest, MultiCharacterString) {
  RegEx ex(std::string("ab"));

  EXPECT_FALSE(ex.Matches(std::string("a")));
  EXPECT_EQ(-1, ex.Match(std::string("a")));

  EXPECT_TRUE(ex.Matches(std::string("ab")));
  EXPECT_EQ(2, ex.Match(std::string("ab")));
  EXPECT_TRUE(ex.Matches(std::string("abba")));
  EXPECT_EQ(2, ex.Match(std::string("abba")));

  // match at start of string only!
  EXPECT_FALSE(ex.Matches(std::string("baab")));
  EXPECT_EQ(-1, ex.Match(std::string("baab")));
}

TEST(RegExTest, OperatorNot) {
  RegEx ex = !RegEx(std::string("ab"));

  EXPECT_TRUE(ex.Matches(std::string("a")));
  EXPECT_EQ(1, ex.Match(std::string("a")));

  EXPECT_FALSE(ex.Matches(std::string("ab")));
  EXPECT_EQ(-1, ex.Match(std::string("ab")));
  EXPECT_FALSE(ex.Matches(std::string("abba")));
  EXPECT_EQ(-1, ex.Match(std::string("abba")));

  // match at start of string only!
  EXPECT_TRUE(ex.Matches(std::string("baab")));
  // Operator not causes only one character to be matched.
  EXPECT_EQ(1, ex.Match(std::string("baab")));
}

TEST(RegExTest, OperatorOr) {
  for (int i = MIN_CHAR; i < 127; ++i) {
    for (int j = i + 1; j < 128; ++j) {
      auto iStr = std::string(1, char(i));
      auto jStr = std::string(1, char(j));
      RegEx ex1 = RegEx(iStr) | RegEx(jStr);
      RegEx ex2 = RegEx(jStr) | RegEx(iStr);

      for (int k = MIN_CHAR; k < 128; ++k) {
        auto str = std::string(1, char(k));
        if (i == k || j == k) {
          EXPECT_TRUE(ex1.Matches(str));
          EXPECT_TRUE(ex2.Matches(str));
          EXPECT_EQ(1, ex1.Match(str));
          EXPECT_EQ(1, ex2.Match(str));
        } else {
          EXPECT_FALSE(ex1.Matches(str));
          EXPECT_FALSE(ex2.Matches(str));
          EXPECT_EQ(-1, ex1.Match(str));
          EXPECT_EQ(-1, ex2.Match(str));
        }
      }
    }
  }
}

TEST(RegExTest, OperatorOrShortCircuits) {
  RegEx ex1 = RegEx(std::string("aaaa")) | RegEx(std::string("aa"));
  RegEx ex2 = RegEx(std::string("aa")) | RegEx(std::string("aaaa"));

  EXPECT_TRUE(ex1.Matches(std::string("aaaaa")));
  EXPECT_EQ(4, ex1.Match(std::string("aaaaa")));

  EXPECT_TRUE(ex2.Matches(std::string("aaaaa")));
  EXPECT_EQ(2, ex2.Match(std::string("aaaaa")));
}

TEST(RegExTest, OperatorAnd) {
  RegEx emptySet = RegEx('a') & RegEx();
  EXPECT_FALSE(emptySet.Matches(std::string("a")));
}

TEST(RegExTest, OperatorAndShortCircuits) {
  RegEx ex1 = RegEx(std::string("aaaa")) & RegEx(std::string("aa"));
  RegEx ex2 = RegEx(std::string("aa")) & RegEx(std::string("aaaa"));

  EXPECT_TRUE(ex1.Matches(std::string("aaaaa")));
  EXPECT_EQ(4, ex1.Match(std::string("aaaaa")));

  EXPECT_TRUE(ex2.Matches(std::string("aaaaa")));
  EXPECT_EQ(2, ex2.Match(std::string("aaaaa")));
}

TEST(RegExTest, OperatorPlus) {
  RegEx ex = RegEx(std::string("hello ")) + RegEx(std::string("there"));

  EXPECT_TRUE(ex.Matches(std::string("hello there")));
  EXPECT_FALSE(ex.Matches(std::string("hello ")));
  EXPECT_FALSE(ex.Matches(std::string("there")));
  EXPECT_EQ(11, ex.Match(std::string("hello there")));
}

TEST(RegExTest, StringOr) {
  std::string str = "abcde";
  RegEx ex = RegEx(str, YAML::REGEX_OR);

  for (size_t i = 0; i < str.size(); ++i) {
    EXPECT_TRUE(ex.Matches(str.substr(i, 1)));
    EXPECT_EQ(1, ex.Match(str.substr(i, 1)));
  }

  EXPECT_EQ(1, ex.Match(str));
}
}  // namespace