/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2022, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "io-precomp.h"	 // Precompiled headers
//
#include <mrpt/io/CFileStream.h>

#include <iomanip>
#include <iostream>
#include <string>  // std::getline()

using namespace mrpt::io;
using namespace std;

static_assert(
	!std::is_copy_constructible_v<CFileStream> &&
		!std::is_copy_assignable_v<CFileStream>,
	"Copy Check");

/*---------------------------------------------------------------
							Constructor
 ---------------------------------------------------------------*/
CFileStream::CFileStream() : m_f() {}
/*---------------------------------------------------------------
							Constructor
 ---------------------------------------------------------------*/
CFileStream::CFileStream(const string& fileName, TFileOpenModes mode_) : m_f()
{
	std::ios_base::openmode mode = std::ios_base::in;
	if (mode_ == fomRead) mode = std::ios_base::in;
	else if (mode_ == fomWrite)
		mode = std::ios_base::out | std::ios_base::trunc;
	else if (mode_ == fomAppend)
		mode = std::ios_base::app | std::ios_base::out;
	else if (mode_ == (fomRead | fomWrite))
		mode = std::ios_base::in | std::ios_base::out | std::ios_base::trunc;
	else if (mode_ == (fomAppend | fomWrite))
		mode = std::ios_base::in | std::ios_base::out | std::ios_base::app;

	// Try to open the file:
	m_f.open(fileName.c_str(), mode);
	if (!m_f.is_open())
		throw std::runtime_error(
			std::string("CFileStream: Error creating/opening: ") + fileName);
}

/*---------------------------------------------------------------
							open
 ---------------------------------------------------------------*/
bool CFileStream::open(const std::string& fileName, TFileOpenModes mode_)
{
	std::ios_base::openmode mode = std::ios_base::in;
	if (mode_ == fomRead) mode = std::ios_base::in;
	else if (mode_ == fomWrite)
		mode = std::ios_base::out | std::ios_base::trunc;
	else if (mode_ == fomAppend)
		mode = std::ios_base::app | std::ios_base::out;
	else if (mode_ == (fomRead | fomWrite))
		mode = std::ios_base::in | std::ios_base::out | std::ios_base::trunc;
	else if (mode_ == (fomAppend | fomWrite))
		mode = std::ios_base::in | std::ios_base::out | std::ios_base::app;

	if (m_f.is_open()) m_f.close();

	m_f.open(fileName.c_str(), ios_base::binary | mode);
	return m_f.is_open();
}

/*---------------------------------------------------------------
							close
 ---------------------------------------------------------------*/
void CFileStream::close() { m_f.close(); }
/*---------------------------------------------------------------
							Destructor
 ---------------------------------------------------------------*/
CFileStream::~CFileStream() { m_f.close(); }
/*---------------------------------------------------------------
							Read
			Reads bytes from the stream into Buffer
 ---------------------------------------------------------------*/
size_t CFileStream::Read(void* Buffer, size_t Count)
{
	if (!m_f.is_open()) return 0;
	m_f.read(static_cast<char*>(Buffer), Count);
	return m_f.fail() ? 0 : Count;
}

/*---------------------------------------------------------------
							Write
			Writes a block of bytes to the stream.
 ---------------------------------------------------------------*/
size_t CFileStream::Write(const void* Buffer, size_t Count)
{
	if (!m_f.is_open()) return 0;

	m_f.write(static_cast<const char*>(Buffer), Count);
	return m_f.fail() ? 0 : Count;
}

/*---------------------------------------------------------------
							Seek
	Method for moving to a specified position in the streamed resource.
	 See documentation of CStream::Seek
 ---------------------------------------------------------------*/
uint64_t CFileStream::Seek(int64_t Offset, CStream::TSeekOrigin Origin)
{
	if (!m_f.is_open()) return 0;

	fstream::off_type offset = Offset;
	fstream::seekdir way;

	switch (Origin)
	{
		case sFromBeginning: way = ios_base::beg; break;
		case sFromCurrent: way = ios_base::cur; break;
		case sFromEnd: way = ios_base::end; break;
		default:
			throw std::runtime_error(
				"[CFileStream::Seek] Invalid value for 'Origin'");
	}

	m_f.seekp(offset, way);
	m_f.seekg(offset, way);

	return getPosition();
}

uint64_t CFileStream::getTotalBytesCount() const
{
	if (!fileOpenCorrectly()) return 0;

	auto& f = const_cast<std::fstream&>(m_f);

	const uint64_t previousPos = f.tellg();
	f.seekg(0, ios_base::end);
	uint64_t fileSize = f.tellg();
	f.seekg(previousPos, ios_base::beg);
	return fileSize;
}

uint64_t CFileStream::getPosition() const
{
	auto& f = const_cast<std::fstream&>(m_f);
	if (m_f.is_open()) return f.tellg();
	else
		return 0;
}

/*---------------------------------------------------------------
						getPositionI
 ---------------------------------------------------------------*/
uint64_t CFileStream::getPositionI()
{
	if (m_f.is_open()) return m_f.tellg();
	else
		return 0;
}

/*---------------------------------------------------------------
						getPositionO
 ---------------------------------------------------------------*/
uint64_t CFileStream::getPositionO()
{
	if (m_f.is_open()) return m_f.tellp();
	else
		return 0;
}

/*---------------------------------------------------------------
						fileOpenCorrectly
 ---------------------------------------------------------------*/
bool CFileStream::fileOpenCorrectly() const { return m_f.is_open(); }
/*---------------------------------------------------------------
						readLine
 ---------------------------------------------------------------*/
bool CFileStream::readLine(string& str)
{
	str = string();	 // clear() is not defined in VC6
	if (!m_f.is_open()) return false;

	std::getline(m_f, str);
	return !m_f.fail() && !m_f.eof();
}

/*---------------------------------------------------------------
						checkEOF
 ---------------------------------------------------------------*/
bool CFileStream::checkEOF()
{
	if (!m_f.is_open()) return true;
	return m_f.eof();
}

void CFileStream::clearError()
{
	if (m_f.is_open()) m_f.clear();
}
