// 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 "testdata_source.h"

#include <libaddressinput/callback.h>
#include <libaddressinput/source.h>

#include <cstddef>
#include <memory>
#include <string>

#include <gtest/gtest.h>

#include "region_data_constants.h"

namespace {

using i18n::addressinput::BuildCallback;
using i18n::addressinput::kDataFileName;
using i18n::addressinput::RegionDataConstants;
using i18n::addressinput::Source;
using i18n::addressinput::TestdataSource;

// Tests for TestdataSource object.
class TestdataSourceTest : public testing::TestWithParam<std::string> {
 public:
  TestdataSourceTest(const TestdataSourceTest&) = delete;
  TestdataSourceTest& operator=(const TestdataSourceTest&) = delete;

 protected:
  TestdataSourceTest()
      : source_(false),
        source_with_path_(false, kDataFileName),
        aggregate_source_(true),
        aggregate_source_with_path_(true, kDataFileName),
        success_(false),
        key_(),
        data_(),
        data_ready_(BuildCallback(this, &TestdataSourceTest::OnDataReady)) {}

  TestdataSource source_;
  TestdataSource source_with_path_;
  TestdataSource aggregate_source_;
  TestdataSource aggregate_source_with_path_;
  bool success_;
  std::string key_;
  std::string data_;
  const std::unique_ptr<const Source::Callback> data_ready_;

 private:
  void OnDataReady(bool success, const std::string& key, std::string* data) {
    ASSERT_FALSE(success && data == nullptr);
    success_ = success;
    key_ = key;
    if (data != nullptr) {
      data_ = *data;
      delete data;
    }
  }
};

// Returns testing::AssertionSuccess if |data| is valid callback data for
// |key|.
testing::AssertionResult DataIsValid(const std::string& data,
                                     const std::string& key) {
  if (data.empty()) {
    return testing::AssertionFailure() << "empty data";
  }

  std::string expected_data_begin = R"({"id":")" + key + R"(")";
  if (data.compare(0, expected_data_begin.length(), expected_data_begin) != 0) {
    return testing::AssertionFailure()
           << data << " does not begin with " << expected_data_begin;
  }

  // Verify that the data ends on "}.
  static const char kDataEnd[] = "\"}";
  static const size_t kDataEndLength = sizeof kDataEnd - 1;
  if (data.compare(data.length() - kDataEndLength,
                   kDataEndLength,
                   kDataEnd,
                   kDataEndLength) != 0) {
    return testing::AssertionFailure()
           << data << " does not end with " << kDataEnd;
  }

  return testing::AssertionSuccess();
}

// Verifies that TestdataSource gets valid data for a region code.
TEST_P(TestdataSourceTest, TestdataSourceHasValidDataForRegion) {
  std::string key = "data/" + GetParam();
  source_.Get(key, *data_ready_);

  EXPECT_TRUE(success_);
  EXPECT_EQ(key, key_);
  EXPECT_TRUE(DataIsValid(data_, key));
};

// Verifies that TestdataSource gets valid data for a region code.
TEST_P(TestdataSourceTest, TestdataSourceWithPathHasValidDataForRegion) {
  std::string key = "data/" + GetParam();
  source_with_path_.Get(key, *data_ready_);

  EXPECT_TRUE(success_);
  EXPECT_EQ(key, key_);
  EXPECT_TRUE(DataIsValid(data_, key));
};

// Returns testing::AssertionSuccess if |data| is valid aggregated callback
// data for |key|.
testing::AssertionResult AggregateDataIsValid(const std::string& data,
                                              const std::string& key) {
  if (data.empty()) {
    return testing::AssertionFailure() << "empty data";
  }

  std::string expected_data_begin = "{\"" + key;
  if (data.compare(0, expected_data_begin.length(), expected_data_begin) != 0) {
    return testing::AssertionFailure()
           << data << " does not begin with " << expected_data_begin;
  }

  // Verify that the data ends on "}}.
  static const char kDataEnd[] = "\"}}";
  static const size_t kDataEndLength = sizeof kDataEnd - 1;
  if (data.compare(data.length() - kDataEndLength,
                   kDataEndLength,
                   kDataEnd,
                   kDataEndLength) != 0) {
    return testing::AssertionFailure()
           << data << " does not end with " << kDataEnd;
  }

  return testing::AssertionSuccess();
}

// Verifies that TestdataSource gets valid aggregated data for a region code.
TEST_P(TestdataSourceTest, TestdataSourceHasValidAggregatedDataForRegion) {
  std::string key = "data/" + GetParam();
  aggregate_source_.Get(key, *data_ready_);

  EXPECT_TRUE(success_);
  EXPECT_EQ(key, key_);
  EXPECT_TRUE(AggregateDataIsValid(data_, key));
};

// Verifies that TestdataSource gets valid aggregated data for a region code.
TEST_P(TestdataSourceTest,
       TestdataSourceWithPathHasValidAggregatedDataForRegion) {
  std::string key = "data/" + GetParam();
  aggregate_source_with_path_.Get(key, *data_ready_);

  EXPECT_TRUE(success_);
  EXPECT_EQ(key, key_);
  EXPECT_TRUE(AggregateDataIsValid(data_, key));
};

// Test all region codes.
INSTANTIATE_TEST_SUITE_P(
    AllRegions, TestdataSourceTest,
    testing::ValuesIn(RegionDataConstants::GetRegionCodes()));

// Verifies that the key "data" also contains valid data.
TEST_F(TestdataSourceTest, GetExistingData) {
  static const std::string kKey = "data";
  source_.Get(kKey, *data_ready_);

  EXPECT_TRUE(success_);
  EXPECT_EQ(kKey, key_);
  EXPECT_TRUE(DataIsValid(data_, kKey));
}

// Verifies that requesting a missing key will return "{}".
TEST_F(TestdataSourceTest, GetMissingKeyReturnsEmptyDictionary) {
  static const std::string kJunkKey = "junk";
  source_.Get(kJunkKey, *data_ready_);

  EXPECT_TRUE(success_);
  EXPECT_EQ(kJunkKey, key_);
  EXPECT_EQ("{}", data_);
}

// Verifies that aggregate requesting of a missing key will also return "{}".
TEST_F(TestdataSourceTest, AggregateGetMissingKeyReturnsEmptyDictionary) {
  static const std::string kJunkKey = "junk";
  aggregate_source_.Get(kJunkKey, *data_ready_);

  EXPECT_TRUE(success_);
  EXPECT_EQ(kJunkKey, key_);
  EXPECT_EQ("{}", data_);
}

// Verifies that requesting an empty key will return "{}".
TEST_F(TestdataSourceTest, GetEmptyKeyReturnsEmptyDictionary) {
  static const std::string kEmptyKey;
  source_.Get(kEmptyKey, *data_ready_);

  EXPECT_TRUE(success_);
  EXPECT_EQ(kEmptyKey, key_);
  EXPECT_EQ("{}", data_);
}

}  // namespace
