/*
 * Copyright (C) 2017 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 "smartselect/tokenizer.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace libtextclassifier {
namespace {

using testing::ElementsAreArray;

class TestingTokenizer : public Tokenizer {
 public:
  explicit TestingTokenizer(
      const std::vector<TokenizationCodepointRange>& codepoint_range_configs)
      : Tokenizer(codepoint_range_configs) {}

  TokenizationCodepointRange::Role TestFindTokenizationRole(int c) const {
    return FindTokenizationRole(c);
  }
};

TEST(TokenizerTest, FindTokenizationRole) {
  std::vector<TokenizationCodepointRange> configs;
  TokenizationCodepointRange* config;

  configs.emplace_back();
  config = &configs.back();
  config->set_start(0);
  config->set_end(10);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);

  configs.emplace_back();
  config = &configs.back();
  config->set_start(32);
  config->set_end(33);
  config->set_role(TokenizationCodepointRange::WHITESPACE_SEPARATOR);

  configs.emplace_back();
  config = &configs.back();
  config->set_start(1234);
  config->set_end(12345);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);

  TestingTokenizer tokenizer(configs);

  // Test hits to the first group.
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(0),
            TokenizationCodepointRange::TOKEN_SEPARATOR);
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(5),
            TokenizationCodepointRange::TOKEN_SEPARATOR);
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(10),
            TokenizationCodepointRange::DEFAULT_ROLE);

  // Test a hit to the second group.
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(31),
            TokenizationCodepointRange::DEFAULT_ROLE);
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(32),
            TokenizationCodepointRange::WHITESPACE_SEPARATOR);
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(33),
            TokenizationCodepointRange::DEFAULT_ROLE);

  // Test hits to the third group.
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(1233),
            TokenizationCodepointRange::DEFAULT_ROLE);
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(1234),
            TokenizationCodepointRange::TOKEN_SEPARATOR);
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(12344),
            TokenizationCodepointRange::TOKEN_SEPARATOR);
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(12345),
            TokenizationCodepointRange::DEFAULT_ROLE);

  // Test a hit outside.
  EXPECT_EQ(tokenizer.TestFindTokenizationRole(99),
            TokenizationCodepointRange::DEFAULT_ROLE);
}

TEST(TokenizerTest, TokenizeOnSpace) {
  std::vector<TokenizationCodepointRange> configs;
  TokenizationCodepointRange* config;

  configs.emplace_back();
  config = &configs.back();
  // Space character.
  config->set_start(32);
  config->set_end(33);
  config->set_role(TokenizationCodepointRange::WHITESPACE_SEPARATOR);

  TestingTokenizer tokenizer(configs);
  std::vector<Token> tokens = tokenizer.Tokenize("Hello world!");

  EXPECT_THAT(tokens,
              ElementsAreArray({Token("Hello", 0, 5), Token("world!", 6, 12)}));
}

TEST(TokenizerTest, TokenizeComplex) {
  std::vector<TokenizationCodepointRange> configs;
  TokenizationCodepointRange* config;

  // Source: http://www.unicode.org/Public/10.0.0/ucd/Blocks-10.0.0d1.txt
  // Latin - cyrilic.
  //   0000..007F; Basic Latin
  //   0080..00FF; Latin-1 Supplement
  //   0100..017F; Latin Extended-A
  //   0180..024F; Latin Extended-B
  //   0250..02AF; IPA Extensions
  //   02B0..02FF; Spacing Modifier Letters
  //   0300..036F; Combining Diacritical Marks
  //   0370..03FF; Greek and Coptic
  //   0400..04FF; Cyrillic
  //   0500..052F; Cyrillic Supplement
  //   0530..058F; Armenian
  //   0590..05FF; Hebrew
  //   0600..06FF; Arabic
  //   0700..074F; Syriac
  //   0750..077F; Arabic Supplement
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0);
  config->set_end(32);
  config->set_role(TokenizationCodepointRange::DEFAULT_ROLE);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(32);
  config->set_end(33);
  config->set_role(TokenizationCodepointRange::WHITESPACE_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(33);
  config->set_end(0x77F + 1);
  config->set_role(TokenizationCodepointRange::DEFAULT_ROLE);

  // CJK
  // 2E80..2EFF; CJK Radicals Supplement
  // 3000..303F; CJK Symbols and Punctuation
  // 3040..309F; Hiragana
  // 30A0..30FF; Katakana
  // 3100..312F; Bopomofo
  // 3130..318F; Hangul Compatibility Jamo
  // 3190..319F; Kanbun
  // 31A0..31BF; Bopomofo Extended
  // 31C0..31EF; CJK Strokes
  // 31F0..31FF; Katakana Phonetic Extensions
  // 3200..32FF; Enclosed CJK Letters and Months
  // 3300..33FF; CJK Compatibility
  // 3400..4DBF; CJK Unified Ideographs Extension A
  // 4DC0..4DFF; Yijing Hexagram Symbols
  // 4E00..9FFF; CJK Unified Ideographs
  // A000..A48F; Yi Syllables
  // A490..A4CF; Yi Radicals
  // A4D0..A4FF; Lisu
  // A500..A63F; Vai
  // F900..FAFF; CJK Compatibility Ideographs
  // FE30..FE4F; CJK Compatibility Forms
  // 20000..2A6DF; CJK Unified Ideographs Extension B
  // 2A700..2B73F; CJK Unified Ideographs Extension C
  // 2B740..2B81F; CJK Unified Ideographs Extension D
  // 2B820..2CEAF; CJK Unified Ideographs Extension E
  // 2CEB0..2EBEF; CJK Unified Ideographs Extension F
  // 2F800..2FA1F; CJK Compatibility Ideographs Supplement
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x2E80);
  config->set_end(0x2EFF + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x3000);
  config->set_end(0xA63F + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0xF900);
  config->set_end(0xFAFF + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0xFE30);
  config->set_end(0xFE4F + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x20000);
  config->set_end(0x2A6DF + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x2A700);
  config->set_end(0x2B73F + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x2B740);
  config->set_end(0x2B81F + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x2B820);
  config->set_end(0x2CEAF + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x2CEB0);
  config->set_end(0x2EBEF + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x2F800);
  config->set_end(0x2FA1F + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);

  // Thai.
  // 0E00..0E7F; Thai
  configs.emplace_back();
  config = &configs.back();
  config->set_start(0x0E00);
  config->set_end(0x0E7F + 1);
  config->set_role(TokenizationCodepointRange::TOKEN_SEPARATOR);

  Tokenizer tokenizer(configs);
  std::vector<Token> tokens;

  tokens = tokenizer.Tokenize(
      "問少目木輸走猶術権自京門録球変。細開括省用掲情結傍走愛明氷。");
  EXPECT_EQ(tokens.size(), 30);

  tokens = tokenizer.Tokenize("問少目 hello 木輸ยามきゃ");
  // clang-format off
  EXPECT_THAT(
      tokens,
      ElementsAreArray({Token("問", 0, 1),
                        Token("少", 1, 2),
                        Token("目", 2, 3),
                        Token("hello", 4, 9),
                        Token("木", 10, 11),
                        Token("輸", 11, 12),
                        Token("ย", 12, 13),
                        Token("า", 13, 14),
                        Token("ม", 14, 15),
                        Token("き", 15, 16),
                        Token("ゃ", 16, 17)}));
  // clang-format on
}

}  // namespace
}  // namespace libtextclassifier