#include "config.h"

// Implementation
#include "File.hpp"

// STDC++
#include <cassert>
#include <fstream>
#include <cstring>

// Local
#include "Text.hpp"

namespace UI {
namespace Util {

std::string getenv(std::string const & name)
{
	char const * const env(Sys::getenv(name.c_str()));
	return env ? env : EmptyString_;
}

std::string guessConfFile(std::string const & id, std::string const & suffix)
{
#ifdef WIN32
	return "./" + id + suffix;
#else
	return (Sys::geteuid() == 0 ? "/etc/" : getenv("HOME") + "/.") + id + suffix;
#endif
}

std::string PIDFile::guess(std::string const & id)
{
	return (Sys::geteuid() == 0 ? "/var/run/" : getenv("HOME") + "/.") + id + ".pid";
}

PIDFile::PIDFile(std::string const & path, pid_t const & pid, bool const & keepExisting, mode_t const & perms)
	:path_(path)
{
#ifndef WIN32
	if (keepExisting && fileExists(path))
	{
		UI_THROW("Won't write to existing PID file: " + path);
	}

	std::ofstream f(path_.c_str(), std::ios::out | std::ios::trunc);
	if (!f.is_open()) { UI_THROW_ERRNO("Error opening pid file: " + path_ + ": "); }

	// Set hardcoded unix permissions (stdc++ can't do it)
	Sys::chmod(path_.c_str(), perms);

	f << pid << std::endl;
	if (f.fail())     { UI_THROW_ERRNO("Error writing to pid file: " + path_ + ": "); }
#endif
}

PIDFile::~PIDFile()
{
#ifndef WIN32
	try { Sys::unlink(path_.c_str()); }
	catch(...) {}
#endif
}

AutoRemoveFile::AutoRemoveFile(std::string const & path)
	:path_(path)
{}

AutoRemoveFile::~AutoRemoveFile()
{
	try { Sys::remove(path_.c_str()); }
	catch(...) {}
}

std::string const & AutoRemoveFile::operator()() const { return path_; }


CFileStream::CFileStream(std::string const & file, std::string const & mode)
	:file_(Sys::fopen(file.c_str(), mode.c_str()))
{}

CFileStream::~CFileStream()
{
	try { Sys::fclose(file_); }
	catch(...) {}
}

FILE * CFileStream::get() const
{
	return file_;
}

void fileCopy(std::string const & src, std::string const & dest)
{
	CFileStream srcFile(src, "rb");
	CFileStream destFile(dest, "wb");

	char buffer[4096];
	int bytesRead(0);
	while ((bytesRead = fread(buffer, sizeof(char), sizeof(buffer), srcFile.get())) > 0)
	{
		fwrite(buffer, sizeof(char), bytesRead, destFile.get());
	}
}

bool fileExists(std::string const & fName)
{
#ifdef WIN32
	return FALSE == PathFileExists(fName.c_str()) ? false : true;
#else
	try
	{
		struct stat fStat;
		Sys::stat(fName.c_str(), &fStat);
		return true;
	}
	catch (Sys::Exception const &) { return false; }
#endif
}

time_t fileModificationTime(std::string const & path)
{
	try
	{
		struct stat st;
		Sys::stat(path.c_str(), &st);
		return st.st_mtime;
	}
	catch (Sys::Exception const &) { return -1; }
}


void FileDescriptor::fdClose(int const & fd, std::string const &, bool const & doClose)
{
	if (doClose)
	{
		try { Sys::close(fd); }
		catch(...) {};
	}
}

FileDescriptor::FileDescriptor(int fd, bool closeFd)
{
	init(fd, closeFd);
}

FileDescriptor::~FileDescriptor()
{
	fdClose(fd_, "FileDescriptor", closeFd_);
}

std::streamsize FileDescriptor::read(void * const buf, std::streamsize count)
{
	try { return static_cast<std::streamsize>(Sys::read(fd_, buf, count)); }
	catch(std::exception const & e) { UI_THROW_CODE(ReadErr_, "Error reading from fd " + tos(getFd()) + ": " + e.what()); }
}

std::streamsize FileDescriptor::write(void const * const buf, std::streamsize count)
{
	try { return static_cast<std::streamsize>(Sys::write(fd_, buf, count)); }
	catch(std::exception const & e) { UI_THROW_CODE(WriteErr_, "Error writing to fd " + tos(getFd()) + ": " + e.what()); }
}

int FileDescriptor::getFd() const
{
	return fd_;
}

void FileDescriptor::init(int fd, bool closeFd)
{
	fd_ = fd;
	closeFd_ = closeFd;
}


File::File(std::string const & name, int flags, mode_t mode, bool closeFd)
	:name_(name)
{
	int fd(Sys::open(name_.c_str(), flags, mode));
	init(fd, closeFd);
}

File::File(int fd, bool closeFd)
	:FileDescriptor(fd, closeFd)
{}

std::string const & File::getName() const
{
	return name_;
}

#ifndef WIN32
PosixFileMutex::PosixFileMutex(std::string const & lockFile)
	:File(lockFile)
{}

PosixFileMutex::PosixFileMutex(int fd)
	:File(fd)
{}

bool PosixFileMutex::setPosixLock(int type, bool wait)
{
	struct flock lock;
	std::memset(&lock, 0, sizeof(lock));
	lock.l_type = type;
	lock.l_start = 0;
	lock.l_whence = SEEK_SET;
	lock.l_len = 0;
	lock.l_pid = Sys::getpid();
	int cmd(wait ? F_SETLKW : F_SETLK);
	try { return Sys::fcntl(fd_, cmd, &lock) == 0; }
	catch (...) { return false; }
}

bool PosixFileMutex::tryEnterMutex() throw()
{
	return setPosixLock(F_WRLCK, false);
}

void PosixFileMutex::enterMutex()
{
	if (!setPosixLock(F_WRLCK, true))
	{
		UI_THROW_CODE_ERRNO(LockErr_, "Error locking posix file mutex on " + getName() + ": ");
	}
}

void PosixFileMutex::leaveMutex()
{
	if (!setPosixLock(F_UNLCK, false))
	{
		UI_THROW_CODE_ERRNO(UnlockErr_, "Error unlocking posix file mutex on \"" + getName() + "\": ");
	}
}


BSDFileMutex::BSDFileMutex(std::string const & lockFile)
	:File(lockFile)
{}

BSDFileMutex::BSDFileMutex(int fd)
	:File(fd)
{}

bool BSDFileMutex::tryEnterMutex() throw()
{
	try { return Sys::flock(fd_, LOCK_EX | LOCK_NB) == 0; }
	catch (...) { return false; }
}

void BSDFileMutex::enterMutex()
{
	Sys::flock(fd_, LOCK_EX);
}

void BSDFileMutex::leaveMutex()
{
	Sys::flock(fd_, LOCK_UN);
}
#endif


uint64_t FsInfo::calc(long double const & value) const
{
	return uint64_t(value * bSize_ / 1024);
}

FsInfo::FsInfo(std::string const & dev, std::string const & file)
{
#ifdef WIN32
	__int64 i64FreeBytesToCaller = 0;
	__int64 i64TotalBytes = 0;
	__int64 i64FreeBytes = 0;
	if (GetDiskFreeSpaceEx(dev.c_str(), (PULARGE_INTEGER)&i64FreeBytesToCaller,
		(PULARGE_INTEGER)&i64TotalBytes,(PULARGE_INTEGER)&i64FreeBytes))
	{
		bSize_ = 1;
		bTotal_ = static_cast<long double>(i64TotalBytes);
		bAvail_ = static_cast<long double>(i64FreeBytesToCaller);
		bFree_ = static_cast<long double>(i64FreeBytes);
	}
	else
	{
		UI_THROW("Cannot get disk usage for device/file on device=" + dev + "/" + file);
	}
#else
	// dev is not used for unix; this line avoids a compiler warning only.
	if (dev.empty()) {};

	struct statvfs stat;
	Sys::statvfs(file.c_str(), &stat);
	bSize_=stat.f_bsize;
	bTotal_=stat.f_blocks;
	bAvail_=stat.f_bavail;
	bFree_=stat.f_bfree;
#endif
}

uint64_t FsInfo::getTotal() const { return calc(bTotal_); }
uint64_t FsInfo::getAvail() const { return calc(bAvail_); }
uint64_t FsInfo::getFree()  const { return calc(bFree_); }
uint64_t FsInfo::getUsed()  const { return calc(bTotal_-bFree_); }

}}
