#ifndef WIBBLE_SYS_DIRECTORY_H
#define WIBBLE_SYS_DIRECTORY_H

#include <string>
#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 stat64> stat( std::string pathname );

/// 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 whole file into memory. Throws exceptions on failure.
std::string readFile(std::ifstream &file);

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

/**
 * 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);


/// Nicely wrap access to directories
class Directory
{
protected:
    std::string m_path;
    // DIR* pointer
    void* dir;

public:
    class const_iterator
    {
        Directory* dir;
        struct dirent* d;

    public:
        // Create an end iterator
        const_iterator() : dir(0), d(0) {}
        // Create a begin iterator
        const_iterator(Directory& dir) : dir(&dir), d(0) { ++(*this); }
        // Cleanup properly
        ~const_iterator();

		// auto_ptr style copy semantics
		const_iterator(const const_iterator& i)
		{
			dir = i.dir;
			d = i.d;
			const_iterator* wi = const_cast<const_iterator*>(&i);
			wi->dir = 0;
			wi->d = 0;
		}
        const_iterator& operator=(const const_iterator& i);

        const_iterator& operator++();

        std::string operator*() const;

		bool operator==(const const_iterator& iter) const
		{
			return dir == iter.dir && d == iter.d;
		}
		bool operator!=(const const_iterator& iter) const
		{
			return dir != iter.dir || d != iter.d;
		}

        /// @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; }

	/// Begin iterator
	const_iterator begin();

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

}
}
}

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