/**
 * @file
 * @brief File, descriptors and sockets.
 */
#ifndef UI_UTIL_FILE_HPP
#define UI_UTIL_FILE_HPP

#ifdef WIN32
#include <fcntl.h>
#endif

// STDC++
#include <string>
#include <iostream>
#include <vector>
#include <ctime>

// C++ libraries
#include <ui-utilcpp/Exception.hpp>
#include <ui-utilcpp/Sys.hpp>

namespace UI {
namespace Util {

/** @brief Like getenv(3), but returns empty string when env is missing. */
std::string getenv(std::string const & name);

/** @brief Guess a config file (/etc/id.conf or ~/.id.conf). */
std::string guessConfFile(std::string const & id, std::string const & suffix=".conf");

/** @brief Write and keep pid file during lifetime. */
class PIDFile
{
public:
	/** @brief Guess a pid file (/var/run/id.pid or ~/.id.pid). */
	static std::string guess(std::string const & id);

	PIDFile(std::string const & path,
#ifdef WIN32
	        pid_t const & pid=Sys::getpid(),
#else
	        pid_t const & pid=Sys::getpgid(0),
#endif
	        bool const & keepExisting=true,
	        mode_t const & perms=S_IRUSR | S_IWUSR
#ifndef WIN32
					| S_IRGRP | S_IROTH
#endif
					);
	~PIDFile();
private:
	std::string const path_;
};

/** @brief Guarantuee to run "remove(2)" on path in destructor. */
class AutoRemoveFile
{
public:
	AutoRemoveFile(std::string const & path);
	~AutoRemoveFile();
	std::string const & operator()() const;
private:
	std::string const path_;
};

/** @brief Simple exception-save FILE abstraction. */
class CFileStream
{
public:
	/** @brief Constructor. */
	CFileStream(std::string const & fileName, std::string const & mode);
	~CFileStream();
	/** @brief Get C file stream. */
	FILE * get() const;
private:
	FILE * const file_;
};

/** @brief File copy from file names. */
void fileCopy(std::string const & src, std::string const & dest);

/** @brief Check if a file exists using stat(2).
 *
 * @param fName File name to check.
 * @return true, if file exists, else false.
 */
bool fileExists(std::string const & fName);

/** @brief Get the modification time for a file
 *
 * @param path File to check.
 * @return time of last modification.
 */
time_t fileModificationTime(std::string const & path);

/** @brief File Descriptor Holder Class.
 *
 * @see read(2), write(2)
 *
 * This should encapsulate system calls on file descriptors, and
 * automate descriptor closing.
 */
class FileDescriptor
{
public:
	/** @brief Helper to close file descriptors from destructors. */
	static void fdClose(int const & fd, std::string const & id, bool const & doClose=true);

	/** @brief Error codes for exceptions. */
	enum ErrorCode
	{
		OpenErr_ = 1,
		ReadErr_,
		WriteErr_,
		LockErr_,
		UnlockErr_,
		BindErr_,
		ConnectErr_,
		UnblockErr_,
		ListenErr_,
		ShutdownErr_
	};
	/** @brief Exceptions for this class. */
	typedef CodeException<ErrorCode> Exception;

	/** @brief Constructor from file descriptor
	 *
	 * @param fd Already opened file descriptor
	 * @param closeFd Whether to call close(2) in destructor
	 *
	 * This constructor takes an already opened file descriptor.  The
	 * destructor will not call close on this descriptor.
	 *
	 */
	FileDescriptor(int fd=-1, bool closeFd=false);

	/** @brief Destructor.
	 *
	 * This will close the file descriptor via close(2), unless it was
	 * constructed by an already opened descriptor.
	 *
	 * It will never delete the file, even if it got created by the
	 * constructor.
	 *
	 */
	virtual ~FileDescriptor();


	/** @brief C++ like virtual read method.
	 *
	 * This default implementation uses read(2).
	 */
	virtual std::streamsize read(void * const buf, std::streamsize count);

	/** @brief C++ like virtual erite method.
	 *
	 * This default implementation uses write(2).
	 */
	virtual std::streamsize write(void const * const buf, std::streamsize count);

	/** @brief Get file descriptor. */
	int getFd() const;

protected:
	/** @brief The file descriptor that is managed. */
	int fd_;

	/** @brief To be called in a constructor. */
	void init(int fd, bool closeFd=false);

private:
	bool closeFd_;
};

/** @brief File representation.
 *
 * @see FileDescriptor, fcntl(2), open(2), close(2)
 */
class File: public FileDescriptor
{
public:
	/** @brief Constructor.
	 *
	 * @param name Path to the file to open
	 * @param flags As in open(2)
	 * @param mode As in open(2)
	 * @param closeFd Whether to call close(2) in destructor
	 * @see open(2)
	 *
	 * This constructor takes a file name, will try to open the file
	 * using open(2). Arguments to open(2) can be given optionally,
	 * and have reasonable defaults.
	 *
	 * OPEN_ERR will be thrown if the file could not be created via
	 * open(2).
	 */
	File(std::string const & name,
	     int flags=O_CREAT | O_WRONLY,
	     mode_t mode=S_IRUSR | S_IWUSR,
	     bool closeFd=true);

	/** @brief Constructor from file descriptor.
	 *
	 * @param fd Already opened file descriptor
	 * @param closeFd Whether to call close(2) in destructor
	 *
	 * This constructor takes an already opened file descriptor.
	 */
	File(int fd, bool closeFd=false);

	/** @brief Get file name. This will always deliver en empty string if constructed from fd. */
	std::string const & getName() const;

private:
	std::string const name_;
};

/**
 * @example FileLock.cpp
 * Example program for advisory POSIX and BSD file locking.
 * Should be installed as ui-utilcpp-filelock along with the library.
 */

/** @brief Mutex For Posix Advisory File Locking
 *
 * @see MutexLock
 * @see fcntl(2), open(2)
 * @attention This does not work from the same process id, as any close() to any open file
 *  descriptor of a process removes all POSIX locks from that (process/fd)-pair.
 */
#ifndef WIN32
class PosixFileMutex: public File
{
public:
	/** @brief Constructor from file name.
	 *
	 * @param lockFile Name of lock file
	 * @see FileDescriptor
	 */
	PosixFileMutex(std::string const & lockFile);

	/** @brief Constructor from file descriptor.
	 *
	 * @param fd Opened file descriptor
	 * @see FileDescriptor
	 */
	PosixFileMutex(int fd);

	/** @name Standard mutex methods.
	 * @{ */
	bool tryEnterMutex() throw();
	void enterMutex();
	void leaveMutex();
	/** @} */

private:
	bool setPosixLock(int type, bool wait);
};

/** @brief Mutex For BSD Advisory File Locking.
 *
 * @see MutexLock
 * @see flock(2), open(2)
 * @attention BSD locking does not work over NFS.
 *
 */
class BSDFileMutex: public File
{
public:
	/** @brief Constructor from file name.
	 *
	 * @param lockFile Name of lock file
	 * @see FileDescriptor
	 */
	BSDFileMutex(std::string const & lockFile);

	/** @brief Constructor from file descriptor.
	 *
	 * @param fd Opened file descriptor
	 * @see FileDescriptor
	 */
	BSDFileMutex(int fd);

	/** @name Standard mutex methods.
	 * @{ */
	bool tryEnterMutex() throw();
	void enterMutex();
	void leaveMutex();
	/** @} */
};
#endif


/** @brief File system information abstraction.
 *
 * @note All methods always return (amounts of) 1024 byte
 * blocks. Calculation is done via float for simplicity -- minor
 * rounding errors may occur.
 */
class FsInfo
{
private:
	uint64_t calc(long double const & value) const;

public:
	FsInfo(std::string const & dev, std::string const & file);

	uint64_t getTotal() const;
	uint64_t getAvail() const;
	uint64_t getFree()  const;
	uint64_t getUsed()  const;

private:
	long double bSize_;
	long double bTotal_;
	long double bAvail_;
	long double bFree_;
};

}}
#endif
