/*
 * This file is part of KDevelop
 * Copyright 2012 Milian Wolff <mail@milianw.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) version 3, or any
 * later version accepted by the membership of KDE e.V. (or its
 * successor approved by the membership of KDE e.V.), which shall
 * act as a proxy defined in Section 6 of version 3 of the license.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef KDEVELOP_PATH_H
#define KDEVELOP_PATH_H

#include "utilexport.h"

#include <QMetaType>
#include <QString>
#include <QVector>
#include <KUrl>

namespace KDevelop {

/**
 * @brief Path data type optimized for memory consumption.
 *
 * This class holds data that represents a local or remote path.
 * In the project model e.g. we usually store whole trees such as
 *
 * /foo/
 * /foo/bar/
 * /foo/bar/asdf.txt
 *
 * Normal QString/KUrl/QUrl types would not share any memory for these paths
 * at all. This class though can share the segments of the paths and thus
 * consume far less total memory.
 *
 * Just like the URL types, the Path can point to a remote location.
 *
 * Example for how to leverage memory sharing for the above input data:
 *
 * @code
 * Path foo("/foo");
 * Path bar(foo, "bar");
 * Path asdf(foo, "asdf.txt");
 * @endcode
 *
 * @note Just as with QString e.g. you won't share any data implicitly when
 * you do something like this:
 *
 * @code
 * Path foo1("/foo");
 * Path foo2("/foo");
 * @endcode
 */
class KDEVPLATFORMUTIL_EXPORT Path
{
public:
    typedef QVector<Path> List;

    /**
     * Construct an empty, invalid Path.
     */
    Path();

    /**
     * Create a Path out of a string representation of a path or URL.
     *
     * @note Not every kind of remote URL is supported, rather only path-like
     * URLs without fragments, queries, sub-Paths and the like are supported.
     *
     * Empty paths or URLs containing one of the following are considered invalid:
     * - URL fragments (i.e. ...#fragment)
     * - URL queries (i.e. ...?query=)
     * - sub-URLs (i.e. file:///tmp/kde.tgz#gzip:/#tar:/kdevelop)
     *
     * @sa isValid()
     */
    explicit Path(const QString& pathOrUrl);

    /**
     * Convert a KUrl to a Path.
     *
     * @note Not every kind of remote URL is supported, rather only path-like
     * URLs without fragments, queries, sub-Paths and the like are supported.
     *
     * Empty paths or URLs containing one of the following are considered invalid:
     * - URL fragments (i.e. ...#fragment)
     * - URL queries (i.e. ...?query=)
     * - sub-URLs (i.e. file:///tmp/kde.tgz#gzip:/#tar:/kdevelop)
     *
     * @sa isValid()
     */
    explicit Path(const KUrl& url);

    /**
     * Create a copy of @p base and optionally append a path segment @p subPath.
     *
     * This implicitly shares the data of @p base and thus is very efficient
     * memory wise compared to creating two Paths from separate strings.
     *
     * @p subPath A relative or absolute path. If this is an absolute path then
     * the path in @p base will be ignored and only the remote data copied. If
     * this is a relative path it will be combined with @p base.
     *
     * @sa addPath()
     */
    Path(const Path& base, const QString& subPath = QString());

    /**
     * Equality comparison between @p other and this Path.
     *
     * @return true if @p other is equal to this Path.
     */
    inline bool operator==(const Path& other) const
    {
        return m_data == other.m_data;
    }

    /**
     * Inequality comparison between @p other and this Path.
     *
     * @return true if @p other is different from this Path.
     */
    inline bool operator!=(const Path& other) const
    {
        return !operator==(other);
    }

    /**
     * Less-than path comparison between @p other and this Path.
     *
     * @return true if this Path is less than @p other.
     */
    bool operator<(const Path& other) const;

    /**
     * Greater-than path comparison between @p other and this Path.
     *
     * @return true if this Path is greater than @p other.
     */
    inline bool operator>(const Path& other) const
    {
        return other < *this;
    }

    /**
     * Less-than-equal path comparison between @p other and this Path.
     *
     * @return true if this Path is less than @p other or equal.
     */
    inline bool operator<=(const Path& other) const
    {
        return *this < other || other == *this;
    }

    /**
     * Greater-than-equal path comparison between @p other and this Path.
     *
     * @return true if this Path is greater than @p other or equal.
     */
    inline bool operator>=(const Path& other) const
    {
        return other < *this || other == *this;
    }

    /**
     * Check whether this Path is valid.
     *
     * @return true if the Path is valid, i.e. contains data, false otherwise.
     */
    inline bool isValid() const
    {
        return !m_data.isEmpty();
    }

    /**
     * Convert the Path to a string, yielding either the plain path for local
     * paths or the stringified URL for remote Paths.
     *
     * @return a string representation of this Path.
     * @sa path()
     */
    QString pathOrUrl() const;

    /**
     * Return the path segment of this Path. This is the same for local paths
     * as calling pathOrUrl(). The difference is only for remote paths which
     * would return the protocol, host etc. data in pathOrUrl but not here.
     *
     * @return the path segment of this Path.
     *
     * @sa pathOrUrl()
     */
    QString path() const;

    /**
     * Return the path for local path and an empty string for remote paths.
     */
    QString toLocalFile() const;

    /**
     * @return the relative path from this path to @p path.
     *
     * Examples:
     * @code
     * Path p1("/foo/bar");
     * Path p2("/foo/bar/asdf/test.txt");
     * p1.relativePath(p2); // returns: asdf/test.txt
     * Path p3("/foo/asdf/lala");
     * p3.relativePath(p1); // returns: ../../bar
     * @endcode
     *
     * @sa KUrl::relativePath
     */
    QString relativePath(const Path& path) const;

    /**
     * @return True if this path is the parent of @p path.
     *
     * For instance, ftp://host/dir/ is a parent of ftp://host/dir/subdir/blub,
     * or /foo is a parent of /foo/bar.
     *
     * NOTE: Contrary to KUrl::isParentOf this returns false if the path equals this one.
     */
    bool isParentOf(const Path& path) const;

    /**
     * @return True if this path is the direct parent of @p path.
     *
     * For instance, ftp://host/dir/ is the direct parent of ftp://host/dir/subdir,
     * but ftp.//host/ is a parent but not the direct parent.
     *
     * This is more efficient than @code path.parent() == *this @endcode since
     * it does not require any temporary allocations as for the parent() call.
     */
    bool isDirectParentOf(const Path& path) const;

    /**
     * @return the prefix of a remote URL containing protocol, host, port etc. pp.
     *         If this path is not remote, this returns an empty string.
     */
    QString remotePrefix() const;

    /**
     * @return an implicitly shared copy of the internal data.
     */
    inline QVector<QString> segments() const
    {
        return m_data;
    }

    /**
     * @return the Path converted to a KUrl.
     */
    KUrl toUrl() const;

    /**
     * @return true when this Path points to a local file, false otherwise.
     */
    inline bool isLocalFile() const
    {
        // if the first data element contains a '/' it is a Path prefix
        return !m_data.isEmpty() && !m_data.first().contains('/');
    }

    /**
     * @return true when this Path points to a remote file, false otherwise.
     */
    inline bool isRemote() const
    {
        return !m_data.isEmpty() && m_data.first().contains('/');
    }

    /**
     * @return the last element of the path.
     *
     * This will never return the remote URL prefix.
     */
    QString lastPathSegment() const;

    /**
     * Set the file name of this Path, i.e. the last element of the path.
     *
     * This will never overwrite the remote URL prefix.
     */
    void setLastPathSegment(const QString& name);

    /**
     * Append @p path to this Path.
     *
     * NOTE: If @p path starts with a slash, this function ignores it.
     *       I.e. you cannot set the path this way. @sa KUrl::addPath
     */
    void addPath(const QString& path);

    /**
     * @return the path pointing to the parent folder of this path.
     *
     * @sa KUrl::upUrl()
     */
    Path parent() const;

    /**
     * Clear the path, i.e. make it invalid and empty.
     */
    void clear();

    /**
     * Change directory by relative path @p dir.
     *
     * NOTE: This is expensive.
     *
     * @sa KUrl::cd
     */
    Path cd(const QString& dir) const;

private:
    /**
     * Initialize this Path from the data of @p url.
     */
    void init(KUrl url);

    // for remote urls the first element contains the a Path prefix
    // containing the protocol, user, port etc. pp.
    QVector<QString> m_data;
};

KDEVPLATFORMUTIL_EXPORT uint qHash(const Path& path);

/**
 * Convert the @p list of KUrls to a list of Paths.
 */
KDEVPLATFORMUTIL_EXPORT Path::List toPathList(const KUrl::List& list);
}

/**
 * qDebug() stream operator.  Writes the string to the debug output.
 */
KDEVPLATFORMUTIL_EXPORT QDebug operator<<(QDebug s, const KDevelop::Path& string);

namespace QTest {

template<typename T> char* toString(const T&);
/**
 * QTestLib integration to have nice output in e.g. QCOMPARE failures.
 */
template<>
KDEVPLATFORMUTIL_EXPORT char* toString(const KDevelop::Path& path);
}

Q_DECLARE_TYPEINFO(KDevelop::Path, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(KDevelop::Path)
Q_DECLARE_TYPEINFO(KDevelop::Path::List, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(KDevelop::Path::List)

#endif // KDEVELOP_PATH_H
