/*
    libmaus2
    Copyright (C) 2019 German Tischler

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <libmaus2/posix/PosixFunctions.hpp>
#include <stdexcept>
#include <limits>
#include <climits>
#include <cstring>

#if defined(LIBMAUS2_HAVE_SYS_TYPES_H)
#include <sys/types.h>
#endif

#if defined(LIBMAUS2_HAVE_SYS_STAT_H)
#include <sys/stat.h>
#endif

#if defined(LIBMAUS2_HAVE_FCNTL_H)
#include <fcntl.h>
#endif

#if defined(LIBMAUS2_HAVE_UNISTD_H)
#include <unistd.h>
#endif

#if defined(LIBMAUS2_HAVE_SYS_WAIT_H)
#include <sys/wait.h>
#endif

#if defined(LIBMAUS2_HAVE_SYS_VFS_H)
#include <sys/vfs.h>
#endif

#if defined(LIBMAUS2_HAVE_LIBGEN_H)
#include <libgen.h>
#endif

#if defined(LIBMAUS2_HAVE_OPEN_2)
int libmaus2::posix::PosixFunctions::open(const char *pathname, int flags)
{
	return ::open(pathname,flags);
}
#else
int libmaus2::posix::PosixFunctions::open(const char *pathname, int flags)
{
	errno = ENOTSUP;
	return -1;
}
#endif

#if defined(LIBMAUS2_HAVE_OPEN_3)
int libmaus2::posix::PosixFunctions::open(const char *pathname, int flags, unsigned char const * inmode)
{
	mode_t const mode = decode<mode_t>(inmode,getOpenModeSize());
	return ::open(pathname,flags,mode);
}
unsigned int libmaus2::posix::PosixFunctions::getOpenModeSize()
{
	return sizeof(mode_t);
}
#else
int libmaus2::posix::PosixFunctions::open(const char *, int, unsigned char const * /* inmode */)
{
	errno = ENOTSUP;
	return -1;
}
unsigned int libmaus2::posix::PosixFunctions::getOpenModeSize()
{
	return 0;
}
#endif

#if defined(LIBMAUS2_HAVE_CLOSE)
int libmaus2::posix::PosixFunctions::close(int fd)
{
	return ::close(fd);
}
#else
int libmaus2::posix::PosixFunctions::close(int fd)
{
	errno = ENOTSUP;
	return -1;
}
#endif

#if defined(LIBMAUS2_HAVE_PIPE)
int libmaus2::posix::PosixFunctions::pipe(int pipefd[2])
{
	return ::pipe(pipefd);
}
#else
int libmaus2::posix::PosixFunctions::pipe(int[2])
{
	errno = ENOTSUP;
	return -1;
}
#endif

#if defined(LIBMAUS2_HAVE_PID_T)
unsigned int libmaus2::posix::PosixFunctions::getPidTSize()
{
	return sizeof(pid_t);
}
bool libmaus2::posix::PosixFunctions::pidIsMinusOne(unsigned char const * upid)
{
	pid_t const pid = decodeSigned<pid_t>(upid,getPidTSize());
	return pid == static_cast<pid_t>(-1);
}
bool libmaus2::posix::PosixFunctions::pidIsZero(unsigned char const * upid)
{
	pid_t const pid = decodeSigned<pid_t>(upid,getPidTSize());
	return pid == static_cast<pid_t>(0);
}
#else
unsigned int libmaus2::posix::PosixFunctions::getPidTSize()
{
	return 0;
}
bool libmaus2::posix::PosixFunctions::pidIsMinusOne(unsigned char const * pid)
{
	throw std::runtime_error("PosixFunctions::pidIsMinusOne: pid_t type not supported");
}
bool libmaus2::posix::PosixFunctions::pidIsZero(unsigned char const * pid)
{
	throw std::runtime_error("PosixFunctions::pidIsZero: pid_t type not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_GETPID) && defined(LIBMAUS2_HAVE_PID_T)
void libmaus2::posix::PosixFunctions::getpid(unsigned char * upid)
{
	pid_t const pid = ::getpid();
	encodeSigned<pid_t>(pid,upid,getPidTSize());
}
std::string libmaus2::posix::PosixFunctions::getPidAsString()
{
	std::ostringstream ostr;
	ostr << ::getpid();
	return ostr.str();
}
#else
void libmaus2::posix::PosixFunctions::getpid(unsigned char * upid)
{
	throw std::runtime_error("PosixFunctions::getpid: function is not supported");
}
std::string libmaus2::posix::PosixFunctions::getPidAsString()
{
	throw std::runtime_error("PosixFunctions::getPidAsString: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_PID_T)
std::string libmaus2::posix::PosixFunctions::pidToString(unsigned char const * upid)
{
	pid_t const pid = decodeSigned<pid_t>(upid,getPidTSize());
	std::ostringstream ostr;
	ostr << pid;
	return ostr.str();
}
#else
std::string libmaus2::posix::PosixFunctions::pidToString(unsigned char const *)
{
	throw std::runtime_error("PosixFunctions::pidToString: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_FORK)
void libmaus2::posix::PosixFunctions::fork(unsigned char * upid)
{
	pid_t const pid = ::fork();
	encodeSigned<pid_t>(pid,upid,getPidTSize());
}
#else
void libmaus2::posix::PosixFunctions::fork(unsigned char *)
{
	throw std::runtime_error("PosixFunctions::fork: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_WAITPID)
void libmaus2::posix::PosixFunctions::waitpid(unsigned char * opid, unsigned char const * upid, int * wstatus, int options)
{
	pid_t const pid = decodeSigned<pid_t>(upid,getPidTSize());
	pid_t const outpid = ::waitpid(pid,wstatus,options);
	encodeSigned<pid_t>(outpid,opid,getPidTSize());
}
#else
void libmaus2::posix::PosixFunctions::waitpid(unsigned char *, unsigned char const *, int *, int)
{
	throw std::runtime_error("PosixFunctions::waitpid: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_O_RDONLY)
int libmaus2::posix::PosixFunctions::get_O_RDONLY()
{
	return O_RDONLY;
}
#else
int libmaus2::posix::PosixFunctions::get_O_RDONLY()
{
	throw std::runtime_error("PosixFunctions::get_O_RDONLY: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_O_WRONLY)
int libmaus2::posix::PosixFunctions::get_O_WRONLY()
{
	return O_WRONLY;
}
#else
int libmaus2::posix::PosixFunctions::get_O_WRONLY()
{
	throw std::runtime_error("PosixFunctions::get_O_WRONLY: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_O_RDWR)
int libmaus2::posix::PosixFunctions::get_O_RDWR()
{
	return O_RDWR;
}
#else
int libmaus2::posix::PosixFunctions::get_O_RDWR()
{
	throw std::runtime_error("PosixFunctions::get_O_RDWR: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_O_TRUNC)
int libmaus2::posix::PosixFunctions::get_O_TRUNC()
{
	return O_TRUNC;
}
#else
int libmaus2::posix::PosixFunctions::get_O_TRUNC()
{
	throw std::runtime_error("PosixFunctions::get_O_TRUNC: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_O_CREAT)
int libmaus2::posix::PosixFunctions::get_O_CREAT()
{
	return O_CREAT;
}
#else
int libmaus2::posix::PosixFunctions::get_O_CREAT()
{
	throw std::runtime_error("PosixFunctions::get_O_CREAT: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_O_LARGEFILE)
int libmaus2::posix::PosixFunctions::get_O_LARGEFILE()
{
	return O_LARGEFILE;
}
#else
int libmaus2::posix::PosixFunctions::get_O_LARGEFILE()
{
	throw std::runtime_error("PosixFunctions::get_O_LARGEFILE: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_O_BINARY)
int libmaus2::posix::PosixFunctions::get_O_BINARY()
{
	return O_BINARY;
}
#else
int libmaus2::posix::PosixFunctions::get_O_BINARY()
{
	throw std::runtime_error("PosixFunctions::get_O_BINARY: function is not supported");
}
#endif


#if defined(LIBMAUS2_HAVE_OFF_T)
unsigned int libmaus2::posix::PosixFunctions::getOffTSize()
{
	return sizeof(off_t);
}
bool libmaus2::posix::PosixFunctions::offTIsMinusOne(unsigned char const * offt)
{
	off_t const inoff = decodeSigned<off_t>(offt,getOffTSize());
	return inoff == static_cast<off_t>(-1);
}
#else
unsigned int libmaus2::posix::PosixFunctions::getOffTSize()
{
	throw std::runtime_error("PosixFunctions::getOffTSize: function is not supported");
}
bool libmaus2::posix::PosixFunctions::offTIsMinusOne(unsigned char const * offt)
{
	throw std::runtime_error("PosixFunctions::offTIsMinusOne: function is not supported");
}
#endif

static unsigned char getHighBit()
{
	return static_cast<unsigned char>(1ul << (CHAR_BIT - 1));
}

static bool getBit(unsigned char const * p, unsigned int const i)
{
	unsigned char const b = p[i / CHAR_BIT];
	unsigned int const shift = (CHAR_BIT-1) - (i%CHAR_BIT);
	return ((b >> shift) & 1) != 0;
}

static void setBit(unsigned char * p, unsigned int const i, unsigned char const v)
{
	unsigned char const o = i / CHAR_BIT;
	unsigned int const shift = (CHAR_BIT-1) - (i%CHAR_BIT);
	p[o] |= (v << shift);
}

void libmaus2::posix::PosixFunctions::translate(unsigned char * out, unsigned int outsize, unsigned char const * in, unsigned int const insize)
{
	std::fill(out,out+outsize,0);

	if ( insize )
	{
		unsigned int magnbitsused = 0;
		for ( unsigned int i = 0; i < (CHAR_BIT * insize); ++i )
		{
			unsigned int const bitid = CHAR_BIT*insize-i-1;
			if ( getBit(in,bitid) )
				magnbitsused = i+1;
		}

		unsigned int const outbitsreq = magnbitsused;
		unsigned int const outbytesreq = (outbitsreq+CHAR_BIT-1)/CHAR_BIT;

		if ( outbytesreq > outsize )
			throw std::runtime_error("PosixFunctions::translateSigned: output type is too small for input number");

		for ( unsigned int i = 0; i < magnbitsused; ++i )
		{
			unsigned int const inbitid  = CHAR_BIT*insize-i-1;
			unsigned int const outbitid = CHAR_BIT*outsize-i-1;
			unsigned char const bit = getBit(in,inbitid) ? 1 : 0;
			setBit(out,outbitid,bit);
		}
	}
}


void libmaus2::posix::PosixFunctions::translateSigned(unsigned char * out, unsigned int outsize, unsigned char const * in, unsigned int const insize)
{
	std::fill(out,out+outsize,0);

	if ( insize )
	{
		unsigned int magnbitsused = 0;
		for ( unsigned int i = 0; i < (CHAR_BIT * insize)-1; ++i )
		{
			unsigned int const bitid = CHAR_BIT*insize-i-1;
			if ( getBit(in,bitid) )
				magnbitsused = i+1;
		}

		unsigned int const outbitsreq = magnbitsused+1;
		unsigned int const outbytesreq = (outbitsreq+CHAR_BIT-1)/CHAR_BIT;

		if ( outbytesreq > outsize )
			throw std::runtime_error("PosixFunctions::translateSigned: output type is too small for input number");


		for ( unsigned int i = 0; i < magnbitsused; ++i )
		{
			unsigned int const inbitid  = CHAR_BIT*insize-i-1;
			unsigned int const outbitid = CHAR_BIT*outsize-i-1;
			unsigned char const bit = getBit(in,inbitid) ? 1 : 0;
			setBit(out,outbitid,bit);
		}

		if ( (in[0] & getHighBit()) != 0 )
			out[0] |= getHighBit();
	}
}

#if defined(LIBMAUS2_HAVE_LSEEK)
void libmaus2::posix::PosixFunctions::posix_lseek(unsigned char * off_out, int const fd, unsigned char const * off_in, int whence)
{
	off_t const inoff = decodeSigned<off_t>(off_in,getOffTSize());
	off_t const outoff = ::lseek(fd,inoff,whence);
	encodeSigned<off_t>(outoff,off_out,getOffTSize());
}
int libmaus2::posix::PosixFunctions::get_SEEK_SET()
{
	return SEEK_SET;
}
int libmaus2::posix::PosixFunctions::get_SEEK_CUR()
{
	return SEEK_CUR;
}
int libmaus2::posix::PosixFunctions::get_SEEK_END()
{
	return SEEK_END;
}
#else
void libmaus2::posix::PosixFunctions::posix_lseek(unsigned char * off_out, int const fd, unsigned char const * off_in, int whence)
{
	throw std::runtime_error("PosixFunctions::lseek: function is not supported");
}
int libmaus2::posix::PosixFunctions::get_SEEK_SET()
{
	throw std::runtime_error("PosixFunctions::get_SEEK_SET: function is not supported");
}
int libmaus2::posix::PosixFunctions::get_SEEK_CUR()
{
	throw std::runtime_error("PosixFunctions::get_SEEK_CUR: function is not supported");
}
int libmaus2::posix::PosixFunctions::get_SEEK_END()
{
	throw std::runtime_error("PosixFunctions::get_SEEK_END: function is not supported");
}
#endif

#if 0
			libmaus2::autoarray::AutoArray<unsigned char> A(sizeof(int64_t));
			libmaus2::autoarray::AutoArray<unsigned char> B(sizeof(int32_t));
			for ( int64_t v = std::numeric_limits<int32_t>::min(); v <= std::numeric_limits<int32_t>::max(); ++v )
			{
				libmaus2::posix::PosixFunctions::encodeSigned<int64_t>(v,A.begin(),sizeof(int64_t));
				int64_t const o = libmaus2::posix::PosixFunctions::decodeSigned<int64_t>(A.begin(),sizeof(int64_t));
				assert ( o == v );

				libmaus2::posix::PosixFunctions::translateSigned(B.begin(),sizeof(int32_t),A.begin(),sizeof(int64_t));
				int32_t const o32 = libmaus2::posix::PosixFunctions::decodeSigned<int32_t>(B.begin(),sizeof(int32_t));

				assert ( o32 == v );

				if ( v % (128*1024*1024) == 0 )
					std::cerr << v << std::endl;
			}

			libmaus2::autoarray::AutoArray<unsigned char> A(sizeof(uint64_t));
			libmaus2::autoarray::AutoArray<unsigned char> B(sizeof(uint32_t));
			for ( uint64_t v = std::numeric_limits<uint32_t>::min(); v <= std::numeric_limits<uint32_t>::max(); ++v )
			{
				libmaus2::posix::PosixFunctions::encode<uint64_t>(v,A.begin(),sizeof(uint64_t));
				uint64_t const o = libmaus2::posix::PosixFunctions::decode<uint64_t>(A.begin(),sizeof(uint64_t));
				assert ( o == v );

				libmaus2::posix::PosixFunctions::translate(B.begin(),sizeof(uint32_t),A.begin(),sizeof(uint64_t));
				uint32_t const o32 = libmaus2::posix::PosixFunctions::decode<uint32_t>(B.begin(),sizeof(uint32_t));

				assert ( o32 == v );

				if ( v % (128*1024*1024) == 0 )
					std::cerr << v << std::endl;
			}

#endif

#if defined(LIBMAUS2_HAVE_READ)
::libmaus2::ssize_t libmaus2::posix::PosixFunctions::read(int fd, void * buf, ::std::size_t count)
{
	return ::read(fd,buf,count);
}
#else
::libmaus2::ssize_t libmaus2::posix::PosixFunctions::read(int fd, void * buf, ::std::size_t count)
{
	throw std::runtime_error("PosixFunctions::read: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_WRITE)
::libmaus2::ssize_t libmaus2::posix::PosixFunctions::write(int fd, void const * buf, ::std::size_t count)
{
	return ::write(fd,buf,count);
}
#else
::libmaus2::ssize_t libmaus2::posix::PosixFunctions::write(int fd, void const * buf, ::std::size_t count)
{
	throw std::runtime_error("PosixFunctions::write: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_FSTAT)
int64_t libmaus2::posix::PosixFunctions::getFileSizeViaFstat(int const fd)
{
	struct stat staterr;

	if ( fstat(fd,&staterr) < 0 )
		return -1;
	else
		return staterr.st_size;
}
#else
int64_t libmaus2::posix::PosixFunctions::getFileSizeViaFstat(int const fd)
{
	throw std::runtime_error("PosixFunctions::getFileSizeViaFstat: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_FSYNC)
int libmaus2::posix::PosixFunctions::fsync(int const fd)
{
	return ::fsync(fd);
}
#else
int libmaus2::posix::PosixFunctions::fsync(int const)
{
	throw std::runtime_error("PosixFunctions::fsync: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_FSTAT)
unsigned int libmaus2::posix::PosixFunctions::getFStatModeSize()
{
	return sizeof(::mode_t);
}
int libmaus2::posix::PosixFunctions::fstatSizeAndMode(int const fd, libmaus2::off_t & size, unsigned char * mode)
{
	struct stat statbuf;
	int const r = ::fstat(fd,&statbuf);

	if ( r < 0 )
		return r;

	size = statbuf.st_size;
	encodeSigned<mode_t>(
		statbuf.st_mode,
		mode,
		getFStatModeSize()
	);

	return r;
}
bool libmaus2::posix::PosixFunctions::modeIsReg(unsigned char const * mode)
{
	mode_t const imode = decodeSigned<mode_t>(mode,getFStatModeSize());
	return S_ISREG(imode);
}
#else
unsigned int libmaus2::posix::PosixFunctions::getFStatModeSize()
{
	throw std::runtime_error("PosixFunctions::getFStatModeSize: function is not supported");
}
int libmaus2::posix::PosixFunctions::fstatSizeAndMode(int const fd, libmaus2::off_t & size, unsigned char * mode)
{
	throw std::runtime_error("PosixFunctions::fstatSizeAndMode: function is not supported");
}
bool libmaus2::posix::PosixFunctions::modeIsReg(unsigned char const * mode)
{
	throw std::runtime_error("PosixFunctions::modeIsReg: function is not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_FSTATFS) && defined(LIBMAUS2_HAVE_STATFS_F_IOSIZE)
int64_t libmaus2::posix::PosixFunctions::getFIOsizeViaFStatFS(int const fd)
{
	struct statfs buf;
	int const r = fstatfs(fd,&buf);

	if ( r < 0 )
		return r;

	return buf.f_iosize;
}
#else
int64_t libmaus2::posix::PosixFunctions::getFIOsizeViaFStatFS(int const /* fd */)
{
	errno = ENOSYS;
	return -1;
}
#endif

#if defined(LIBMAUS2_HAVE_GETPAGESIZE)
int libmaus2::posix::PosixFunctions::getpagesize()
{
	return ::getpagesize();
}
#else
int libmaus2::posix::PosixFunctions::getpagesize()
{
	throw std::runtime_error("PosixFunctions::getpagesize: function not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_SYSCONF)
long libmaus2::posix::PosixFunctions::sysconf(int arg)
{
	return ::sysconf(arg);
}
#else
long libmaus2::posix::PosixFunctions::sysconf(int)
{
	throw std::runtime_error("PosixFunctions::sysconf: function not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_WIFEXITED)
int libmaus2::posix::PosixFunctions::posix_WIFEXITED(int status)
{
	return WIFEXITED(status);
}
#else
int libmaus2::posix::PosixFunctions::posix_WIFEXITED(int)
{
	throw std::runtime_error("PosixFunctions::WIFEXITED: function not supported");
}
#endif

#if defined(LIBMAUS2_HAVE_WEXITSTATUS)
int libmaus2::posix::PosixFunctions::posix_WEXITSTATUS(int status)
{
	return WEXITSTATUS(status);
}
#else
int libmaus2::posix::PosixFunctions::posix_WEXITSTATUS(int)
{
	throw std::runtime_error("PosixFunctions::WEXITSTATUS: function not supported");
}
#endif
