// Copyright (C) 2013 Google Inc.
//
// 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 <libaddressinput/address_ui.h>

#include <libaddressinput/address_field.h>
#include <libaddressinput/address_ui_component.h>
#include <libaddressinput/localization.h>

#include <set>
#include <string>
#include <vector>

#include <gtest/gtest.h>

namespace {

using i18n::addressinput::AddressField;
using i18n::addressinput::AddressUiComponent;
using i18n::addressinput::BuildComponents;
using i18n::addressinput::GetRegionCodes;
using i18n::addressinput::Localization;

using i18n::addressinput::COUNTRY;
using i18n::addressinput::ADMIN_AREA;
using i18n::addressinput::POSTAL_CODE;
using i18n::addressinput::STREET_ADDRESS;
using i18n::addressinput::ORGANIZATION;
using i18n::addressinput::RECIPIENT;

const char kUiLanguageTag[] = "en";

// Returns testing::AssertionSuccess if the |components| are valid. Uses
// |region_code| in test failure messages.
testing::AssertionResult ComponentsAreValid(
    const std::vector<AddressUiComponent>& components) {
  if (components.empty()) {
    return testing::AssertionFailure() << "no components";
  }

  for (const auto& component : components) {
    static const AddressField kMinAddressField = COUNTRY;
    static const AddressField kMaxAddressField = RECIPIENT;
    if (component.literal.empty()) {
      if (component.field < kMinAddressField ||
          component.field > kMaxAddressField) {
        return testing::AssertionFailure()
               << "unexpected field " << component.field;
      }

      if (component.name.empty()) {
        return testing::AssertionFailure()
               << "empty field name for field " << component.field;
      }
    } else {
      // This is a literal.
      if (!component.name.empty()) {
        return testing::AssertionFailure() << "literals don't have names";
      }
    }
  }

  return testing::AssertionSuccess();
}

// Tests for address UI functions.
class AddressUiTest : public testing::TestWithParam<std::string> {
 public:
  AddressUiTest(const AddressUiTest&) = delete;
  AddressUiTest& operator=(const AddressUiTest&) = delete;

 protected:
  AddressUiTest() = default;
  Localization localization_;
  std::string best_address_language_tag_;
};

// Verifies that a region code consists of two characters, for example "TW".
TEST_P(AddressUiTest, RegionCodeHasTwoCharacters) {
  EXPECT_EQ(2, GetParam().size());
}

// Verifies that BuildComponents() returns valid UI components for a region
// code.
TEST_P(AddressUiTest, ComponentsAreValid) {
  EXPECT_TRUE(ComponentsAreValid(BuildComponents(
      GetParam(), localization_, kUiLanguageTag, &best_address_language_tag_)));
}

// Verifies that BuildComponentsWithLiterals() returns valid UI components for a
// region code.
TEST_P(AddressUiTest, ComponentsWithLiteralsAreValid) {
  EXPECT_TRUE(ComponentsAreValid(BuildComponentsWithLiterals(
      GetParam(), localization_, kUiLanguageTag, &best_address_language_tag_)));
}

// Verifies that BuildComponents() returns at most one input field of each type.
TEST_P(AddressUiTest, UniqueFieldTypes) {
  std::set<AddressField> fields;
  const std::vector<AddressUiComponent>& components =
      BuildComponents(GetParam(), localization_, kUiLanguageTag,
                      &best_address_language_tag_);
  for (const auto& component : components) {
    EXPECT_TRUE(fields.insert(component.field).second);
  }
}

// Verifies that BuildComponentsWithLiterals() returns at most one input field
// of each type.
TEST_P(AddressUiTest, UniqueFieldTypesByBuildComponentsWithLiterals) {
  std::set<AddressField> fields;
  const std::vector<AddressUiComponent>& components =
      BuildComponentsWithLiterals(GetParam(), localization_, kUiLanguageTag,
                                  &best_address_language_tag_);
  for (const auto& component : components) {
    // Literal components don't have types.
    if (component.literal.empty()) {
      EXPECT_TRUE(fields.insert(component.field).second);
    }
  }
}

// Test all regions codes.
INSTANTIATE_TEST_SUITE_P(AllRegions, AddressUiTest,
                         testing::ValuesIn(GetRegionCodes()));

// Verifies that BuildComponents() returns an empty vector for an invalid region
// code.
TEST_F(AddressUiTest, InvalidRegionCodeReturnsEmptyVector) {
  EXPECT_TRUE(BuildComponents(
      "INVALID-REGION-CODE", localization_, kUiLanguageTag,
      &best_address_language_tag_).empty());
}

// Verifies that BuildComponentsWithLiteras() does return literals. It uses "LV"
// as an aribtrary short example that has exactly one literal and 4 new lines.
TEST_F(AddressUiTest, ComponentsWithLiteralsReadsLiteralsForLV) {
  const std::vector<AddressUiComponent>& components =
      BuildComponentsWithLiterals("LV", localization_, kUiLanguageTag,
                                  &best_address_language_tag_);
  std::string literal;
  int newlines_count = 0;
  for (const auto& component : components) {
    if (!component.literal.empty()) {
      if (component.literal == "\n") {
        newlines_count++;
        continue;
      }
      // There should be exactly one non-newline literal.
      EXPECT_TRUE(literal.empty());
      literal = component.literal;
    }
  }
  EXPECT_EQ(", ", literal);
  EXPECT_EQ(4, newlines_count);
}

// Test data for determining the best language tag and whether the right format
// pattern was used (fmt vs lfmt).
struct LanguageTestCase {
  LanguageTestCase(const std::string& region_code,
                   const std::string& ui_language_tag,
                   const std::string& expected_best_address_language_tag,
                   AddressField expected_first_field)
      : region_code(region_code),
        ui_language_tag(ui_language_tag),
        expected_best_address_language_tag(expected_best_address_language_tag),
        expected_first_field(expected_first_field) {}

  ~LanguageTestCase() = default;

  // The CLDR region code to test.
  const std::string region_code;

  // The BCP 47 language tag to test.
  const std::string ui_language_tag;

  // The expected value for the best language tag returned by BuildComponents().
  const std::string expected_best_address_language_tag;

  // The first field expected to be returned from BuildComponents(). Useful for
  // determining whether the returned format is in Latin or default order.
  const AddressField expected_first_field;
};

class BestAddressLanguageTagTest
    : public testing::TestWithParam<LanguageTestCase> {
 public:
  BestAddressLanguageTagTest(const BestAddressLanguageTagTest&) = delete;
  BestAddressLanguageTagTest& operator=(const BestAddressLanguageTagTest&) =
      delete;

 protected:
  BestAddressLanguageTagTest() = default;
  Localization localization_;
  std::string best_address_language_tag_;
};

std::string GetterStub(int) { return std::string(); }

TEST_P(BestAddressLanguageTagTest, CorrectBestAddressLanguageTag) {
  localization_.SetGetter(&GetterStub);
  const std::vector<AddressUiComponent>& components = BuildComponents(
      GetParam().region_code, localization_, GetParam().ui_language_tag,
      &best_address_language_tag_);
  EXPECT_EQ(GetParam().expected_best_address_language_tag,
            best_address_language_tag_);
  ASSERT_FALSE(components.empty());
  EXPECT_EQ(GetParam().expected_first_field, components.front().field);
}

INSTANTIATE_TEST_SUITE_P(
    LanguageTestCases, BestAddressLanguageTagTest,
    testing::Values(
        // Armenia supports hy and has a Latin format.
        LanguageTestCase("AM", "", "hy", RECIPIENT),
        LanguageTestCase("AM", "hy", "hy", RECIPIENT),
        LanguageTestCase("AM", "en", "hy-Latn", RECIPIENT),

        // P.R. China supports zh and has a Latin format.
        LanguageTestCase("CN", "zh-hans", "zh", POSTAL_CODE),
        LanguageTestCase("CN", "zh-hant", "zh", POSTAL_CODE),
        LanguageTestCase("CN", "zh-hans-CN", "zh", POSTAL_CODE),
        LanguageTestCase("CN", "zh", "zh", POSTAL_CODE),
        LanguageTestCase("CN", "ZH_HANS", "zh", POSTAL_CODE),
        LanguageTestCase("CN", "zh-cmn-Hans-CN", "zh", POSTAL_CODE),
        LanguageTestCase("CN", "zh-Latn", "zh-Latn", RECIPIENT),
        LanguageTestCase("CN", "zh-latn-CN", "zh-Latn", RECIPIENT),
        LanguageTestCase("CN", "en", "zh-Latn", RECIPIENT),
        LanguageTestCase("CN", "ja", "zh-Latn", RECIPIENT),
        LanguageTestCase("CN", "ko", "zh-Latn", RECIPIENT),
        LanguageTestCase("CN", "ZH_LATN", "zh-Latn", RECIPIENT),
        // Libaddressinput does not have information about extended language
        // subtags, so it uses the zh-Latn language tag for all base languages
        // that are not zh, even if it's effectively the same language.
        // Mandarin Chinese, Simplified script, as used in China:
        LanguageTestCase("CN", "cmn-Hans-CN", "zh-Latn", RECIPIENT),

        // Hong Kong supports zh-Hant and en. It has a Latin format.
        LanguageTestCase("HK", "zh", "zh-Hant", ADMIN_AREA),
        LanguageTestCase("HK", "zh-hans", "zh-Hant", ADMIN_AREA),
        LanguageTestCase("HK", "zh-hant", "zh-Hant", ADMIN_AREA),
        LanguageTestCase("HK", "zh-yue-HK", "zh-Hant", ADMIN_AREA),
        LanguageTestCase("HK", "en", "en", ADMIN_AREA),
        LanguageTestCase("HK", "zh-latn", "zh-Latn", RECIPIENT),
        LanguageTestCase("HK", "fr", "zh-Latn", RECIPIENT),
        LanguageTestCase("HK", "ja", "zh-Latn", RECIPIENT),
        LanguageTestCase("HK", "ko", "zh-Latn", RECIPIENT),
        // Libaddressinput does not have information about extended language
        // subtags, so it uses the zh-Latn language tag for all base languages
        // that are not zh or en, even if it's effectively the same language.
        // Cantonese Chinese, as used in Hong Kong:
        LanguageTestCase("HK", "yue-HK", "zh-Latn", RECIPIENT),

        // Macao supports zh-Hant and pt. It has a Latin format.
        LanguageTestCase("MO", "zh", "zh-Hant", STREET_ADDRESS),
        LanguageTestCase("MO", "zh-Hant", "zh-Hant", STREET_ADDRESS),
        LanguageTestCase("MO", "pt", "pt", STREET_ADDRESS),
        LanguageTestCase("MO", "zh-Latn", "zh-Latn", RECIPIENT),
        LanguageTestCase("MO", "en", "zh-Latn", RECIPIENT),

        // Switzerland supports de, fr, and it.
        LanguageTestCase("CH", "de", "de", ORGANIZATION),
        LanguageTestCase("CH", "de-DE", "de", ORGANIZATION),
        LanguageTestCase("CH", "de-Latn-DE", "de", ORGANIZATION),
        LanguageTestCase("CH", "fr", "fr", ORGANIZATION),
        LanguageTestCase("CH", "it", "it", ORGANIZATION),
        LanguageTestCase("CH", "en", "de", ORGANIZATION),

        // Antarctica does not have language information.
        LanguageTestCase("AQ", "en", "en", RECIPIENT),
        LanguageTestCase("AQ", "fr", "fr", RECIPIENT),
        LanguageTestCase("AQ", "es", "es", RECIPIENT),
        LanguageTestCase("AQ", "zh-Hans", "zh-Hans", RECIPIENT),

        // Egypt supports ar and has a Latin format.
        LanguageTestCase("EG", "ar", "ar", RECIPIENT),
        LanguageTestCase("EG", "ar-Arab", "ar", RECIPIENT),
        LanguageTestCase("EG", "ar-Latn", "ar-Latn", RECIPIENT),
        LanguageTestCase("EG", "fr", "ar-Latn", RECIPIENT),
        LanguageTestCase("EG", "fa", "ar-Latn", RECIPIENT),
        // Libaddressinput does not have language-to-script mapping, so it uses
        // the ar-Latn language tag for all base languages that are not ar, even
        // if the script is the same.
        LanguageTestCase("EG", "fa-Arab", "ar-Latn", RECIPIENT)));

}  // namespace
