#ifndef WIBBLE_SYS_DIRECTORY_H
#define WIBBLE_SYS_DIRECTORY_H

#include <string>
#include <iosfwd>
#include <memory>       // auto_ptr
#include <sys/types.h>  // mode_t
#include <sys/stat.h>   // struct stat
#include <unistd.h>     // access

struct dirent;

namespace wibble {
namespace sys {
namespace fs {

/**
 * stat() the given file and return the struct stat with the results.
 * If the file does not exist, return NULL.
 * Raises exceptions in case of errors.
 */
std::auto_ptr<struct stat> stat(const std::string& pathname);

/**
 * stat() the given file filling in the given structure.
 * Raises exceptions in case of errors, including if the file does not exist.
 */
void stat(const std::string& pathname, struct stat& st);

/// access() a filename
bool access(const std::string& s, int m);

/// Same as access(s, F_OK);
bool exists(const std::string& s);

/**
 * Get the absolute path of a file
 */
std::string abspath(const std::string& pathname);

// Create a temporary directory based on a template.
std::string mkdtemp( std::string templ );

/// Create the given directory, if it does not already exists.
/// It will complain if the given pathname already exists but is not a
/// directory.
void mkdirIfMissing(const std::string& dir, mode_t mode = 0777);

/// Create all the component of the given directory, including the directory
/// itself.
void mkpath(const std::string& dir);

/// Ensure that the path to the given file exists, creating it if it does not.
/// The file itself will not get created.
void mkFilePath(const std::string& file);

/// Read whole file into memory. Throws exceptions on failure.
std::string readFile(const std::string &file);

/**
 * Read the entire contents of a file into a string
 *
 * @param filename
 *   name or description of the stream we are reading. Used only for error
 *   messages.
 */
std::string readFile(std::istream& file, const std::string& filename);

/// Write \a data to \a file, replacing existing contents if it already exists
void writeFile(const std::string &file, const std::string &data);

/**
 * Write \a data to \a file, replacing existing contents if it already exists
 *
 * Data is written to a temporary file, then moved to its final destination, to
 * ensure an atomic operation.
 */
void writeFileAtomically(const std::string &file, const std::string &data);

/**
 * Compute the absolute path of an executable.
 *
 * If \a name is specified as a partial path, it ensures it is made absolute.
 * If \a name is not specified as a path, it looks for the executable in $PATH
 * and return its absolute pathname.
 */
std::string findExecutable(const std::string& name);

/**
 * Delete a file if it exists. If it does not exist, do nothing.
 *
 * @return true if the file was deleted, false if it did not exist
 */
bool deleteIfExists(const std::string& file);

/// Move \a src to \a dst, without raising exception if \a src does not exist
void renameIfExists(const std::string& src, const std::string& dst);

/// Delete the file
void unlink(const std::string& fname);

/// Remove the directory using rmdir(2)
void rmdir(const std::string& dirname);

/// Delete the directory \a dir and all its content
void rmtree(const std::string& dir);

/**
 * Returns true if the given pathname is a directory, else false.
 *
 * It also returns false if the pathname does not exist.
 */
bool isdir(const std::string& pathname);

/// Same as isdir but checks for block devices
bool isblk(const std::string& pathname);

/// Same as isdir but checks for character devices
bool ischr(const std::string& pathname);

/// Same as isdir but checks for FIFOs
bool isfifo(const std::string& pathname);

/// Same as isdir but checks for symbolic links
bool islnk(const std::string& pathname);

/// Same as isdir but checks for regular files
bool isreg(const std::string& pathname);

/// Same as isdir but checks for sockets
bool issock(const std::string& pathname);

/// File mtime
time_t timestamp(const std::string& file);

/// File mtime (or def if the file does not exist)
time_t timestamp(const std::string& file, time_t def);

/// File size
size_t size(const std::string& file);

/// File size (or def if the file does not exist)
size_t size(const std::string& file, size_t def);

/// File inode number
ino_t inode(const std::string& file);

/// File inode number (or 0 if the file does not exist)
ino_t inode(const std::string& file, ino_t def);


/// Nicely wrap access to directories
class Directory
{
protected:
    /// Directory pathname
    std::string m_path;

public:
    class const_iterator
    {
        /// Directory we are iterating
        const Directory* dir;
        /// DIR* pointer
        void* dirp;
        /// dirent structure used for iterating entries
        struct dirent* direntbuf;

    public:
        // Create an end iterator
        const_iterator();
        // Create a begin iterator
        const_iterator(const Directory& dir);
        // Cleanup properly
        ~const_iterator();

        /// auto_ptr style copy semantics
        const_iterator(const const_iterator& i);
        const_iterator& operator=(const const_iterator& i);

        /// Move to the next directory entry
        const_iterator& operator++();

        /// @return the current file name
        std::string operator*() const;

        bool operator==(const const_iterator& iter) const;
        bool operator!=(const const_iterator& iter) const;

        /// @return true if we refer to a directory, else false
        bool isdir() const;

        /// @return true if we refer to a block device, else false
        bool isblk() const;

        /// @return true if we refer to a character device, else false
        bool ischr() const;

        /// @return true if we refer to a named pipe (FIFO).
        bool isfifo() const;

        /// @return true if we refer to a symbolic link.
        bool islnk() const;

        /// @return true if we refer to a regular file.
        bool isreg() const;

        /// @return true if we refer to a Unix domain socket.
        bool issock() const;
    };

    Directory(const std::string& path);
    ~Directory();

	/// Pathname of the directory
	const std::string& path() const { return m_path; }

    /// Check if the directory exists
    bool exists() const;

    /// Begin iterator
    const_iterator begin() const;

	/// End iterator
	const_iterator end() const;
};

}
}
}

// vim:set ts=4 sw=4:
#endif
