/*
 * fdstream.hpp
 *
 * This file is part of Algorithms library toolkit.
 * Copyright (C) 2017 Jan Travnicek (jan.travnicek@fit.cvut.cz)

 * Algorithms library toolkit 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.

 * Algorithms library toolkit 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 Algorithms library toolkit.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Created on: Feb 12, 2016
 * Author: Radovan Cerveny
 */

/*
 * \brief
 * ofdstream takes a primary file descriptor (fd) and fallback file descriptor by constructor to use for output
 * if the primary fd is not valid for any reason, ofdstream will try to use fallback fd, if even fallback fd is not valid, ofdstream sets its state to ios::fail
 * if the primary fd (or fallback fd) is valid , it is used for successive buffered output
 *
 * supplied file descriptor has to be already open in order for ofdstream to work
 * ofdstream does not close the file descriptor and does not change internal flags of the file descriptor during the lifetime of ofdstream and during destruction of ofdstream
 *
 * typical use is to use as primary fd some arbitrary fd (5) and for fallback fd some standard fd (2) if we wish to see the output
 */

#pragma once

#include <unistd.h>
#include <fcntl.h>
#include <ostream>
#include <istream>
#include <array>

namespace ext {

/**
 * \brief
 * The number of standard error stream descriptor. Expected to be 2.
 */
extern const int CERR_FD;

/**
 * \brief
 * The number of measurement stream descriptor. Expected to be 5.
 */
extern const int CMEASURE_FD;

/**
 * \brief
 * The number of logging stream descriptor. Expected to be 4.
 */
extern const int CLOG_FD;

/**
 * \brief
 * The number of descriptor when binding to the predefined one fails.
 */
extern const int FAIL_FD;

/**
 * \brief
 * Class representing buffered stream designed to work on defined file descriptor instead of usual file name.
 *
 */
class fdstreambuf final : public std::streambuf {
	/**
	 * \brief
	 * The size of the internal buffer.
	 */
	static const size_t buff_sz = 512;

	/**
	 * \brief
	 * The file descriptor number.
	 */
	int fd;

	/**
	 * \brief
	 * The internal buffer
	 */
	std::array < char_type, buff_sz > buff;

	/**
	 * \brief
	 * A method to flush the buffer to the destination represented by the file descriptor
	 */
	bool flush ( );

protected:
	/**
	 * \brief
	 * Put character back in the case of backup underflow.
	 *
	 * Virtual function called by other member functions to put a character back into the controlled input sequence and decrease the position indicator.
	 *
	 * For more defails see the documentation of the standard library.
	 */
	int_type pbackfail ( int_type ) override;

	/**
	 * \brief
	 * Get character on underflow.
	 *
	 * Virtual function called by other member functions to get the current character in the controlled input sequence without changing the current position.
	 *
	 * For more defails see the documentation of the standard library.
	 */
	int_type underflow ( ) override;

	/**
	 * \brief
	 * Put character on overflow.
	 *
	 * Virtual function called by other member functions to put a character into the controlled output sequence without changing the current position.
	 *
	 * For more defails see the documentation of the standard library.
	 */
	int_type overflow ( int_type ) override;

	/**
	 * \brief
	 * Synchronize stream buffer.
	 *
	 * Virtual function called by the public member function pubsync to synchronize the contents in the buffer with those of the associated character sequence.
	 *
	 * For more defails see the documentation of the standard library.
	 */
	int sync ( ) override;

public:
	/**
	 * \brief
	 * Constructor of the fdstreambuf
	 *
	 * \param fd the file descriptor number
	 */
	explicit fdstreambuf ( int fileDescriptor );

	/**
	 * \brief
	 * The destructor of the fdstreambuf
	 */
	~fdstreambuf ( ) override;

	/**
	 * \brief
	 * Do not allow to copy construct the fdsreambuf
	 */
	fdstreambuf ( const fdstreambuf & ) = delete;

	/**
	 * \brief
	 * Do not allow to copy assign the fdsreambuf
	 */
	fdstreambuf & operator =( const fdstreambuf & ) = delete;
};

/**
 * \brief
 * A class determining the used file descriptor from prefered (if available) and fallback file descriptor.
 *
 * The prefered descriptor is used if the descriptor of the given number is available in the runtime.
 */
class fdaccessor {
	/**
	 * \brief
	 * The resulting used file descriptor number.
	 */
	int fd;

	/**
	 * \brief
	 * Flag stating, whether the prefered file descriptor (true) or fallback file descriptor (false) is used.
	 */
	bool redirected;

public:
	/**
	 * \brief
	 * Constrcutor of the class determining the resulting used descriptor.
	 */
	explicit fdaccessor ( int, int );

	/**
	 * \brief
	 * Getter of the chosen file descriptor, either the prefered, or fallback one.
	 *
	 * \return the chosen file descriptor.
	 */
	int get_fd ( ) const;

	/**
	 * \brief
	 * Getter of the redirected flag informing whether prefered or fallback file descriptor is used.
	 *
	 * \return true if the prefered file descriptor is used or false if the fallback is used
	 */
	bool is_redirected ( ) const;
};

/**
 * \brief
 * A class implementing an output stream interface, with destination specified by file descriptor.
 */
class ofdstream : public std::ostream {
	/**
	 * \brief
	 * The representation of switch between prefered and fallback file descriptor
	 */
	fdaccessor fda;

	/**
	 * \brief
	 * The destination stream buffer of data.
	 */
	fdstreambuf fdbuf;

public:
	/**
	 * \brief
	 * A constructor of the output file descriptor stream with specified prefered and fallback file descriptors.
	 *
	 * \param preferedFD the prefered destination file descriptor
	 * \param fallbackFD the fallback destination file descriptor
	 */
	explicit ofdstream ( int fd, int fallback_fd = FAIL_FD);

	/**
	 * \brief
	 * A desctructor of the output file descriptor
	 */
	~ofdstream ( ) override;

	/**
	 * \brief
	 * Getter of the redirected flag informing whether prefered or fallback file descriptor is used.
	 *
	 * \return true if the prefered file descriptor is used or false if the fallback is used
	 */
	bool is_redirected ( ) const;
};

/**
 * \brief
 * A class implementing an input stream interface, with destination specified by file descriptor.
 */
class ifdstream : public std::istream {
	/**
	 * The representation of switch between prefered and fallback file descriptor
	 */
	fdaccessor fda;

	/**
	 * \brief
	 * The source stream buffer of data.
	 */
	fdstreambuf fdbuf;

public:
	/**
	 * \brief
	 * A constructor of the input file descriptor stream with specified prefered and fallback file descriptors.
	 *
	 * \param preferedFD the prefered destination file descriptor
	 * \param fallbackFD the fallback destination file descriptor
	 */
	explicit ifdstream ( int, int = FAIL_FD);

	/**
	 * \brief
	 * A desctructor of the input file descriptor
	 */
	~ifdstream ( ) override;

	/**
	 * \brief
	 * Getter of the redirected flag informing whether prefered or fallback file descriptor is used.
	 *
	 * \return true if the prefered file descriptor is used or false if the fallback is used
	 */
	bool is_redirected ( ) const;
};

} /* namespace ext */

