
/****************************************************************************** 
 * 
 *  file:  StdOutput.h
 * 
 *  Copyright (c) 2004, Michael E. Smoot
 *  All rights reverved.
 * 
 *  See the file COPYING in the top directory of this distribution for
 *  more information.
 *  
 *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.  
 *  
 *****************************************************************************/ 

#ifndef DtCmdLine_STDCMDLINEOUTPUT_H
#define DtCmdLine_STDCMDLINEOUTPUT_H

//! \file cmdStdOutput.h
//! \ingroup cmdLine
//! \brief  This file declares the class DtCmdLine::StdOutput

#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>
#include <vlutil/vlPrint.h>
#include <vlutil/vlProcessControl.h>

#include "cmdLineInterface.h"
#include "cmdLineOutput.h"
#include "cmdXorHandler.h"
#include "cmdArg.h"

#ifdef DtUSE_UTILITIES_NAMESPACE
namespace DtUSE_UTILITIES_NAMESPACE
{
#endif

   namespace DtCmdLine {

      /**
       * A class that isolates any output from the CmdLine object so that it
       * may be easily modified.
       */
      //! \ingroup cmdLine
      class StdOutput : public CmdLineOutput
      {

      public:

         virtual ~StdOutput() { }

         /**
          * Prints the usage to stdout.  Can be overridden to 
          * produce alternative behavior.
          * \param c - The CmdLine object the output is generated for. 
          */
         virtual void usage(CmdLineInterface& c);

         /**
          * Prints the version to stdout. Can be overridden 
          * to produce alternative behavior.
          * \param c - The CmdLine object the output is generated for. 
          */
         virtual void version(CmdLineInterface& c);

         /**
          * Prints (to stderr) an error message, short usage 
          * Can be overridden to produce alternative behavior.
          * \param c - The CmdLine object the output is generated for. 
          * \param e - The ArgException that caused the failure. 
          */
         virtual void failure(CmdLineInterface& c, 
                              ArgException& e );

      protected:

         /**
          * Writes a brief usage message with short args.
          * \param c - The CmdLine object the output is generated for. 
          * \param os - The stream to write the message to.
          */
         void _shortUsage( CmdLineInterface& c, std::ostream& os ) const;

         /**
          * Writes a longer usage message with long and short args, 
          * provides descriptions and prints message.
          * \param c - The CmdLine object the output is generated for. 
          * \param os - The stream to write the message to.
          */
         void _longUsage( CmdLineInterface& c, std::ostream& os ) const;

         /**
          * This function inserts line breaks and indents long strings 
          * according the  params input. It will only break lines at spaces, 
          * commas and pipes.
          * \param os - The stream to be printed to.
          * \param s - The string to be printed.
          * \param maxWidth - The maxWidth allowed for the output line. 
          * \param indentSpaces - The number of spaces to indent the first line. 
          * \param secondLineOffset - The number of spaces to indent the second
          * and all subsequent lines in addition to indentSpaces.
          */
         void spacePrint( std::ostream& os, 
                          const std::string& s, 
                          int maxWidth, 
                          int indentSpaces, 
                          int secondLineOffset ) const;

      };


      inline void StdOutput::version(CmdLineInterface& _cmd) 
      {
         std::string progName = _cmd.getProgramName();
         std::string version = _cmd.getVersion();

         int oldNotifyLevel = DtNotifyLevel;
         DtNotifyLevel = 3;
         DtInfo 
            << '\n' << progName << "  version: " 
            << version << "\n\n";
         DtNotifyLevel = oldNotifyLevel;
      }

      inline void StdOutput::usage(CmdLineInterface& _cmd ) 
      {
         int oldNotifyLevel = DtNotifyLevel;
         DtNotifyLevel = 3;
         DtInfo << '\n' << "USAGE: " << '\n' << '\n'; 

         _shortUsage( _cmd, DtInfo );

         DtInfo << '\n' << "Where: " << '\n';

         _longUsage( _cmd, DtInfo );

         DtInfo << std::endl; 
         DtNotifyLevel = oldNotifyLevel;

      }

      inline void StdOutput::failure( CmdLineInterface& _cmd, ArgException& e ) 
      {
         //! Make sure this gets printed out, no matter what the notify level
         int oldNotifyLevel = DtNotifyLevel;
         DtNotifyLevel = 4;
         std::string progName = _cmd.getProgramName();

         DtWarn 
            << "PARSE ERROR: " << e.argId() << '\n'
            << "             " << e.error() << "\n\n"
            << "Brief USAGE: " << '\n';

         _shortUsage( _cmd, DtWarn );	

         DtWarn 
            << '\n' << "For complete USAGE and HELP type: " 
            << '\n' << "   " << progName << " --help" 
            << '\n' << std::endl;
         DtNotifyLevel = oldNotifyLevel;

      }

      inline void StdOutput::_shortUsage( CmdLineInterface& _cmd, 
                                          std::ostream& os ) const
      {
         std::list<Arg*> argList = _cmd.getArgList();
         std::string progName = _cmd.getProgramName();
         XorHandler xorHandler = _cmd.getXorHandler();
         std::vector< std::vector<Arg*> > xorList = xorHandler.getXorList();

         std::string s = progName + " ";

         //! first the xor
         for ( int i = 0; (unsigned int)i < xorList.size(); i++ )
         {
            s += " {";
            for ( ArgVectorIterator it = xorList[i].begin(); 
                  it != xorList[i].end(); it++ )
               s += (*it)->shortID() + "|";

            s[s.length()-1] = '}';
         }

         //! then the rest
         for (ArgListIterator it = argList.begin(); it != argList.end(); it++)
            if ( !xorHandler.contains( (*it) ) )
               s += " " + (*it)->shortID();

         //! if the program name is too long, then adjust the second line offset 
         int secondLineOffset = (int)(progName.length()) + 2;
         if ( secondLineOffset > 80/2 )
            secondLineOffset = (int)(80/2);

         spacePrint( os, s, 80, 3, secondLineOffset );
      }

      inline void StdOutput::_longUsage( CmdLineInterface& _cmd, 
                                         std::ostream& os ) const
      {
         std::list<Arg*> argList = _cmd.getArgList();
         std::string message = _cmd.getMessage();
         XorHandler xorHandler = _cmd.getXorHandler();
         std::vector< std::vector<Arg*> > xorList = xorHandler.getXorList();

         //! first the xor 
         for ( int i = 0; (unsigned int)i < xorList.size(); i++ )
         {
            for ( ArgVectorIterator it = xorList[i].begin(); 
                  it != xorList[i].end(); 
                  it++ )
            {
               spacePrint( os, (*it)->longID(), 80, 3, 3 );
               spacePrint( os, (*it)->getDescription(), 80, 12, 0 );

               if ( it+1 != xorList[i].end() )
                  spacePrint(os, "-- OR --", 80, 25, 0);
            }
         }

         //! then the rest
         for (ArgListIterator it = argList.begin(); it != argList.end(); it++)
         {
            if ( !xorHandler.contains( (*it) ) )
            {
               spacePrint( os, (*it)->longID(), 80, 3, 3 ); 
               spacePrint( os, (*it)->getDescription(), 80, 12, 0 ); 
            }
         }
         os << '\n';
         spacePrint( os, message, 80, 3, 0 );
      }

      inline void StdOutput::spacePrint( std::ostream& os, 
                                         const std::string& s, 
                                         int maxWidth, 
                                         int indentSpaces, 
                                         int secondLineOffset ) const
      {
         int len = (int)(s.length());

         if ( (len + indentSpaces > maxWidth) && maxWidth > 0 )
         {
            int allowedLen = maxWidth - indentSpaces;
            int start = 0;
            while ( start < len )
            {
               //! find the substring length
#ifdef _WIN32
               int stringLen = ( len - start < allowedLen ? len - start : allowedLen);
#else
               int stringLen = std::min( len - start, allowedLen );
#endif

               //! trim the length so it doesn't end in middle of a word
               if ( stringLen == allowedLen )
                  while ( stringLen >= 0 &&
                          s[stringLen+start] != ' ' && 
                          s[stringLen+start] != ',' &&
                          s[stringLen+start] != '|')
                     stringLen--;

               //! ok, the word is longer than the line, so just split in the 
               //! wherever the line ends
               if ( stringLen <= 0 )
                  stringLen = allowedLen;

               //! check for newlines
               int i  = 0;
               for (i = 0; i < stringLen; i++ )
                  if ( s[start+i] == '\n' )
                     stringLen = i+1;

               //! print the indent	
               for (i = 0; i < indentSpaces; i++ )
                  os << " ";

               if ( start == 0 )
               {
                  //! handle second line offsets
                  indentSpaces += secondLineOffset;

                  //! adjust allowed len
                  allowedLen -= secondLineOffset;
               }

               os << s.substr(start,stringLen) << std::endl;

               //! so we don't start a line with a space
               while ( s[stringLen+start] == ' ' && start < len )
                  start++;

               start += stringLen;
            }
         }
         else
         {
            for ( int i = 0; i < indentSpaces; i++ )
               os << " ";
            os << s << std::endl;
         }
      }

   } //namespace DtCmdLine

#ifdef DtUSE_UTILITIES_NAMESPACE
}     //! end of namespace DtUSE_VLUTIL_NAMESPACE {
#endif

#endif 
