#include "../src/util_string.hpp"

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

namespace {

Sass::sass::string escape_string(const Sass::sass::string& str) {
  Sass::sass::string out;
  out.reserve(str.size());
  for (char c : str) {
    switch (c) {
      case '\n':
        out.append("\\n");
        break;
      case '\r':
        out.append("\\r");
        break;
      case '\f':
        out.append("\\f");
        break;
      default:
        out += c;
    }
  }
  return out;
}

#define ASSERT_TRUE(cond) \
  if (!cond) { \
    std::cerr << \
      "Expected condition to be true at " << __FILE__ << ":" << __LINE__ << \
      std::endl; \
    return false; \
  } \

#define ASSERT_FALSE(cond) \
  ASSERT_TRUE(!(cond)) \

#define ASSERT_STR_EQ(a, b) \
  if (a != b) { \
    std::cerr << \
      "Expected LHS == RHS at " << __FILE__ << ":" << __LINE__ << \
      "\n  LHS: [" << escape_string(a) << "]" \
      "\n  RHS: [" << escape_string(b) << "]" << \
      std::endl; \
    return false; \
  } \

bool TestNormalizeNewlinesNoNewline() {
  Sass::sass::string input = "a";
  Sass::sass::string normalized = Sass::Util::normalize_newlines(input);
  ASSERT_STR_EQ(input, normalized);
  return true;
}

bool TestNormalizeNewlinesLF() {
  Sass::sass::string input = "a\nb";
  Sass::sass::string normalized = Sass::Util::normalize_newlines(input);
  ASSERT_STR_EQ(input, normalized);
  return true;
}

bool TestNormalizeNewlinesCR() {
  Sass::sass::string normalized = Sass::Util::normalize_newlines("a\rb");
  ASSERT_STR_EQ("a\nb", normalized);
  return true;
}

bool TestNormalizeNewlinesCRLF() {
  Sass::sass::string normalized = Sass::Util::normalize_newlines("a\r\nb\r\n");
  ASSERT_STR_EQ("a\nb\n", normalized);
  return true;
}

bool TestNormalizeNewlinesFF() {
  Sass::sass::string normalized = Sass::Util::normalize_newlines("a\fb\f");
  ASSERT_STR_EQ("a\nb\n", normalized);
  return true;
}

bool TestNormalizeNewlinesMixed() {
  Sass::sass::string normalized = Sass::Util::normalize_newlines("a\fb\nc\rd\r\ne\ff");
  ASSERT_STR_EQ("a\nb\nc\nd\ne\nf", normalized);
  return true;
}

bool TestNormalizeUnderscores() {
  Sass::sass::string normalized = Sass::Util::normalize_underscores("a_b_c");
  ASSERT_STR_EQ("a-b-c", normalized);
  return true;
}

bool TestNormalizeDecimalsLeadingZero() {
  Sass::sass::string normalized = Sass::Util::normalize_decimals("0.5");
  ASSERT_STR_EQ("0.5", normalized);
  return true;
}

bool TestNormalizeDecimalsNoLeadingZero() {
  Sass::sass::string normalized = Sass::Util::normalize_decimals(".5");
  ASSERT_STR_EQ("0.5", normalized);
  return true;
}

bool testEqualsLiteral() {
  ASSERT_TRUE(Sass::Util::equalsLiteral("moz", "moz"));
  ASSERT_TRUE(Sass::Util::equalsLiteral(":moz", ":moz"));
  ASSERT_FALSE(Sass::Util::equalsLiteral("moz", ":moz"));
  ASSERT_FALSE(Sass::Util::equalsLiteral(":moz", "moz"));
  ASSERT_TRUE(Sass::Util::equalsLiteral("moz-foo", "MOZ-foo"));
  ASSERT_FALSE(Sass::Util::equalsLiteral("moz-foo", "moz_foo"));
  ASSERT_TRUE(Sass::Util::equalsLiteral("moz-foo", "MOZ-FOOS"));
  ASSERT_FALSE(Sass::Util::equalsLiteral("moz-foos", "moz-foo"));
  ASSERT_FALSE(Sass::Util::equalsLiteral("-moz-foo", "moz-foo"));
  return true;

}

bool TestUnvendor() {
  // Generated by using dart sass
  ASSERT_STR_EQ("moz", Sass::Util::unvendor("moz"));
  ASSERT_STR_EQ(":moz", Sass::Util::unvendor(":moz"));
  ASSERT_STR_EQ("-moz", Sass::Util::unvendor("-moz"));
  ASSERT_STR_EQ("--moz", Sass::Util::unvendor("--moz"));
  ASSERT_STR_EQ("moz-bar", Sass::Util::unvendor("moz-bar"));
  ASSERT_STR_EQ("bar", Sass::Util::unvendor("-moz-bar"));
  ASSERT_STR_EQ("bar-", Sass::Util::unvendor("-moz-bar-"));
  ASSERT_STR_EQ("--moz-bar", Sass::Util::unvendor("--moz-bar"));
  ASSERT_STR_EQ("-bar", Sass::Util::unvendor("-moz--bar"));
  ASSERT_STR_EQ("any", Sass::Util::unvendor("-s-any"));
  ASSERT_STR_EQ("any-more", Sass::Util::unvendor("-s-any-more"));
  ASSERT_STR_EQ("any--more", Sass::Util::unvendor("-s-any--more"));
  ASSERT_STR_EQ("--s-any--more", Sass::Util::unvendor("--s-any--more"));
  ASSERT_STR_EQ("s-any--more", Sass::Util::unvendor("s-any--more"));
  ASSERT_STR_EQ("_s_any_more", Sass::Util::unvendor("_s_any_more"));
  ASSERT_STR_EQ("more", Sass::Util::unvendor("-s_any-more"));
  ASSERT_STR_EQ("any_more", Sass::Util::unvendor("-s-any_more"));
  ASSERT_STR_EQ("_s_any_more", Sass::Util::unvendor("_s_any_more"));
  return true;
}

bool Test_ascii_str_to_lower() {
  Sass::sass::string str = "A B";
  Sass::Util::ascii_str_tolower(&str);
  ASSERT_STR_EQ("a b", str);
  return true;
}

bool Test_ascii_str_to_upper() {
  Sass::sass::string str = "a b";
  Sass::Util::ascii_str_toupper(&str);
  ASSERT_STR_EQ("A B", str);
  return true;
}

bool Test_ascii_isalpha() {
  ASSERT_TRUE(Sass::Util::ascii_isalpha('a'));
  ASSERT_FALSE(Sass::Util::ascii_isalpha('3'));
  return true;
}

bool Test_ascii_isxdigit() {
  ASSERT_TRUE(Sass::Util::ascii_isxdigit('a'));
  ASSERT_TRUE(Sass::Util::ascii_isxdigit('F'));
  ASSERT_TRUE(Sass::Util::ascii_isxdigit('3'));
  ASSERT_FALSE(Sass::Util::ascii_isxdigit('G'));
  return true;
}

bool Test_ascii_isspace() {
  ASSERT_TRUE(Sass::Util::ascii_isspace(' '));
  ASSERT_TRUE(Sass::Util::ascii_isspace('\t'));
  ASSERT_TRUE(Sass::Util::ascii_isspace('\v'));
  ASSERT_TRUE(Sass::Util::ascii_isspace('\f'));
  ASSERT_TRUE(Sass::Util::ascii_isspace('\r'));
  ASSERT_TRUE(Sass::Util::ascii_isspace('\n'));
  ASSERT_FALSE(Sass::Util::ascii_isspace('G'));
  return true;
}

}  // namespace

#define TEST(fn) \
  if (fn()) { \
    passed.push_back(#fn); \
  } else { \
    failed.push_back(#fn); \
    std::cerr << "Failed: " #fn << std::endl; \
  } \

int main(int argc, char **argv) {
  std::vector<std::string> passed;
  std::vector<std::string> failed;
  TEST(TestNormalizeNewlinesNoNewline);
  TEST(TestNormalizeNewlinesLF);
  TEST(TestNormalizeNewlinesCR);
  TEST(TestNormalizeNewlinesCRLF);
  TEST(TestNormalizeNewlinesFF);
  TEST(TestNormalizeNewlinesMixed);
  TEST(TestNormalizeUnderscores);
  TEST(TestNormalizeDecimalsLeadingZero);
  TEST(TestNormalizeDecimalsNoLeadingZero);
  TEST(testEqualsLiteral);
  TEST(TestUnvendor);
  TEST(Test_ascii_str_to_lower);
  TEST(Test_ascii_str_to_upper);
  TEST(Test_ascii_isalpha);
  TEST(Test_ascii_isxdigit);
  TEST(Test_ascii_isspace);
  std::cerr << argv[0] << ": Passed: " << passed.size()
            << ", failed: " << failed.size()
            << "." << std::endl;
  return failed.size();
}