// Copyright 2014 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

#include "android/base/files/PathUtils.h"

#include "android/base/containers/StringVector.h"
#include "android/base/String.h"

#include <gtest/gtest.h>

#define ARRAY_SIZE(x)  (sizeof(x)/sizeof(x[0]))

namespace android {
namespace base {

static const int kHostTypeCount = PathUtils::kHostTypeCount;

TEST(PathUtils, isDirSeparator) {
    static const struct {
        int ch;
        bool expected[kHostTypeCount];
    } kData[] = {
        { '/', { true, true }},
        { '\\', { false, true }},
        { '$', { false, false }},
        { ':', { false, false }},
        { ';', { false, false }},
    };

    for (size_t n = 0; n < ARRAY_SIZE(kData); ++n) {
        int ch = kData[n].ch;
        EXPECT_EQ(kData[n].expected[kHostPosix],
                  PathUtils::isDirSeparator(ch, kHostPosix))
                << "Testing '" << ch << "'";
        EXPECT_EQ(kData[n].expected[kHostWin32],
                  PathUtils::isDirSeparator(ch, kHostWin32))
                << "Testing '" << ch << "'";
        EXPECT_EQ(kData[n].expected[kHostType],
                  PathUtils::isDirSeparator(ch))
                << "Testing '" << ch << "'";
    }
}

TEST(PathUtils, isPathSeparator) {
    static const struct {
        int ch;
        bool expected[kHostTypeCount];
    } kData[] = {
        { ':', { true, false }},
        { ';', { false, true }},
        { '/', { false, false }},
        { '\\', { false, false }},
        { '$', { false, false }},
    };

    for (size_t n = 0; n < ARRAY_SIZE(kData); ++n) {
        int ch = kData[n].ch;
        EXPECT_EQ(kData[n].expected[kHostPosix],
                  PathUtils::isPathSeparator(ch, kHostPosix))
                << "Testing '" << ch << "'";
        EXPECT_EQ(kData[n].expected[kHostWin32],
                  PathUtils::isPathSeparator(ch, kHostWin32))
                << "Testing '" << ch << "'";
        EXPECT_EQ(kData[n].expected[kHostType],
                  PathUtils::isPathSeparator(ch))
                << "Testing '" << ch << "'";
    }
}

TEST(PathUtils, rootPrefixSize) {
    static const struct {
        const char* path;
        size_t prefixSize[kHostTypeCount];
    } kData[] = {
        { NULL, { 0u, 0u} },
        { "", { 0u, 0u } },
        { "foo", { 0u, 0u } },
        { "foo/bar", { 0u, 0u } },
        { "/foo", { 1u, 1u } },
        { "//foo", { 1u, 5u } },
        { "//foo/bar", { 1u, 6u } },
        { "c:", { 0u, 2u } },
        { "c:foo", { 0u, 2u } },
        { "c/foo", { 0u, 0u } },
        { "c:/foo", { 0u, 3u } },
        { "c:\\", { 0u, 3u } },
        { "c:\\\\", { 0u, 3u } },
        { "1:/foo", { 0u, 0u } },
        { "\\", { 0u, 1u } },
        { "\\foo", { 0u, 1u } },
        { "\\foo\\bar", { 0u, 1u } },
        { "\\\\foo", { 0u, 5u } },
        { "\\\\foo\\", { 0u, 6u } },
        { "\\\\foo\\\\bar", { 0u, 6u } },
    };
    for (size_t n = 0; n < ARRAY_SIZE(kData); ++n) {
        const char* path = kData[n].path;
        EXPECT_EQ(kData[n].prefixSize[kHostPosix],
                  PathUtils::rootPrefixSize(path, kHostPosix))
                << "Testing '" << (path ? path : "<NULL>") << "'";
        EXPECT_EQ(kData[n].prefixSize[kHostWin32],
                  PathUtils::rootPrefixSize(path, kHostWin32))
                << "Testing '" << (path ? path : "<NULL>") << "'";
        EXPECT_EQ(kData[n].prefixSize[kHostType],
                  PathUtils::rootPrefixSize(path))
                << "Testing '" << (path ? path : "<NULL>") << "'";
    }
}

TEST(PathUtils, isAbsolute) {
    static const struct {
        const char* path;
        bool expected[kHostTypeCount];
    } kData[] = {
        { "foo", { false, false } },
        { "/foo", { true, true } },
        { "\\foo", { false, true } },
        { "/foo/bar", { true, true } },
        { "\\foo\\bar", { false, true } },
        { "C:foo", { false, false } },
        { "C:/foo", { false, true } },
        { "C:\\foo", { false, true } },
        { "//server", { true, false } },
        { "//server/path", { true, true } },
    };
    for (size_t n = 0; n < ARRAY_SIZE(kData); ++n) {
        const char* path = kData[n].path;
        EXPECT_EQ(kData[n].expected[kHostPosix],
                  PathUtils::isAbsolute(path, kHostPosix))
                << "Testing '" << (path ? path : "<NULL>") << "'";
        EXPECT_EQ(kData[n].expected[kHostWin32],
                  PathUtils::isAbsolute(path, kHostWin32))
                << "Testing '" << (path ? path : "<NULL>") << "'";
        EXPECT_EQ(kData[n].expected[kHostType],
                  PathUtils::isAbsolute(path))
                << "Testing '" << (path ? path : "<NULL>") << "'";
    }
}

static const int kMaxComponents = 10;

typedef const char* ComponentList[kMaxComponents];

static void checkComponents(const ComponentList& expected,
                            const StringVector& components,
                            const char* hostType,
                            const char* path) {
    size_t m;
    for (m = 0; m < components.size(); ++m) {
        if (!expected[m])
            break;
        const char* component = expected[m];
        EXPECT_STREQ(component, components[m].c_str())
                << hostType << " component #" << (m + 1) << " in " << path;
    }
    EXPECT_EQ(m, components.size())
                << hostType << " component #" << (m + 1) << " in " << path;
}

TEST(PathUtils, decompose) {
    static const struct {
        const char* path;
        const ComponentList components[kHostTypeCount];
    } kData[] = {
        { "", { { NULL }, { NULL } } },
        { "foo", {
            { "foo", NULL },
            { "foo", NULL } } },
        { "foo/", {
            { "foo", NULL },
            { "foo", NULL } } },
        { "foo/bar", {
            { "foo", "bar", NULL },
            { "foo", "bar", NULL } } },
        { "foo//bar/zoo", {
            { "foo", "bar", "zoo", NULL },
            { "foo", "bar", "zoo", NULL } } },
        { "\\foo\\bar\\", {
            { "\\foo\\bar\\", NULL },
            { "\\", "foo", "bar", NULL } } },
        { "C:foo\\bar", {
            { "C:foo\\bar", NULL },
            { "C:", "foo", "bar", NULL } } },
        { "C:/foo", {
            { "C:", "foo", NULL },
            { "C:/", "foo", NULL } } },
        { "/foo", {
            { "/", "foo", NULL },
            { "/", "foo", NULL } } },
        { "\\foo", {
            { "\\foo", NULL },
            { "\\", "foo", NULL } } },
    };
    for (size_t n = 0; n < ARRAY_SIZE(kData); ++n) {
        const char* path = kData[n].path;
        checkComponents(kData[n].components[kHostPosix],
                        PathUtils::decompose(path, kHostPosix),
                        "posix",
                        path);

        checkComponents(kData[n].components[kHostWin32],
                        PathUtils::decompose(path, kHostWin32),
                        "win32",
                        path);

        checkComponents(kData[n].components[kHostType],
                        PathUtils::decompose(path),
                        "host",
                        path);
    }
}

static StringVector componentListToVector(
        const ComponentList& input) {
    StringVector result;
    for (size_t i = 0; input[i]; ++i)
        result.push_back(input[i]);
    return result;
}

TEST(PathUtils, recompose) {
    static const struct {
        const ComponentList input;
        const char* path[kHostTypeCount];
    } kData[] = {
        { { NULL }, { "", "" } },
        { { ".", NULL }, { ".", "." } },
        { { "..", NULL }, { "..", ".." } },
        { { "/", NULL }, { "/", "/" } },
        { { "/", "foo", NULL }, { "/foo", "/foo" } },
        { { "\\", "foo", NULL }, { "\\/foo", "\\foo" } },
        { { "foo", NULL }, { "foo", "foo" } },
        { { "foo", "bar", NULL }, { "foo/bar", "foo\\bar" } },
        { { ".", "foo", "..", NULL }, { "./foo/..", ".\\foo\\.." } },
        { { "C:", "foo", NULL }, { "C:/foo", "C:foo" } },
    };
    for (size_t n = 0; n < ARRAY_SIZE(kData); ++n) {
        StringVector components = componentListToVector(kData[n].input);
        EXPECT_STREQ(kData[n].path[kHostPosix],
                     PathUtils::recompose(components, kHostPosix).c_str());
        EXPECT_STREQ(kData[n].path[kHostWin32],
                     PathUtils::recompose(components, kHostWin32).c_str());
        EXPECT_STREQ(kData[n].path[kHostType],
                     PathUtils::recompose(components).c_str());
    }
}


// Convert a vector of strings |components| into a file path, using
// |separator| as the directory separator.
static String componentsToPath(
        const ComponentList& components,
        char separator) {
    String result;
    for (size_t n = 0; components[n]; ++n) {
        if (n)
            result += separator;
        result += components[n];
    }
    return result;
}

static String stringVectorToPath(
        const StringVector& input,
        char separator) {
    String result;
    for (size_t n = 0; n < input.size(); ++n) {
        if (n)
            result += separator;
        result += input[n];
    }
    return result;
}

TEST(PathUtils, simplifyComponents) {
    static const struct {
        const ComponentList input;
        const ComponentList expected;
    } kData[] = {
        { { NULL }, { ".", NULL } },
        { { ".", NULL }, { ".", NULL } },
        { { "..", NULL }, { "..", NULL } },
        { { "foo", NULL }, { "foo", NULL } },
        { { "foo", ".", NULL }, { "foo", NULL } },
        { { "foo", "bar", NULL }, { "foo", "bar", NULL } },
        { { ".", "foo", ".", "bar", ".", NULL }, { "foo", "bar", NULL } },
        { { "foo", "..", "bar", NULL }, { "bar", NULL } },
        { { ".", "..", "foo", "bar", NULL }, { "..", "foo", "bar", NULL } },
        { { "..", "foo", "..", "bar", NULL }, { "..", "bar", NULL } },
        { { "foo", "..", "..", "bar", NULL }, { "..", "bar", NULL } },
    };
    for (size_t n = 0; n < ARRAY_SIZE(kData); ++n) {
        const ComponentList& input = kData[n].input;
        String inputPath = componentsToPath(input, '!');
        String expectedPath = componentsToPath(kData[n].expected, '!');
        StringVector components = componentListToVector(input);
        PathUtils::simplifyComponents(&components);
        String path = stringVectorToPath(components, '!');

        EXPECT_STREQ(expectedPath.c_str(), path.c_str())
            << "When simplifying " << inputPath.c_str();
    }
};

}  // namespace android
}  // namespace base