/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "configuration_test.h"
#include "common/configuration.h"
#include "common/configuration_loader.h"
#include "utils/temp-file.h"
#include "utils/temp-dir.h"

#include <gmock/gmock.h>
#include <cstdio>
#include <fstream>

using ::testing::_;

using namespace hdfs;

namespace hdfs {

TEST(ConfigurationTest, TestDegenerateInputs) {
  /* Completely empty stream */
  {
    std::stringstream stream;
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>("");
    EXPECT_FALSE(config && "Empty stream");
  }

  /* No values */
  {
    std::string data = "<configuration></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(data);
    EXPECT_TRUE(config && "Blank config");
  }

  /* Extraneous values */
  {
    std::string data = "<configuration><spam></spam></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(data);
    EXPECT_TRUE(config && "Extraneous values");
  }
}

TEST(ConfigurationTest, TestBasicOperations) {
  /* Single value */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "value1");
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
  }

  /* Multiple values */
  {
    optional<Configuration> config =
        simpleConfig("key1", "value1", "key2", "value2");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
    EXPECT_EQ("value2", config->GetWithDefault("key2", ""));
  }

  /* Case-insensitive */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "value1");
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    EXPECT_EQ("value1", config->GetWithDefault("KEY1", ""));
  }

  /* No defaults */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "value1");
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    optional<std::string> value = config->Get("key1");
    EXPECT_TRUE((bool)value);
    EXPECT_EQ("value1", *value);
    EXPECT_FALSE(config->Get("key2"));
  }
}

TEST(ConfigurationTest, TestCompactValues) {
  {
    std::stringstream stream;
    stream << "<configuration><property name=\"key1\" "
              "value=\"value1\"/></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Compact value parse");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
  }
}

TEST(ConfigurationTest, TestMultipleResources) {
  /* Single value */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "value1");
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));

    std::stringstream stream2;
    simpleConfigStream(stream2, "key2", "value2");
    optional<Configuration> config2 =
        ConfigurationLoader().OverlayResourceString(config.value(), stream2.str());
    EXPECT_TRUE(config2 && "Parse second stream");
    EXPECT_EQ("value1", config2->GetWithDefault("key1", ""));
    EXPECT_EQ("value2", config2->GetWithDefault("key2", ""));
  }
}

TEST(ConfigurationTest, TestStringResource) {
  /* Single value */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "value1");
    std::string str = stream.str();

    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
  }
}

TEST(ConfigurationTest, TestValueOverlay) {
  /* Incremental updates */
  {
    ConfigurationLoader loader;
    std::stringstream stream;
    stream << "<configuration>"
            "<property><name>key1</name><value>value1</value><final>false</final></property>"
            "<property><name>final2</name><value>value2</value><final>true</final></property>"
            "</configuration>";
    optional<Configuration> config = loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
    EXPECT_EQ("value2", config->GetWithDefault("final2", ""));
    config = loader.OverlayValue(config.value(), "key3", "value3");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
    EXPECT_EQ("value2", config->GetWithDefault("final2", ""));
    EXPECT_EQ("value3", config->GetWithDefault("key3", ""));
    config = loader.OverlayValue(config.value(), "final2", "value4");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
    EXPECT_EQ("value2", config->GetWithDefault("final2", ""));
    EXPECT_EQ("value3", config->GetWithDefault("key3", ""));

    // Case insensitive overlay
    config = loader.OverlayValue(config.value(), "KEY3", "value3a");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
    EXPECT_EQ("value2", config->GetWithDefault("final2", ""));
    EXPECT_EQ("value3a", config->GetWithDefault("key3", ""));
  }
}

TEST(ConfigurationTest, TestFinal) {
  {
    /* Explicitly non-final non-compact value */
    std::stringstream stream;
    stream << "<configuration><property><name>key1</name><value>value1</"
              "value><final>false</final></property></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));

    std::stringstream stream2;
    simpleConfigStream(stream2, "key1", "value2");
    optional<Configuration> config2 =
        ConfigurationLoader().OverlayResourceString(config.value(), stream2.str());
    EXPECT_TRUE(config2 && "Parse second stream");
    EXPECT_EQ("value2", config2->GetWithDefault("key1", ""));
  }
  {
    /* Explicitly final non-compact value */
    std::stringstream stream;
    stream << "<configuration><property><name>key1</name><value>value1</"
              "value><final>true</final></property></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));

    std::stringstream stream2;
    simpleConfigStream(stream2, "key1", "value2");
    optional<Configuration> config2 =
        ConfigurationLoader().OverlayResourceString(config.value(), stream2.str());
    EXPECT_TRUE(config2 && "Parse second stream");
    EXPECT_EQ("value1", config2->GetWithDefault("key1", ""));
  }
  {
    /* Explicitly non-final compact value */
    std::stringstream stream;
    stream << "<configuration><property name=\"key1\" value=\"value1\" "
              "final=\"false\"/></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));

    std::stringstream stream2;
    simpleConfigStream(stream2, "key1", "value2");
    optional<Configuration> config2 =
        ConfigurationLoader().OverlayResourceString(config.value(), stream2.str());
    EXPECT_TRUE(config2 && "Parse second stream");
    EXPECT_EQ("value2", config2->GetWithDefault("key1", ""));
  }
  {
    /* Explicitly final compact value */
    std::stringstream stream;
    stream << "<configuration><property name=\"key1\" value=\"value1\" "
              "final=\"true\"/></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));

    std::stringstream stream2;
    simpleConfigStream(stream2, "key1", "value2");
    optional<Configuration> config2 =
        ConfigurationLoader().OverlayResourceString(config.value(), stream2.str());
    EXPECT_TRUE(config2 && "Parse second stream");
    EXPECT_EQ("value1", config2->GetWithDefault("key1", ""));
  }
  {
    /* Bogus final value */
    std::stringstream stream;
    stream << "<configuration><property><name>key1</name><value>value1</"
              "value><final>spam</final></property></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));

    std::stringstream stream2;
    simpleConfigStream(stream2, "key1", "value2");
    optional<Configuration> config2 =
        ConfigurationLoader().OverlayResourceString(config.value(), stream2.str());
    EXPECT_TRUE(config2 && "Parse second stream");
    EXPECT_EQ("value2", config2->GetWithDefault("key1", ""));
  }
  {
    /* Blank final value */
    std::stringstream stream;
    stream << "<configuration><property><name>key1</name><value>value1</"
              "value><final></final></property></configuration>";
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));

    std::stringstream stream2;
    simpleConfigStream(stream2, "key1", "value2");
    optional<Configuration> config2 =
        ConfigurationLoader().OverlayResourceString(config.value(), stream2.str());
    EXPECT_TRUE(config2 && "Parse second stream");
    EXPECT_EQ("value2", config2->GetWithDefault("key1", ""));
  }
}

TEST(ConfigurationTest, TestFileReads)
{
  // Single stream
  {
    TestUtils::TempFile tempFile;
    writeSimpleConfig(tempFile.GetFileName(), "key1", "value1");

    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config =
        config_loader.LoadFromFile<Configuration>(tempFile.GetFileName());
    EXPECT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
  }

  // Multiple files
  {
    TestUtils::TempFile tempFile;
    writeSimpleConfig(tempFile.GetFileName(), "key1", "value1");

    ConfigurationLoader loader;
    optional<Configuration> config =
        loader.LoadFromFile<Configuration>(tempFile.GetFileName());
    ASSERT_TRUE(config && "Parse first stream");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));

    TestUtils::TempFile tempFile2;
    writeSimpleConfig(tempFile2.GetFileName(), "key2", "value2");
    optional<Configuration> config2 =
        loader.OverlayResourceFile(*config, tempFile2.GetFileName());
    ASSERT_TRUE(config2 && "Parse second stream");
    EXPECT_EQ("value1", config2->GetWithDefault("key1", ""));
    EXPECT_EQ("value2", config2->GetWithDefault("key2", ""));
  }

  // Try to add a directory
  {
    TestUtils::TempDir tempDir;

    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.LoadFromFile<Configuration>(tempDir.GetPath());
    EXPECT_FALSE(config && "Add directory as file resource");
  }


  // Search path splitting
  {
    ConfigurationLoader loader;
    loader.SetSearchPath("foo:/bar:baz/:/fioux/:/bar/bar/bong");

    // Paths will have / appended to them if not already present
    EXPECT_EQ("foo/:/bar/:baz/:/fioux/:/bar/bar/bong/", loader.GetSearchPath());
  }

  // Search path
  {
    TestUtils::TempDir tempDir1;
    TestUtils::TempFile tempFile1(tempDir1.GetPath() + "/file1.xml");
    writeSimpleConfig(tempFile1.GetFileName(), "key1", "value1");
    TestUtils::TempDir tempDir2;
    TestUtils::TempFile tempFile2(tempDir2.GetPath() + "/file2.xml");
    writeSimpleConfig(tempFile2.GetFileName(), "key2", "value2");
    TestUtils::TempDir tempDir3;
    TestUtils::TempFile tempFile3(tempDir3.GetPath() + "/file3.xml");
    writeSimpleConfig(tempFile3.GetFileName(), "key3", "value3");

    ConfigurationLoader loader;
    loader.SetSearchPath(tempDir1.GetPath() + ":" + tempDir2.GetPath() + ":" +
                         tempDir3.GetPath());
    optional<Configuration> config1 = loader.LoadFromFile<Configuration>("file1.xml");
    EXPECT_TRUE(config1 && "Parse first stream");
    optional<Configuration> config2 = loader.OverlayResourceFile(*config1, "file2.xml");
    EXPECT_TRUE(config2 && "Parse second stream");
    optional<Configuration> config3 = loader.OverlayResourceFile(*config2, "file3.xml");
    EXPECT_TRUE(config3 && "Parse third stream");
    EXPECT_EQ("value1", config3->GetWithDefault("key1", ""));
    EXPECT_EQ("value2", config3->GetWithDefault("key2", ""));
    EXPECT_EQ("value3", config3->GetWithDefault("key3", ""));
  }
}

TEST(ConfigurationTest, TestDefaultConfigs) {
  // Search path
  {
    TestUtils::TempDir tempDir;
    TestUtils::TempFile coreSite(tempDir.GetPath() + "/core-site.xml");
    writeSimpleConfig(coreSite.GetFileName(), "key1", "value1");

    ConfigurationLoader loader;
    loader.SetSearchPath(tempDir.GetPath());

    optional<Configuration> config = loader.LoadDefaultResources<Configuration>();
    EXPECT_TRUE(config && "Parse streams");
    EXPECT_EQ("value1", config->GetWithDefault("key1", ""));
  }
}

TEST(ConfigurationTest, TestIntConversions) {
  /* No defaults */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "1");
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    optional<int64_t> value = config->GetInt("key1");
    EXPECT_TRUE((bool)value);
    EXPECT_EQ(1, *value);
    EXPECT_FALSE(config->GetInt("key2"));
  }

  {
    optional<Configuration> config = simpleConfig("key1", "1");
    EXPECT_EQ(1, config->GetIntWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "-100");
    EXPECT_EQ(-100, config->GetIntWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key1", " 1 ");
    EXPECT_EQ(1, config->GetIntWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "");
    EXPECT_EQ(-1, config->GetIntWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "spam");
    EXPECT_EQ(-1, config->GetIntWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key2", "");
    EXPECT_EQ(-1, config->GetIntWithDefault("key1", -1));
  }
}

TEST(ConfigurationTest, TestDoubleConversions) {
  /* No defaults */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "1");
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    optional<double> value = config->GetDouble("key1");
    EXPECT_TRUE((bool)value);
    EXPECT_EQ(1, *value);
    EXPECT_FALSE(config->GetDouble("key2"));
  }

  {
    optional<Configuration> config = simpleConfig("key1", "1");
    EXPECT_EQ(1, config->GetDoubleWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "-100");
    EXPECT_EQ(-100, config->GetDoubleWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key1", " 1 ");
    EXPECT_EQ(1, config->GetDoubleWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "");
    EXPECT_EQ(-1, config->GetDoubleWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "spam");
    EXPECT_EQ(-1, config->GetDoubleWithDefault("key1", -1));
  }
  {
    optional<Configuration> config = simpleConfig("key2", "");
    EXPECT_EQ(-1, config->GetDoubleWithDefault("key1", -1));
  }
  { /* Out of range */
    optional<Configuration> config = simpleConfig("key2", "1e9999");
    EXPECT_EQ(-1, config->GetDoubleWithDefault("key1", -1));
  }
}

TEST(ConfigurationTest, TestBoolConversions) {
  /* No defaults */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "true");
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    optional<bool> value = config->GetBool("key1");
    EXPECT_TRUE((bool)value);
    EXPECT_EQ(true, *value);
    EXPECT_FALSE(config->GetBool("key2"));
  }

  {
    optional<Configuration> config = simpleConfig("key1", "true");
    EXPECT_EQ(true, config->GetBoolWithDefault("key1", false));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "tRuE");
    EXPECT_EQ(true, config->GetBoolWithDefault("key1", false));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "false");
    EXPECT_FALSE(config->GetBoolWithDefault("key1", true));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "FaLsE");
    EXPECT_FALSE(config->GetBoolWithDefault("key1", true));
  }
  {
    optional<Configuration> config = simpleConfig("key1", " FaLsE ");
    EXPECT_FALSE(config->GetBoolWithDefault("key1", true));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "");
    EXPECT_EQ(true, config->GetBoolWithDefault("key1", true));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "spam");
    EXPECT_EQ(true, config->GetBoolWithDefault("key1", true));
  }
  {
    optional<Configuration> config = simpleConfig("key1", "");
    EXPECT_EQ(true, config->GetBoolWithDefault("key2", true));
  }
}

TEST(ConfigurationTest, TestUriConversions) {
  /* No defaults */
  {
    std::stringstream stream;
    simpleConfigStream(stream, "key1", "hdfs:///");
    ConfigurationLoader config_loader;
    config_loader.ClearSearchPath();
    optional<Configuration> config = config_loader.Load<Configuration>(stream.str());
    EXPECT_TRUE(config && "Parse single value");
    optional<URI> value = config->GetUri("key1");
    EXPECT_TRUE((bool)value);
    EXPECT_EQ("hdfs:///", value->str());
    EXPECT_FALSE(config->GetUri("key2"));
  }

  {
    optional<Configuration> config = simpleConfig("key1", "hdfs:///");
    EXPECT_EQ("hdfs:///", config->GetUriWithDefault("key1", "http:///").str());
  }
  {
    optional<Configuration> config = simpleConfig("key1", " hdfs:/// ");
    EXPECT_EQ("hdfs:///", config->GetUriWithDefault("key1", "http:///").str());
  }
  {
    optional<Configuration> config = simpleConfig("key1", "");
    EXPECT_EQ("", config->GetUriWithDefault("key1", "http:///").str());
  }
  {
    optional<Configuration> config = simpleConfig("key1", "%%");  // invalid URI
    EXPECT_EQ("http:///", config->GetUriWithDefault("key1", "http:///").str());
  }
  {
    optional<Configuration> config = simpleConfig("key2", "hdfs:///");
    EXPECT_EQ("http:///", config->GetUriWithDefault("key1", "http:///").str());
  }
}



int main(int argc, char *argv[]) {
  /*
   *  The following line must be executed to initialize Google Mock
   * (and Google Test) before running the tests.
   */
  ::testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}
}
