/*
    libmaus2
    Copyright (C) 2009-2019 German Tischler
    Copyright (C) 2011-2014 Genome Research Limited

    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/aio/PosixFdInput.hpp>

std::time_t libmaus2::aio::PosixFdInput::getTime()
{
	return time(nullptr);
}

void libmaus2::aio::PosixFdInput::printWarning(char const * const functionname, std::time_t const time, std::string const & filename, int const fd)
{
	if ( warnThreshold > 0 && time >= warnThreshold )
	{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "[W] warning PosixFdInput: " << functionname << "(" << fd << ")" << " took " << time << "s ";
		if ( filename.size() )
			std::cerr << " on " << filename;
		std::cerr << std::endl;
	}
}

void libmaus2::aio::PosixFdInput::printWarningRead(char const * const functionname, std::time_t const time, std::string const & filename, int const fd, uint64_t const size)
{
	if ( warnThreshold > 0.0 && time >= warnThreshold )
	{
		libmaus2::aio::StreamLock::lock_type::scope_lock_type slock(libmaus2::aio::StreamLock::cerrlock);
		std::cerr << "[W] warning PosixFdInput: " << functionname << "(" << fd << "," << size << ")" << " took " << time << "s ";
		if ( filename.size() )
			std::cerr << " on " << filename;
		std::cerr << std::endl;
	}
}

int libmaus2::aio::PosixFdInput::getDefaultFlags()
{
	int flags = libmaus2::posix::PosixFunctions::get_O_RDONLY();
	#if defined(LIBMAUS2_HAVE_O_LARGEFILE)
	flags |= libmaus2::posix::PosixFunctions::get_O_LARGEFILE();
	#endif
	#if defined(LIBMAUS2_HAVE_O_BINARY)
	flags |= libmaus2::posix::PosixFunctions::get_O_BINARY();
	#endif

	return flags;
}

libmaus2::aio::PosixFdInput::~PosixFdInput()
{
	if ( closeOnDeconstruct && fd >= 0 )
		close();
}

bool libmaus2::aio::PosixFdInput::tryOpen(std::string const & filename, int const rflags)
{
	char const * cfilename = filename.c_str();
	int fd = -1;

	// try to open the file
	while ( fd == -1 )
	{
		std::time_t const time_bef = getTime();
		fd = libmaus2::posix::PosixFunctions::open(cfilename,rflags);
		std::time_t const time_aft = getTime();
		printWarning("open",time_aft-time_bef,filename,fd);

		if ( fd == -1 )
		{
			switch ( errno )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
					// cannot open file
					return false;
			}
		}
	}

	assert ( fd != -1 );

	// close file
	while ( fd != -1 )
	{
		std::time_t const time_bef = getTime();
		int const r = libmaus2::posix::PosixFunctions::close(fd);
		std::time_t const time_aft = getTime();
		printWarning("close",time_aft-time_bef,filename,fd);

		if ( r == -1 )
		{
			switch ( errno )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
				{
					int const error = errno;
					libmaus2::exception::LibMausException se;
					se.getStream() << "PosixFdInput::tryOpen(" << filename << "," << rflags << "): " << strerror(error) << std::endl;
					se.finish();
					throw se;

				}
			}
		}
		else
		{
			fd = -1;
		}
	}

	// could open file
	return true;
}

libmaus2::aio::PosixFdInput::PosixFdInput(int const rfd) : filename(), fd(rfd), gcnt(0), closeOnDeconstruct(false)
{
}

libmaus2::aio::PosixFdInput::PosixFdInput(std::string const & rfilename, int const rflags)
: filename(rfilename), fd(-1), gcnt(0), closeOnDeconstruct(true)
{
	while ( fd == -1 )
	{
		std::time_t const time_bef = getTime();
		fd = libmaus2::posix::PosixFunctions::open(filename.c_str(),rflags);
		std::time_t const time_aft = getTime();
		printWarning("open",time_aft-time_bef,filename,fd);

		if ( fd < 0 )
		{
			switch ( errno )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
				{
					int const error = errno;
					libmaus2::exception::LibMausException se;
					se.getStream() << "PosixFdInput(" << filename << "," << rflags << "): " << strerror(error) << std::endl;
					se.finish();
					throw se;
				}
			}
		}
	}
}

::libmaus2::ssize_t libmaus2::aio::PosixFdInput::read(char * const buffer, uint64_t n)
{
	::libmaus2::ssize_t r = -1;
	gcnt = 0;

	while ( fd >= 0 && r < 0 )
	{
		std::time_t const time_bef = getTime();
		r = ::libmaus2::posix::PosixFunctions::read(fd,buffer,n);
		std::time_t const time_aft = getTime();
		printWarningRead("read",time_aft-time_bef,filename,fd,n);

		if ( r < 0 )
		{
			switch ( errno )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
				{
					int const error = errno;
					libmaus2::exception::LibMausException se;
					se.getStream() << "PosixFdInput::read(" << filename << "," << n << "): " << strerror(error) << std::endl;
					se.finish();
					throw se;
				}
			}
		}
		else
		{
			gcnt = r;
		}
	}

	return gcnt;
}

int64_t libmaus2::aio::PosixFdInput::lseek(uint64_t const n)
{
	if ( fd < 0 )
		return -1;

	libmaus2::autoarray::AutoArray<unsigned char> Aoffin(libmaus2::posix::PosixFunctions::getOffTSize());
	libmaus2::autoarray::AutoArray<unsigned char> Aoffout(libmaus2::posix::PosixFunctions::getOffTSize());
	libmaus2::autoarray::AutoArray<unsigned char> Aoffin64(sizeof(int64_t));

	// encode to 64 bit array
	libmaus2::posix::PosixFunctions::encodeSigned<int64_t>(n,Aoffin64.begin(),sizeof(int64_t));
	// translate to off_t
	libmaus2::posix::PosixFunctions::translateSigned(Aoffin.begin(),Aoffin.size(),Aoffin64.begin(),Aoffin64.size());

	// translate back
	libmaus2::posix::PosixFunctions::translateSigned(Aoffin64.begin(),Aoffin64.size(),Aoffin.begin(),Aoffin.size());
	// check
	assert ( libmaus2::posix::PosixFunctions::decodeSigned<int64_t>(Aoffin64.begin(),Aoffin64.size()) == static_cast<int64_t>(n) );

	while ( true )
	{
		std::time_t const time_bef = getTime();
		libmaus2::posix::PosixFunctions::posix_lseek(Aoffout.begin(),fd,Aoffin.begin(),libmaus2::posix::PosixFunctions::get_SEEK_SET());
		std::time_t const time_aft = getTime();
		printWarning("lseek",time_aft-time_bef,filename,fd);

		if ( libmaus2::posix::PosixFunctions::offTIsMinusOne(Aoffout.begin()) )
		{
			switch ( errno )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
				{
					int const error = errno;
					libmaus2::exception::LibMausException se;
					se.getStream() << "PosixFdInput::lseek(" << filename << "," << n << "): " << strerror(error) << std::endl;
					se.finish();
					throw se;
				}
			}
		}
		else
		{
			return libmaus2::posix::PosixFunctions::decodeSigned<int64_t>(Aoffout.begin(),Aoffout.size());
		}
	}
}

::libmaus2::ssize_t libmaus2::aio::PosixFdInput::gcount() const
{
	return gcnt;
}

void libmaus2::aio::PosixFdInput::close()
{
	int r = -1;

	while ( fd >= 0 && r == -1 )
	{
		std::time_t const time_bef = getTime();
		r = libmaus2::posix::PosixFunctions::close(fd);
		std::time_t const time_aft = getTime();
		printWarning("close",time_aft-time_bef,filename,fd);

		if ( r == 0 )
		{
			fd = -1;
		}
		else
		{
			assert ( r == -1 );

			int const error = errno;

			switch ( error )
			{
				case EINTR:
					break;
				default:
				{
					libmaus2::exception::LibMausException se;
					se.getStream() << "PosixFdInput::close(" << filename << "," << fd << "): " << strerror(error) << std::endl;
					se.finish();
					throw se;
				}
			}
		}
	}
}

uint64_t libmaus2::aio::PosixFdInput::size()
{
	libmaus2::autoarray::AutoArray<unsigned char> Amode(libmaus2::posix::PosixFunctions::getFStatModeSize());
	libmaus2::off_t size;
	int r = -1;

	while ( fd >= 0 && r < 0 )
	{
		std::time_t const time_bef = getTime();
		r = libmaus2::posix::PosixFunctions::fstatSizeAndMode(fd,size,Amode.begin());
		std::time_t const time_aft = getTime();
		printWarning("fstat",time_aft-time_bef,filename,fd);

		if ( r < 0 )
		{
			switch ( errno )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
				{
					int const error = errno;
					libmaus2::exception::LibMausException se;
					se.getStream() << "PosixFdInput::size(" << filename << "): " << strerror(error) << " fd=" << fd << " r=" << r << std::endl;
					se.finish();
					throw se;
				}
			}
		}
	}

	if ( ! libmaus2::posix::PosixFunctions::modeIsReg(Amode.begin()) )
	{
		libmaus2::exception::LibMausException se;
		se.getStream() << "PosixFdInput::size(" << filename << "," << fd << "): file descriptor does not designate a (regular) file" << std::endl;
		se.finish();
		throw se;
	}

	return size;
}

int64_t libmaus2::aio::PosixFdInput::sizeChecked()
{
	libmaus2::autoarray::AutoArray<unsigned char> Amode(libmaus2::posix::PosixFunctions::getFStatModeSize());
	libmaus2::off_t size;
	int r = -1;

	while ( fd >= 0 && r < 0 )
	{
		std::time_t const time_bef = getTime();
		r = libmaus2::posix::PosixFunctions::fstatSizeAndMode(fd,size,Amode.begin());
		std::time_t const time_aft = getTime();
		printWarning("fstat",time_aft-time_bef,filename,fd);

		if ( r < 0 )
		{
			switch ( errno )
			{
				case EINTR:
				case EAGAIN:
					break;
				default:
				{
					int const error = errno;
					libmaus2::exception::LibMausException se;
					se.getStream() << "PosixFdInput::sizeChecked(" << filename << "): " << strerror(error) << " fd=" << fd << " r=" << r << std::endl;
					se.finish();
					throw se;
				}
			}
		}
	}

	if ( libmaus2::posix::PosixFunctions::modeIsReg(Amode.begin()) )
		return size;
	else
		return -1;
}

int64_t libmaus2::aio::PosixFdInput::getOptimalIOBlockSize()
{
	return getOptimalIOBlockSize(fd, filename);
}

int64_t libmaus2::aio::PosixFdInput::getOptimalIOBlockSize(int const fd, std::string const & filename)
{
	int64_t override = -1;
	for ( std::map<std::string,uint64_t>::const_iterator ita = blocksizeoverride.begin(); ita != blocksizeoverride.end(); ++ita )
	{
		std::string const & key = ita->first;

		if ( key.size() <= filename.size() && filename.substr(0,key.size()) == key )
			override = static_cast<int64_t>(ita->second);
	}

	if ( override > 0 )
		return override;

	while ( fd >= 0 )
	{
		std::time_t const time_bef = getTime();
		int64_t const r = libmaus2::posix::PosixFunctions::getFIOsizeViaFStatFS(fd);
		std::time_t const time_aft = getTime();
		printWarning("fstatfs",time_aft-time_bef,filename,fd);

		if ( r < 0 )
		{
			switch ( errno )
			{
				case EINTR:
				case EAGAIN:
					break;
				// file system does not support statfs
				case ENOSYS:
				case ENOTSUP:
					return -1;
				#if defined(__FreeBSD__)
				case EINVAL:
					return -1;
				#endif
				default:
				{
					int const error = errno;
					libmaus2::exception::LibMausException se;
					se.getStream() << "PosixFdInput::getOptimalIOBlockSize(" << filename << "): " << strerror(error) << std::endl;
					se.finish();
					throw se;
				}
			}
		}
		else
		{
			return r;
		}
	}

	return -1;
}

uint64_t libmaus2::aio::PosixFdInput::getTotalIn()
{
	uint64_t ltotalin;
	libmaus2::parallel::StdSpinLock::scope_lock_type slock(totalinlock);
	ltotalin = totalin;
	return ltotalin;
}
