/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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 "perfetto/ext/base/string_splitter.h"

#include <vector>

#include "test/gtest_and_gmock.h"

namespace perfetto {
namespace base {
namespace {

using testing::ElementsAreArray;

TEST(StringSplitterTest, StdString) {
  {
    StringSplitter ss("", 'x');
    EXPECT_EQ(nullptr, ss.cur_token());
    EXPECT_EQ(0u, ss.cur_token_size());
    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(nullptr, ss.cur_token());
    EXPECT_EQ(0u, ss.cur_token_size());
  }
  {
    StringSplitter ss(std::string(), 'x');
    EXPECT_EQ(nullptr, ss.cur_token());
    EXPECT_EQ(0u, ss.cur_token_size());
    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(nullptr, ss.cur_token());
    EXPECT_EQ(0u, ss.cur_token_size());
  }
  {
    StringSplitter ss("a", 'x');
    EXPECT_EQ(nullptr, ss.cur_token());
    EXPECT_EQ(0u, ss.cur_token_size());
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("a", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());
    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(0u, ss.cur_token_size());
  }
  {
    StringSplitter ss("abc", 'x');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("abc", ss.cur_token());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_FALSE(ss.Next());
  }
  {
    StringSplitter ss("ab,", ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("ab", ss.cur_token());
    EXPECT_EQ(2u, ss.cur_token_size());
    EXPECT_FALSE(ss.Next());
  }
  {
    StringSplitter ss(",ab,", ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("ab", ss.cur_token());
    EXPECT_EQ(2u, ss.cur_token_size());
    EXPECT_FALSE(ss.Next());
  }
  {
    StringSplitter ss("a,b,c", ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(1u, ss.cur_token_size());
    EXPECT_STREQ("a", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("b", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("c", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());

    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(nullptr, ss.cur_token());
    EXPECT_EQ(0u, ss.cur_token_size());
  }
  {
    StringSplitter ss("a,b,c,", ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("a", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("b", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("c", ss.cur_token());

    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(nullptr, ss.cur_token());
  }
  {
    StringSplitter ss(",,a,,b,,,,c,,,", ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("a", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("b", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("c", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());

    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
    }
  }
  {
    StringSplitter ss(",,", ',');
    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(nullptr, ss.cur_token());
      EXPECT_EQ(0u, ss.cur_token_size());
    }
  }
  {
    StringSplitter ss(",,foo", ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("foo", ss.cur_token());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_FALSE(ss.Next());
  }
}

TEST(StringSplitterTest, CString) {
  {
    char buf[] = "\0x\0";
    StringSplitter ss(buf, sizeof(buf), ',');
    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(nullptr, ss.cur_token());
  }
  {
    char buf[] = "foo\nbar\n\nbaz\n";
    StringSplitter ss(buf, sizeof(buf), '\n');
    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("foo", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("bar", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("baz", ss.cur_token());

    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(0u, ss.cur_token_size());
  }
  {
    char buf[] = "";
    StringSplitter ss(buf, 0, ',');
    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(nullptr, ss.cur_token());
    EXPECT_EQ(0u, ss.cur_token_size());
  }
  {
    char buf[] = "\0";
    StringSplitter ss(buf, 1, ',');
    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(nullptr, ss.cur_token());
    EXPECT_EQ(0u, ss.cur_token_size());
  }
  {
    char buf[] = ",,foo,bar\0,baz";
    StringSplitter ss(buf, sizeof(buf), ',');

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("foo", ss.cur_token());
    EXPECT_EQ(3u, ss.cur_token_size());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("bar", ss.cur_token());
    EXPECT_EQ(3u, ss.cur_token_size());

    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
    }
  }
  {
    char buf[] = ",,a\0,b,";
    StringSplitter ss(buf, sizeof(buf), ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("a", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());
    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(nullptr, ss.cur_token());
      EXPECT_EQ(0u, ss.cur_token_size());
    }
  }
  {
    char buf[] = ",a,\0b";
    StringSplitter ss(buf, sizeof(buf), ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("a", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());
    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
      EXPECT_EQ(nullptr, ss.cur_token());
    }
  }
  {
    char buf[] = ",a\0\0,x\0\0b";
    StringSplitter ss(buf, sizeof(buf), ',');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("a", ss.cur_token());
    EXPECT_EQ(1u, ss.cur_token_size());
    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
      EXPECT_EQ(nullptr, ss.cur_token());
    }
  }
}

TEST(StringSplitterTest, SplitOnNUL) {
  {
    StringSplitter ss(std::string(""), '\0');
    EXPECT_FALSE(ss.Next());
    EXPECT_EQ(nullptr, ss.cur_token());
  }
  {
    std::string str;
    str.resize(48);
    memcpy(&str[0], "foo\0", 4);
    memcpy(&str[4], "bar\0", 4);
    memcpy(&str[20], "baz", 3);
    StringSplitter ss(std::move(str), '\0');
    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("foo", ss.cur_token());
    EXPECT_EQ(3u, ss.cur_token_size());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("bar", ss.cur_token());
    EXPECT_EQ(3u, ss.cur_token_size());

    EXPECT_TRUE(ss.Next());
    EXPECT_STREQ("baz", ss.cur_token());
    EXPECT_EQ(3u, ss.cur_token_size());

    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
      EXPECT_EQ(nullptr, ss.cur_token());
    }
  }
  {
    char buf[] = "foo\0bar\0baz\0";
    StringSplitter ss(buf, sizeof(buf), '\0');
    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("foo", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("bar", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("baz", ss.cur_token());

    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
      EXPECT_EQ(nullptr, ss.cur_token());
    }
  }
  {
    char buf[] = "\0\0foo\0\0\0\0bar\0baz\0\0";
    StringSplitter ss(buf, sizeof(buf), '\0');
    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("foo", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("bar", ss.cur_token());

    EXPECT_TRUE(ss.Next());
    EXPECT_EQ(3u, ss.cur_token_size());
    EXPECT_STREQ("baz", ss.cur_token());

    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
      EXPECT_EQ(nullptr, ss.cur_token());
    }
  }
  {
    char buf[] = "";
    StringSplitter ss(buf, 0, '\0');
    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
      EXPECT_EQ(nullptr, ss.cur_token());
    }
  }
  {
    char buf[] = "\0";
    StringSplitter ss(buf, 1, '\0');
    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
      EXPECT_EQ(nullptr, ss.cur_token());
    }
  }
  {
    char buf[] = "\0\0";
    StringSplitter ss(buf, 2, '\0');
    for (int i = 0; i < 3; i++) {
      EXPECT_FALSE(ss.Next());
      EXPECT_EQ(0u, ss.cur_token_size());
      EXPECT_EQ(nullptr, ss.cur_token());
    }
  }
}

TEST(StringSplitterTest, NestedUsage) {
  char text[] = R"(
l1w1 l1w2 l1w3

,l,2,w,1   l,2,,w,,2,,
)";
  std::vector<std::string> all_lines;
  std::vector<std::string> all_words;
  std::vector<std::string> all_tokens;
  for (StringSplitter lines(text, sizeof(text), '\n'); lines.Next();) {
    all_lines.push_back(lines.cur_token());
    for (StringSplitter words(&lines, ' '); words.Next();) {
      all_words.push_back(words.cur_token());
      for (StringSplitter tokens(&words, ','); tokens.Next();) {
        all_tokens.push_back(tokens.cur_token());
      }
    }
  }
  EXPECT_THAT(all_lines,
              ElementsAreArray({"l1w1 l1w2 l1w3", ",l,2,w,1   l,2,,w,,2,,"}));
  EXPECT_THAT(all_words, ElementsAreArray({"l1w1", "l1w2", "l1w3", ",l,2,w,1",
                                           "l,2,,w,,2,,"}));
  EXPECT_THAT(all_tokens, ElementsAreArray({"l1w1", "l1w2", "l1w3", "l", "2",
                                            "w", "1", "l", "2", "w", "2"}));
}  // namespace

TEST(StringSplitterTest, EmptyTokens) {
  char text[] = "a,,b";
  std::vector<std::string> tokens;
  for (StringSplitter lines(text, sizeof(text), ',',
                            StringSplitter::EmptyTokenMode::ALLOW_EMPTY_TOKENS);
       lines.Next();) {
    tokens.push_back(lines.cur_token());
  }
  EXPECT_THAT(tokens, testing::ElementsAre("a", "", "b"));
}  // namespace

}  // namespace
}  // namespace base
}  // namespace perfetto
