/**
 * @file
 * @brief  FLCBYTBF - C++ stream with FLUC byte interface
 * @author limes datentechnik gmbh
 * @date   31.10.2014
 * @copyright limes datentechnik gmbh
 *
 * @mainpage FLUC C++ Stream Buffer Interface
 *
 * The C++ stream buffer interface allows to use the C++ stream facilities
 * to read and write files through the FLUC byte interface. More precisely,
 * it is backed by the byte interface's <code>fcb*()</code> functions. This
 * wrapper interface in the form of the #flcbyt_buffer class is meant to
 * facilitate working with FLUC when reading or writing streamable data.
 *
 * To open a file, a file string and a format string is required. For
 * detailed information about those strings, please consult the
 * documentation of the FLUC byte interface (FLCBYT).
 *
 * @note
 * This stream interface cannot be reasonably used with
 * <code>format.record()</code> and <code>format.element()</code>
 * format strings because those reading/writing formats are not
 * streamable. To use any of them, you have to use FLUC byte interface
 * (FLCBYT) directly.
 *
 **********************************************************************/

/*! @cond PRIVATE */
#ifndef FLCBYT_BUFFER_HPP
#define FLCBYT_BUFFER_HPP

#include <streambuf>
#include <vector>
#include <cstdlib>
#include <iostream>

#if defined(__BORLANDC__)
#include <stdio.h>
#else
#include <cstdio>
#endif
/*! @endcond */

#include "FLMDEF.h"

/**********************************************************************/
/*! @cond PRIVATE */
#define FCB_READ_FILE               FLC_READ_FILE
#define FCB_READ_FORMAT             FLC_READ_FORMAT
#define FCB_WRITE_FILE              FLC_WRITE_FILE
#define FCB_WRITE_FORMAT            FLC_WRITE_FORMAT
#define FCB_INPUT_FILE              FLC_INPUT_FILE
#define FCB_OUTPUT_FILE             FLC_OUTPUT_FILE
#define FCB_INFO                    FLC_INFO
#define FCB_STATE                   FLC_STATE
/*! @endcond */
/**********************************************************************/

/**
 * @brief A C++ stream buffer to use with istream/ostream
 *
 * flcbyt_buffer is a streambuf implementation that allows to use the C++
 * stream facilities to read and write files via FLUC. It is supposed to
 * be passed to an istream or ostream object. The stream buffer is backed
 * by the FLUC byte interface. (fcb*() functions)
 */
class flcbyt_buffer: public std::streambuf {
   public:
      /**
       * @brief Constructor for a FLUC byte interface streambuf implementation
       *
       * Calls *fcbopen(filstr, fmtstr)* of the FLUC byte interface to open a
       * normal file, a member in a FLAM4FILE, a GZIP file or a member in a
       * concatenated GZIP-, BZIP- or XZ-File or some other supported file
       * format. It supports base encoding, encryption, compression, character
       * conversion and text formatting, among others.
       *
       * The handle obtained from *fcbopen()* is stored internally for subsequent
       * operations, alongside with an internal buffer.
       *
       * The file definition string uses the same syntax as the read or write
       * method of the FLCL CONV command. The complete syntax and help can be
       * determined with the corresponding functions below. The zero-terminated
       * string must start with 'read.' or 'write.' followed by the corresponding
       * method (binary/char/text/flam4/xml/...).
       *
       * Examples:
       * @code
       *    "read.text(file='test.txt')"
       *    "write.binary(file='test.gz' comp.gzip(level=BEST))"
       * @endcode
       *
       * For more information, please use:
       * @code
       *    'fcbhelp/syntax(FLC_READ_FILE,10,NULL)'
       *    'fcbhelp/syntax(FLC_WRITE_FILE,10,NULL)'
       * @endcode
       *
       * The format string uses the syntax of the 'format' union which was specially
       * designed for the byte interface to format a byte stream based on the FLAM
       * elements. The zero-terminated string must start with 'format.' followed by
       * the corresponding method.
       *
       * Examples:
       * @code
       *    "format.text(method=CRLF suptws ccsid='UTF-16LE')"
       *    "format.bin()"
       *    "format.char(ccsid=DEFAULT)"
       *    "format.record(ccsid='IBM-1141')"
       * @endcode
       *
       * For more information, please use:
       * @code
       *    'fcbhelp/syntax(FLC_READ_FORMAT,10,NULL)'
       *    'fcbhelp/syntax(FLC_WRITE_FORMAT,10,NULL)'
       * @endcode
       *
       * The file string defines how the FLAM elements are read from or written
       * to the file. The format string defines how the FLAM elements are converted
       * into a byte stream or records.
       *
       * The state parameter can be used to (optionally) transfer some internal
       * state from an input stream to an output stream. The internal state can
       * be used to transfer attributes/values/information from a "read.*" to a "write.*"
       * operation. The state string contains mainly file attributes which must be
       * known to fill header information for archives (GZIP, FLAM) correctly.
       * You are responsible to free() the string when you no longer need the state
       * string obtained from a read operation.
       *
       * The parameter is a pointer to a pointer to a string. On read mode, if
       * you pass a non-null pointer, the pointer points to a string pointer
       * pointing to the state string after the open procedure. On write mode,
       * if you pass a non-null pointer to a pointer to a state string, this
       * state string is used to set the internal state. Otherwise, the state
       * will be initialized with default values.
       *
       * You can look up the state string syntax and help with the corresponding
       * functions below. The zero-terminated string must start with 'state('
       * followed by the corresponding value definitions and closed with ')'.
       *
       * Example:
       * @code
       * "state(member='test.txt' recf=FBA recl=120)"
       * @endcode
       *
       * For more information, please use:
       * @code
       *    'fcbhelp/syntax(FLC_STATE,10,NULL)'
       * @endcode
       *
       * Below you can find two examples:
       *
       * 1. Write only
       * @code
       * char* state="STATE(file='test.txt')";
       * flcbyt_buffer writebuf("write.text(comp.gzip())","format.text()",&state);
       * std::ostream out(&writebuf);
       * @endcode
       * 2. Read and write
       * @code
       * char* state=NULL;
       * flcbyt_buffer readbuf("read.text(file='test.txt')","format.text()",&state);
       * flcbyt_buffer writebuf("write.text(comp.gzip())","format.text()",&state);
       * std::istream in(&readbuf);
       * std::ostream out(&writebuf);
       * if (state)
       *    free(state);
       * @endcode
       *
       * In both cases, the member name for the original file in the GZIP header
       * is 'test.txt'.
       *
       * @param [in]      filstr   File string with READ/WRITE instructions
       * @param [in]      fmtstr   Format string with FORMAT instructions
       * @param [inout]   state    Pointer to a pointer to a string with STATE values (optional, must be free()d on "read.*" if you provide a non-null pointer)
       * @param [in]      buff_sz  Size of the internal buffer (optional, default: 4096)
       * @param [in]      put_back Number of bytes you are guaranteed to be able to put back (only for istream, optional, default: 1)
       * @throws flam_exception Thrown if the file cannot be opened by fcbopen()
       */
      explicit flcbyt_buffer(const char* filstr, const char* fmtstr, char** state=NULL, size_t buff_sz=4096, size_t put_back=1);
      /**
       * @brief Flushes buffers and closes the file
       *
       * Unless close() has been called, calls *fcbclose()* to flush all buffers and close the file.
       * There will be no indication if an error occurs while flushing buffers (except an incomplete output file).
       * So, it is recommended to call close() which throws an exception on error.
       */
      ~flcbyt_buffer();
      /**
       * @brief Flushes all internal buffers and closes the file.
       *
       * Flushes all internal buffers and closes the file by calling *fcbclose()*.
       * If an error occurs, a flam_exception is thrown, containing an error message.
       *
       * It is recommended to call close() after the last operation on the file.
       * Otherwise, there will be no indication if data that still resides in
       * internal buffers fails to write to the file, resulting in an incomplete file.
       *
       * This function returns statistics information in readable string format.
       * You can define the format of the string. The default is FLMOUT_FORMAT_LST,
       * resulting in a simple list of strings with each line separated by '\\n'.
       *
       * @param statfmt The returned statistics format (Possible values: FLMOUT_FORMAT_* [see FLAMDEF.h])
       *
       * @return A human-readable string with statistics information
       */
      std::string close(const int statfmt=FLMOUT_FORMAT_LST);
      /**
       * @brief Returns an error code for the last byte interface operation
       *
       * Returns the error code for the last FLUC byte interface operation.
       * Must not be called after a calling close() (undefined behavior).
       * The error code is the same as returned by *fcberror()*.
       *
       * @return A non-zero value if the last operation resulted in an error,
       *         zero otherwise
       */
      int error();
      /**
       * @brief Returns an error message for the last byte interface operation
       *
       * Returns an error message for the last FLUC byte interface operation on
       * the file. Must not be called after a calling close() (undefined behavior).
       * The error message is the same as returned by *fcberrms(fcberror())*.
       *
       * @return A non-zero value if the last operation resulted in an error,
       *         zero otherwise
       */
      std::string errormsg();
      /**
       * @brief Returns a FLAM error trace
       *
       * This function can be used to get an error trace for the last byte
       * interface operation. The error trace contains the FLAM error stack.
       * If parsing the file or format string failed, the error trace also
       * contains the complete error stack of the Command Line Parser (FLAMCLP).
       *
       * @return A multi-line string containing the current FLAM error trace
       */
      static std::string errortrace();
      /**
       * @brief Retrieves version information
       *
       * This function returns a string with version information for each
       * used component. This should be used in a support case.
       *
       * @return String with version information
       */
      static std::string version();
      /**
       * @brief Retrieves about information
       *
       * This function returns a string with about information for this
       * library on multiple lines and license information for used
       * external libraries.
       *
       * @return String with about information
       */
      static std::string about();
      /**
       * @brief Retrieves license information
       *
       * This function can be used to get the current license text on multiple
       * lines. The license text defines the permissible use of this library.
       *
       * @return String with license information
       */
      static std::string license();
      /**
       * @brief Returns help information about the file and format strings
       *
       * This function can be used to get all help messages or man pages for
       * each parameter in the file and format strings. If depth 0 or greater
       * then 9 aliases are provided. In all other cases aliases are suppressed.
       * Please use fcbsyntax() to see possible aliases for the same argument.
       *
       * @param[in]  what  Integer constant for the corresponding file or format string (one of FLC_READ_* or FLC_WRITE_*)
       * @param[in]  depth Number of levels to display (0-Man page, 1-One Level, 2-Two Level, ..., <9-All)
       * @param[in]  path  Path (e.g. read.text.ccsid) to limit help to the (sub)tree of a certain object
       *
       * @return a string containing the help information on several
       *         lines, in case of an error the string is empty.
       */
      static std::string help(const int what, const int depth, const char* path);
      /**
       * @brief Returns syntax information about the file and format strings
       *
       * This function can be used to determine the complete syntax of the
       * file and format strings.
       *
       * @param[in]  what  Integer constant for the corresponding file or format string (one of FLC_READ_* or FLC_WRITE_*)
       * @param[in]  depth Number of levels to display (0-Man page, 1-One Level, 2-Two Level, ..., <9-All)
       * @param[in]  path  Path (e.g. read.text.ccsid) to limit syntax to the (sub)tree of a certain object
       *
       * @return a pointer to a static area with a zero-terminated string
       *         containing the syntax information in one (level == 1) or
       *         several lines (level > 1), in case of an error the string
       *         is empty.
       */
      static std::string syntax(const int what, const int depth, const char* path);

   private:
      // overrides base class functions underflow(), overflow(), sync()
      int_type underflow();
      int_type overflow(int_type ch);
      int sync();
      //xsputn() // TODO: overwrite, else each several byte is copied

      // copy constructor and assignment not implemented;
      flcbyt_buffer(const flcbyt_buffer &);
      // copying not allowed
      flcbyt_buffer &operator=(const flcbyt_buffer &);

   private:
      const char* filstr_;
      const char* fmtstr_;
      const std::size_t put_back_;
      std::vector<char> buffer_;
      void* handle;
};

/**********************************************************************/

/*! @cond PRIVATE */
#endif /*FLCBYT_BUFFER_HPP*/
/*! @endcond */
