#include "BATesting.h"

#include "Base/Util/PathUtil.h"
#include "Tests/GTestWrapper/google_test.h"
#include <algorithm>
#include <filesystem>
#include <fstream>

class FileSystemUtilsTest : public ::testing::Test {
protected:
    void SetUp() override
    {
        Base::Path::createDirectories(BATesting::TestOutDir_Base);
        ASSERT_TRUE(std::filesystem::exists(BATesting::TestOutDir_Base));
        ASSERT_TRUE(assureNonExistingTestCasePath());
    }

    void TearDown() override
    {
        ASSERT_TRUE(assureNonExistingTestCasePath());
        // by design no removing of BATesting::TestOutDir_Base which may have been
        // created in SetUp
    }

    std::string testCaseFolderName() const
    {
        const auto* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
        return test_info->test_suite_name() + std::string("_") + test_info->name();
    }

    std::string testCasePath() const
    {
        return Base::Path::jointPath(BATesting::TestOutDir_Base, testCaseFolderName());
    }

    //! Assures the test case specific path is not existent. Removes it if necessary.
    bool assureNonExistingTestCasePath() const
    {
        // current dir must not be the dir to be removed
        std::filesystem::current_path(BATesting::TestOutDir_Base);
        std::filesystem::remove_all(testCasePath());
        return !std::filesystem::exists(testCasePath());
    }
};


TEST_F(FileSystemUtilsTest, extension)
{
    EXPECT_EQ(Base::Path::extension(""), "");
    EXPECT_EQ(Base::Path::extension("/home/james/"), "");
    EXPECT_EQ(Base::Path::extension("/home/james/."), "");
    EXPECT_EQ(Base::Path::extension("/home/james/.."), "");
    EXPECT_EQ(Base::Path::extension("/home/james/.hidden"), "");
    EXPECT_EQ(Base::Path::extension("/home/james/.hidden.txt"), ".txt");
    EXPECT_EQ(Base::Path::extension("/home/james/file.txt"), ".txt");
    EXPECT_EQ(Base::Path::extension("/home/james/file.txt.gz"), ".gz");
    EXPECT_EQ(Base::Path::extension("/home/james/file.txt.GZ"), ".GZ");
}

TEST_F(FileSystemUtilsTest, hasExtension)
{
    EXPECT_TRUE(Base::Path::hasExtension("", ""));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/", ""));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/.", ""));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/..", ""));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/.hidden", ""));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/.hidden.txt", ".txt"));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/file.txt", ".txt"));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/file.TXT", ".txt"));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/file.txt.gz", ".gz"));
    EXPECT_TRUE(Base::Path::hasExtension("/home/james/file.TXT.GZ", ".gz"));
}

TEST_F(FileSystemUtilsTest, extensions)
{
    EXPECT_EQ(Base::Path::extensions(""), "");
    EXPECT_EQ(Base::Path::extensions("/home/james/"), "");
    EXPECT_EQ(Base::Path::extensions("/home/james/."), "");
    EXPECT_EQ(Base::Path::extensions("/home/james/.."), "");
    EXPECT_EQ(Base::Path::extensions("/home/james/.hidden"), "");
    EXPECT_EQ(Base::Path::extensions("/home/james/.hidden.txt"), ".txt");
    EXPECT_EQ(Base::Path::extensions("/home/james/.hidden.txt.gz"), ".txt.gz");
    EXPECT_EQ(Base::Path::extensions("/home/james/f"), "");
    EXPECT_EQ(Base::Path::extensions("/home/james/f.txt"), ".txt");
    EXPECT_EQ(Base::Path::extensions("/home/james/file.txt"), ".txt");
    EXPECT_EQ(Base::Path::extensions("/home/james/file.txt.gz"), ".txt.gz");
}

TEST_F(FileSystemUtilsTest, filename)
{
    EXPECT_EQ(Base::Path::filename(""), "");
    EXPECT_EQ(Base::Path::filename("/home/james/"), "");
    EXPECT_EQ(Base::Path::filename("/home/james/."), ".");   // sic! according to C++17
    EXPECT_EQ(Base::Path::filename("/home/james/.."), ".."); // sic! according to C++17
    EXPECT_EQ(Base::Path::filename("/home/james/.hidden"), ".hidden");
    EXPECT_EQ(Base::Path::filename("/home/james/file.txt"), "file.txt");
    EXPECT_EQ(Base::Path::filename("/home/james/file"), "file");
}

TEST_F(FileSystemUtilsTest, stem)
{
    EXPECT_EQ(Base::Path::stem(""), "");
    EXPECT_EQ(Base::Path::stem("/home/james/"), "");
    EXPECT_EQ(Base::Path::stem("/home/james/."), ".");
    EXPECT_EQ(Base::Path::stem("/home/james/.."), "..");
    EXPECT_EQ(Base::Path::stem("/home/james/.hidden"), ".hidden");
    EXPECT_EQ(Base::Path::stem("/home/james/.hidden.txt"), ".hidden");
    EXPECT_EQ(Base::Path::stem("/home/james/.hidden.txt.gz"), ".hidden.txt");
    EXPECT_EQ(Base::Path::stem("/home/james/filename.txt"), "filename");
    EXPECT_EQ(Base::Path::stem("/home/james/filename.txt.gz"), "filename.txt");
}

TEST_F(FileSystemUtilsTest, StemAndExtension)
{
    EXPECT_EQ(Base::Path::stem_ext(""), "");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/"), "");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/."), ".");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/.."), "..");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/.hidden"), ".hidden");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/.hidden.txt"), ".hidden");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/.hidden.txt.gz"), ".hidden");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/filename"), "filename");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/filename.txt"), "filename");
    EXPECT_EQ(Base::Path::stem_ext("/home/james/filename.txt.gz"), "filename");
}

TEST_F(FileSystemUtilsTest, createDirectory)
{
    // with absolute path
    EXPECT_TRUE(Base::Path::createDirectories(testCasePath()));
    EXPECT_FALSE(Base::Path::createDirectories(testCasePath()));

    // with relative path
    std::filesystem::current_path(testCasePath());
    EXPECT_TRUE(Base::Path::createDirectories("sub"));
    EXPECT_FALSE(Base::Path::createDirectories("sub"));
    EXPECT_TRUE(std::filesystem::exists("sub"));
    EXPECT_TRUE(std::filesystem::exists(Base::Path::jointPath(testCasePath(), "sub")));
}

TEST_F(FileSystemUtilsTest, createDirectories)
{
    // with absolute path
    const auto sub1 = Base::Path::jointPath(testCasePath(), "sub1");
    const auto sub2 = Base::Path::jointPath(sub1, "sub2");

    EXPECT_TRUE(Base::Path::createDirectories(sub2));
    EXPECT_FALSE(Base::Path::createDirectories(sub2));
    EXPECT_TRUE(std::filesystem::exists(testCasePath()));
    EXPECT_TRUE(std::filesystem::exists(sub1));
    EXPECT_TRUE(std::filesystem::exists(sub2));

    // with relative path
    const auto* const sub4 = "sub3/sub4";
    std::filesystem::current_path(sub2);
    EXPECT_TRUE(Base::Path::createDirectories(sub4));
    EXPECT_FALSE(Base::Path::createDirectories(sub4));
    EXPECT_TRUE(std::filesystem::exists("sub3"));
    EXPECT_TRUE(std::filesystem::exists("sub3/sub4"));
    EXPECT_TRUE(std::filesystem::exists(Base::Path::jointPath(sub2, sub4)));
}

TEST_F(FileSystemUtilsTest, jointPath)
{
#ifdef _WIN32
    EXPECT_EQ(Base::Path::jointPath("a", "b"), "a\\b");
#else
    EXPECT_EQ(Base::Path::jointPath("a", "b"), "a/b");
#endif

    EXPECT_FAILED_ASSERT(Base::Path::jointPath("", ""));
    EXPECT_FAILED_ASSERT(Base::Path::jointPath("a", ""));
    EXPECT_EQ(Base::Path::jointPath("", "b"), "b");
}

TEST_F(FileSystemUtilsTest, filesInDirectoryIsFileExists)
{
    EXPECT_ANY_THROW(Base::Path::filesInDirectory("non-existent/directory"));

    // assure clean preconditions
    ASSERT_TRUE(Base::Path::createDirectories(testCasePath()));
    ASSERT_TRUE(Base::Path::filesInDirectory(testCasePath()).empty());

    // create a few files
    std::filesystem::current_path(testCasePath());
    std::ofstream("file1.txt");
    std::ofstream("file2.txt");
    std::ofstream("file3.txt");

    EXPECT_EQ(Base::Path::filesInDirectory(testCasePath()).size(), 3uL); // abs
    EXPECT_EQ(Base::Path::filesInDirectory(".").size(), 3uL);            // rel
    const auto files = Base::Path::filesInDirectory(".");
    EXPECT_TRUE(std::find(files.begin(), files.end(), "file1.txt") != files.end());
    EXPECT_TRUE(std::find(files.begin(), files.end(), "file2.txt") != files.end());
    EXPECT_TRUE(std::find(files.begin(), files.end(), "file3.txt") != files.end());

    // tests for IsFileExists
    EXPECT_TRUE(Base::Path::IsFileExists("file1.txt"));
    EXPECT_TRUE(Base::Path::IsFileExists("file2.txt"));
    EXPECT_TRUE(Base::Path::IsFileExists("file3.txt"));
    EXPECT_FALSE(Base::Path::IsFileExists("nonexisting.txt"));
}
