/**
 * @file form_out_stream.cc
 *
 * FormOutStream - Stream class for specially formatted text output.
 *
 * Copyright: 2002-2003 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: form_out_stream.cc,v 1.1.2.1 2006/02/28 21:35:42 thomas Exp $
 * Project  CAPI for BSD
 * Target   capitrace - Tracing CAPI calls and messages
 * @date    01.12.2002
 * @author  "Thomas Wintergerst" <twinterg@gmx.de>
 * <p>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * </p>
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

// System includes
#include <sys/types.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <iomanip>
#include <stdexcept>
#include <typeinfo>

// Import includes

#define __FORM_OUT_STREAM__

// Local includes
#ifndef __FORM_OUT_STREAM_H
#  include "form_out_stream.h"
#endif





// === Private definitions ===============================================





// === Definitions for public declarations ===============================





// === Prototypes of private functions ===================================





// === Implementation of class members ===================================





// --- Stream class for specially formatted text output ------------------

/**
 * Constructor.
 */

CFormOutStream::CFormOutStream (void)
{
   m_uLeftMargin = 0;
   m_uNextLeftMargin = 0;
   m_fLeftMarginDelayed = false;
   m_uRightMargin = 0;
   
} // CFormOutStream::CFormOutStream





/**
 * Destructor.
 */

CFormOutStream::~CFormOutStream (void)
{
} // CFormOutStream::~CFormOutStream





/**
 * Switch output to a regular file.
 */

void CFormOutStream::open
   (const char         *pszFileName,
    std::ios::openmode  mode,
    int                 iProt)
{
   // first be sure the underlying file stream is closed
   if (m_ofs.is_open ())
   {
      m_ofs.close ();
   }
   
   // if the file name is empty or a simple "-" output is assumed to go to
   // cout
   if (pszFileName && pszFileName [0] != '\0' &&
       strcmp (pszFileName, "-") != 0)
   {
      m_ofs.open (pszFileName, std::ios::out | std::ios::trunc);
      if (! m_ofs.is_open())
      {
         std::ostringstream oss;
         oss << "Error opening output file \"" << pszFileName
             << "\": " << strerror (errno) << " (" << errno << ")";
         throw std::runtime_error (oss.str ().c_str ());
      }
   }
   
} // CFormOutStream::open





/**
 * Switch output back to cout.
 */

void CFormOutStream::close (void)
{
   if (m_ofs.is_open ())
   {
      m_ofs.close ();
   }
   
} // CFormOutStream::close





/**
 * Query if output is redirected to a regular file.
 */

bool CFormOutStream::is_open (void)
{
   return (m_ofs.is_open ());
} // CFormOutStream::is_open





/**
 * Start a new line with formatting.
 *
 * This function assumes the internal stream buffer is filled with a
 * single line of output (not ending with a cr, lf or eos). According to
 * the current settings for the left and right margin the line is written
 * to the current output stream (the file stream member or cout if the
 * file stream is not open).
 *
 * First as many spaces as the left margin is set to are written to the
 * output stream. If the right margin is set to a position right of the
 * left margin (i.e. it is greater than the left margin) the line will be
 * separated into parts (hopefully) fitting into the margin settings. We
 * start searching for a whitespace character from the right margin string
 * position to the beginning. If none is found the search goes right to
 * the next whitespace character. At the position found this way we insert
 * a newline character into the line string. The remaining characters of
 * the line are print the same way between the two margins.
 *
 * @param None.
 *
 * @return The reference to this stream object.
 */

std::ostream &CFormOutStream::newl (void)
{
   std::string line = str ();
   
   if (m_uRightMargin > m_uLeftMargin)
   {
      size_t nWidth = m_uRightMargin - m_uLeftMargin;
      size_t nPos;
      
      while (line.length () > 0)
      {
         nPos = find_separator_pos (line, nWidth);
         write_string (line.substr (0, nPos));
         while (nPos < line.length () && isspace (line [nPos]))
         {
            ++nPos;
         }
         line.erase (0, nPos);

         if (m_fLeftMarginDelayed)
         {
            m_fLeftMarginDelayed = false;
            m_uLeftMargin = m_uNextLeftMargin;
            nWidth = m_uRightMargin - m_uLeftMargin;
         }
      }
   }
   else
   {
      write_string (line);

      if (m_fLeftMarginDelayed)
      {
         m_fLeftMarginDelayed = false;
         m_uLeftMargin = m_uNextLeftMargin;
      }
   }
   
   str (std::string (""));

   return (*this);
} // CFormOutStream::newl





/**
 * Set the left margin for the current output line.
 */

void CFormOutStream::leftmargin
   (unsigned uNewMargin,
    bool     fDelayed)
{
   if (fDelayed)
   {
      m_uNextLeftMargin = uNewMargin;
      m_fLeftMarginDelayed = true;
   }
   else
   {
      m_uLeftMargin = uNewMargin;
   }
   
} // CFormOutStream::leftmargin





/**
 * Read the current left margin setting.
 */

unsigned CFormOutStream::leftmargin (void)
{
   return (m_uLeftMargin);
} // CFormOutStream::leftmargin





/**
 * Set the right margin for the current output line.
 */

void CFormOutStream::rightmargin
   (unsigned uNewMargin)
{
   m_uRightMargin = uNewMargin;
   
} // CFormOutStream::rightmargin





/**
 * Read the current right margin setting.
 */

unsigned CFormOutStream::rightmargin (void)
{
   return (m_uRightMargin);
} // CFormOutStream::rightmargin





/**
 * Perform a hex dump output.
 */

void CFormOutStream::hexdump
   (const void *pData,
    size_t      nLenData)
{
   // prepare the stream for the following operation
   char c = fill ();
   fmtflags f = flags ();
   fill ('0');
   setf (std::ios::hex | std::ios::right,
         std::ios::basefield | std::ios::adjustfield);

   // loop to print out a line of at most 16 bytes
   int iLineIdx;
   int iColIdx;
   const u_int8_t *p;
   const u_int8_t *q;
   for (iLineIdx = 0; (size_t) iLineIdx < nLenData; iLineIdx += 16)
   {
      // address the data for the current line
      p = &(((const u_int8_t *) pData) [iLineIdx]);

      // print the current address offset for the buffer
      (*this) << std::setw (4) << iLineIdx << " : ";
      
      // loop to print out the next 16 bytes of data in hex
      for (iColIdx = 0, q = p;
           iColIdx < 16 && (size_t) (iLineIdx + iColIdx) < nLenData;
           ++iColIdx, ++q)
      {
         if (iColIdx == 7)
         {
            (*this) << std::setw (2) << (unsigned) *q << '-';
         }
         else
         {
            (*this) << std::setw (2) << (unsigned) *q << ' ';
         }
      }
      
      // if the line was not complete print out spaces for the remaining bytes
      // up to the full 16 bytes in hex representation
      for ( ; iColIdx < 16; ++iColIdx)
      {
         (*this) << "   ";
      }
      
      // separate the hex dump from the ASCII dump with an additional space
      (*this) << ' ';
      
      // loop to print out each of the at most 16 bytes as its ASCII
      // representation if it is a printable character
      for (iColIdx = 0, q = p;
           iColIdx < 16 && (size_t) (iLineIdx + iColIdx) < nLenData;
           ++iColIdx, ++q)
      {
         if (isprint ((int) (unsigned) *q))
         {
            (*this) << (char) *q;
         }
         else
         {
            (*this) << '.';
         }
      }
      
      // if the line was not complete print out dots for the remaining bytes
      for ( ; iColIdx < 16; ++iColIdx)
      {
         (*this) << '.';
      }
      
      // finally close the current line
      this->newl ();
   }
   
   // finally restore the stream settings
   fill (c);
   flags (f);
   
} // CFormOutStream::hexdump





/**
 * Print out an array of bytes as a byte sequence.
 */

void CFormOutStream::byte_sequence
   (const void *pData,
    size_t      nLenData)
{
   if (! pData || nLenData == 0)
   {
      (*this) << "<Empty>";
      this->newl ();
      return;
   }

   // prepare the stream for the following operation
   char c = fill ();
   fmtflags f = flags ();
   fill ('0');
   setf (std::ios::hex | std::ios::right,
         std::ios::basefield | std::ios::adjustfield);

   enum State_t
   {
      INIT, ASCII, BYTES
   };
   State_t              state;
   State_t              newState;
   const unsigned char *p = (const unsigned char *) pData;
   
   // loop until all bytes are processed
   for (state = INIT; nLenData > 0; ++p, --nLenData)
   {
      if (isascii (*p) && isprint (*p))
      {
         newState = ASCII;
      }
      else
      {
         newState = BYTES;
      }
      
      if (state != newState)
      {
         switch (state)
         {
            case ASCII: (*this) << "' "; break;
            case BYTES: (*this) << "> "; break;
            default:    break;
         }
         switch (newState)
         {
            case ASCII: (*this) << "'"; break;
            case BYTES: (*this) << "<"; break;
            default:    break;
         }
      }
      else if (state == BYTES)
      {
         (*this) << " ";
      }
      
      state = newState;
      
      switch (state)
      {
         case ASCII: (*this) << *p; break;
         default:
         case BYTES: (*this) << std::setw (2) << (unsigned) *p; break;
      }
   }
   switch (state)
   {
      case ASCII: (*this) << "'"; break;
      case BYTES: (*this) << ">"; break;
      default:    break;
   }
   this->newl ();

   // finally restore the stream settings
   fill (c);
   flags (f);
   
} // CFormOutStream::byte_sequence





/**
 * Find the separation position in a line string.
 */

size_t CFormOutStream::find_separator_pos
   (const std::string &line,
    size_t             nMargin)
{
   // if the line completely fits into the margins: just return the complete
   // size
   if (nMargin >= line.length ())
   {
      return (line.length ());
   }

   // initialize our index
   int i = nMargin;
   
   // if we are in the middle of a word: find the start of whitespace
   while (i >= 0 && ! isspace (line [i]))
   {
      --i;
   }
   if (i < 0)
   {
      // we walked (reverse) behind the start of the line --> the current word
      // starts at the beginning of the line and continues behind the margin,
      // find the end of the word or the end of the line
      for (i = (int) nMargin;
           (size_t) i < line.length () && ! isspace (line [i]);
           ++i)
      {
         ;
      }
      return ((size_t) i);
   }
   
   // now we should be in a whitespace run (or before the line start) --> find
   // the end of the previous word
   while (i >= 0 && isspace (line [i]))
   {
      --i;
   }
   if (i < 0)
   {
      // we walked (reverse) behind the start of the line --> the line starts
      // with space and the first word crosses the margin, the separation
      // position is the first column
      return (0);
   }
   
   // now we found the end of the word to cut off behind (i points to it)
   return ((size_t) (i + 1));
} // CFormOutStream::find_separator_pos





/**
 * Write out a string line to the current output stream.
 */

void CFormOutStream::write_string
   (const std::string &line)
{
   if (m_ofs.is_open ())
   {
      m_ofs << std::setw (m_uLeftMargin) << "" << line << std::endl;
   }
   else
   {
      std::cout << std::setw (m_uLeftMargin) << "" << line << std::endl;
   }
   
} // CFormOutStream::write_string





// === Implementation of public functions ================================





/**
 * A function to call CFormOutStream::newl() as a stream manipulator.
 */

std::ostream &newl
   (std::ostream &ros)
{
   try
   {
      return ((dynamic_cast<CFormOutStream &>(ros)).newl ());
   }
   catch (std::bad_cast)
   {
      return std::endl (ros);
   }
} // newl





// === Implementation of private functions ===============================
