/**
 * @file config.cc
 *
 * Config - Configuration data handling for the capi tracer.
 *
 * Copyright: 2002-2003 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: config.cc,v 1.1.2.1 2006/02/28 21:35:41 thomas Exp $
 * Project  CAPI for BSD
 * Target   capitrace - Tracing CAPI calls and messages
 * @date    16.09.2002
 * @author  "Thomas Wintergerst" <twinterg@gmx.de>
 *
 * 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.
 */

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

// System includes
#include <stdexcept>
#include <iostream>

// Import includes

#define __CONFIG__

// Local includes
#ifndef __CONFIG_H
#  include "config.h"
#endif

using namespace thwutil;





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





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





/// The global config object.
CConfig e_oConfig;





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





// === Definitions for class members =====================================





// --- Class for configuration data handling -----------------------------

/**
 * Constructor.
 */

CConfig::CConfig (void):
   m_oCmdLineEvaluator (),
   m_oCmdOptInterpretTraceFile (m_oCmdLineEvaluator, "i",
                                CCmdOptBase::UNIQUE_OPTION, NULL),
   m_oCmdOptAppTracer (m_oCmdLineEvaluator, "a",
                       CCmdOptBase::UNIQUE_OPTION, NULL),
   m_oCmdOptCtlrTracer (m_oCmdLineEvaluator, "c",
                        CCmdOptBase::HAS_PARAMETER,
                        NULL),
   m_oCmdOptIncludeFctCalls (m_oCmdLineEvaluator, "tf",
                             CCmdOptBase::HAS_PARAMETER |
                                CCmdOptBase::PARAM_OPTIONAL |
                                CCmdOptBase::IGNORE_PARAM_CASE,
                             &(m_nextTracerConfig.fIncludeFctCalls)),
   m_oCmdOptIncludeMessages (m_oCmdLineEvaluator, "tm",
                             CCmdOptBase::HAS_PARAMETER |
                                CCmdOptBase::PARAM_OPTIONAL |
                                CCmdOptBase::IGNORE_PARAM_CASE,
                             &(m_nextTracerConfig.fIncludeMessages)),
   m_oCmdOptIncludeDataB3Msgs (m_oCmdLineEvaluator, "td",
                               CCmdOptBase::HAS_PARAMETER |
                                  CCmdOptBase::PARAM_OPTIONAL |
                                  CCmdOptBase::IGNORE_PARAM_CASE,
                               &m_fDataB3MsgOutput, true),
   m_oCmdOptIncludeDataB3Blocks (m_oCmdLineEvaluator, "tb",
                                 CCmdOptBase::HAS_PARAMETER |
                                    CCmdOptBase::PARAM_OPTIONAL |
                                    CCmdOptBase::IGNORE_PARAM_CASE,
                                 &m_fDataB3BlockOutput, true),
   m_oCmdOptDataB3BlockLen (m_oCmdLineEvaluator, "bl",
                            CCmdOptBase::HAS_PARAMETER,
                            &m_uDataB3BlockLen, UINT_MAX),
   m_oCmdOptHexOutput (m_oCmdLineEvaluator, "x",
                       CCmdOptBase::HAS_PARAMETER |
                          CCmdOptBase::PARAM_OPTIONAL |
                          CCmdOptBase::IGNORE_PARAM_CASE,
                       &m_fHexOutput, true),
   m_oCmdOptOutputFileName (m_oCmdLineEvaluator, "o",
                            CCmdOptBase::HAS_PARAMETER,
                            &m_strOutputFileName),
   m_oCmdOptHelp1 (m_oCmdLineEvaluator, "h",
                   CCmdOptBase::TERMINATE_EVALUATION, NULL),
   m_oCmdOptHelp2 (m_oCmdLineEvaluator, "?",
                   CCmdOptBase::TERMINATE_EVALUATION, NULL)
{
   // set the default program name, will be overwritten later in initialization
   m_strProgName = "capitrace";
   
   // the default configuration for the interpreter
   m_fDataB3MsgOutput   = true;         // print message if contained in trace
   m_fDataB3BlockOutput = true;         // print data block if cont. in trace
   m_uDataB3BlockLen    = UINT_MAX;     // print all data in trace
   m_fHexOutput         = true;         // additional hex. output by default
   
   // the default configuration for the tracers
   m_nextTracerConfig.fIncludeFctCalls     = true;
   m_nextTracerConfig.fIncludeMessages     = true;
   m_nextTracerConfig.fIncludeDataB3Msgs   = false;
   m_nextTracerConfig.fIncludeDataB3Blocks = false;
   m_nextTracerConfig.nDataB3BlockLen      = 48;
   
} // CConfig::CConfig





/**
 * Destructor.
 */

CConfig::~CConfig (void)
{
   // nothing to do
   
} // CConfig::~CConfig





/**
 * Perform general initialization.
 *
 * As this class is designed to create a static object, the constructor
 * will not get enough information for initializing the object. This will
 * thus be done by calling this member function.
 *
 * The main task is to set all internal member variables to reasonable
 * defaults. In addition the short program name is determined from the
 * argument specified. It is expected to be a file name, possibly
 * including some path specification. The basename of this file name will
 * be set as the program name.
 *
 * @param pszProgFileName       I: The file name of the current program, maybe
 *                                 including a path specification.
 *
 * @retval None.
 *
 * @throws std::invalid_argument
 *                              The argument is the NULL pointer or the empty
 *                              string.
 */

void CConfig::Init
   (const char *pszProgFileName)
{
   const char *p;
   
   // check for valid parameters
   if (! pszProgFileName || ! *pszProgFileName)
   {
      throw std::invalid_argument
               ("CConfig::Init(): Invalid program file name");
   }
   
   // find the basename in the file name
   p = strrchr (pszProgFileName, '/');
   if (p)
   {
      ++p;
   }
   else
   {
      p = pszProgFileName;
   }
   
   // set the internal short program name to the basename found
   m_strProgName = p;

} // CConfig::Init





/**
 * Evalutate the command line.
 *
 * The command line is evaluated in runs up to a non-option argument. This
 * ending argument will specify a tracer file, regardless of the task to
 * execute. Up to this argument any option is allowed, but the three
 * options for the task specification are special.
 *
 * Trace interpretation and trace creation are distinct operations. A
 * single program run may not perform both actions. In addition to this an
 * application tracer may only be started once.
 *
 * When starting multiple tracers, the settings for the last one created
 * will be inherited by the next one. If this is not wanted, it may be
 * changed by specifying the respective options again with different
 * parameters. Every option representing a simple switch accepts an
 * optional parameter that may set the options value explicitly to "true"
 * or "false.
 *
 * @param iArgc                 I: The number of command line arguments in
 *                                 papszArgv.
 * @param papszArgv             I: The command line arguments as an array of
 *                                 pointers to character vectors.
 *
 * @retval TASK_UNKNOWN         No main action specified, i.e. neither "-i",
 *                              "-a" nor "-c#" was specified on the command
 *                              line.
 * @retval TASK_ERROR           An error occurred evaluating the command line.
 * @retval TASK_USAGE           Only the help text shall be printed.
 * @retval TASK_START_TRACER    The task is to start one or more tracers.
 * @retval TASK_INTERPRET_TRACE The task is to interpret a formerly created
 *                              CAPI trace file.
 */

CConfig::TaskToExecute_t CConfig::EvalCommandLine
   (int    iArgc,
    char **papszArgv)
{
   int                                    iNextArg;
   CCmdLineEvaluator::CmdLineEvalResult_t resEval;

   // loop until all command line arguments are handled
   iNextArg = 1;
   while (iNextArg < iArgc)
   {
      // Reset the options for starting a controller or an application trace.
      // These actions may occur multiple times on the command line (-a only
      // once but in combination with -c). The other actions (in fact only -i)
      // may only be specified once.
      m_oCmdOptAppTracer.ResetValue ();
      m_oCmdOptCtlrTracer.ResetValue ();
      
      // evaluate the next run up to the first non-option argument
      resEval = m_oCmdLineEvaluator.EvalCommandLine
                   (iArgc, papszArgv, iNextArg, &iNextArg);
      
      // distinguish between the possible results
      switch (resEval)
      {
         case CCmdLineEvaluator::CLE_ERR_DUPLICATE_OPTION:
            // duplicate option error detected: abort
            std::cerr << GetProgName ()
                      << ": Options \"-i\" and \"-a\" may only be specified once"
                      << std::endl;
            return (TASK_ERROR);

         default:
            // other error detected: abort
            std::cerr << GetProgName ()
                      << ": Error in command line evaluation at \""
                      << (papszArgv [iNextArg] ? papszArgv [iNextArg] : "(NULL)")
                      << "\": " << m_oCmdLineEvaluator.ResultMsg (resEval)
                      << std::endl;
            return (TASK_ERROR);

         case CCmdLineEvaluator::CLE_EVALUATION_TERMINATED:
            // evaluation terminated: must be help option, return to caller
            return (TASK_USAGE);

         case CCmdLineEvaluator::CLE_OK:
            // no error, continue with checking the action specified
            break;
      }

      // if action is to interpret a trace:
      if (m_oCmdOptInterpretTraceFile.WasFound ())
      {
         // check for previously specified tracer start
         if (! m_tracerConfigList.empty ())
         {
            std::cerr << GetProgName ()
                      << ": Trace interpretation may not be executed in conjunction "
                      << "with creating a CAPI trace"
                      << std::endl;
            return (TASK_ERROR);
         }

         // check for available next argument for the trace file
         if (iNextArg >= iArgc ||
             ! papszArgv [iNextArg] ||
             ! papszArgv [iNextArg] [0])
         {
            std::cerr << GetProgName ()
                      << ": No trace file to interpret"
                      << std::endl;
            return (TASK_ERROR);
         }

         // transfer all options recognized to their respective configuration
         // variable location
         m_oCmdLineEvaluator.TakeCommandLineOptions (NULL);
         
         // set the next argument as the trace file name to interpret
         m_strInterpretSourceFileName = papszArgv [iNextArg];
         ++iNextArg;
         
         // continue with evaluating next run if there are more arguments on the
         // command line
      }
      // else if action is to start a tracer:
      else if (m_oCmdOptAppTracer.WasFound () ||
               m_oCmdOptCtlrTracer.WasFound ())
      {
         // check for previously specified trace interpretation
         if (m_oCmdOptInterpretTraceFile.WasFound ())
         {
            std::cerr << GetProgName ()
                      << ": CAPI trace creation may not be executed in conjunction "
                      << "with interpreting a trace file"
                      << std::endl;
            return (TASK_ERROR);
         }

         // check if both an application tracer and a controller tracer shall be
         // started
         if (m_oCmdOptAppTracer.WasFound () &&
             m_oCmdOptCtlrTracer.WasFound ())
         {
            std::cerr << GetProgName ()
                      << ": Only either an application or a controller trace may be "
                      << "written to a single file"
                      << std::endl;
            return (TASK_ERROR);
         }
         
         // check for available next argument for the trace file
         if (iNextArg >= iArgc ||
             ! papszArgv [iNextArg] ||
             ! papszArgv [iNextArg] [0])
         {
            std::cerr << GetProgName ()
                      << ": Trace file missing to write to"
                      << std::endl;
            return (TASK_ERROR);
         }

         // transfer all options recognized to their respective configuration
         // variable location
         m_oCmdLineEvaluator.TakeCommandLineOptions (NULL);
         
         // create a new tracer configuration object and fill in the current
         // configuration
         if (m_oCmdOptAppTracer.WasFound ())
         {
            m_nextTracerConfig.fIsCtlrTracer = false;
         }
         else
         {
            m_nextTracerConfig.fIsCtlrTracer = true;
            m_nextTracerConfig.uCtlrNum = m_oCmdOptCtlrTracer.GetValue ();
         }
         if (m_oCmdOptIncludeDataB3Msgs.WasFound ())
         {
            m_nextTracerConfig.fIncludeDataB3Msgs =
               m_oCmdOptIncludeDataB3Msgs.GetValue ();
         }
         if (m_oCmdOptIncludeDataB3Blocks.WasFound ())
         {
            m_nextTracerConfig.fIncludeDataB3Blocks =
               m_oCmdOptIncludeDataB3Blocks.GetValue ();
         }
         if (m_oCmdOptDataB3BlockLen.WasFound ())
         {
            m_nextTracerConfig.nDataB3BlockLen =
               (size_t) (m_oCmdOptDataB3BlockLen.GetValue ());
         }
         m_nextTracerConfig.strTraceFile = papszArgv [iNextArg];
         m_tracerConfigList.push_back (m_nextTracerConfig);
         ++iNextArg;
         
         // continue with evaluating next run if there are more arguments on the
         // command line
      }
      // else: no action specified, abort
      else
      {
         std::cerr << GetProgName ()
                   << ": Neither \"-i\", \"-a\" nor \"-c#\" specified, no task to "
                   << "execute"
                   << std::endl;
         return (TASK_UNKNOWN);
      }
   }
   // if this point is reached, we can assume either a valid configuration or
   // there was not task specified (e.g. empty command line)
   
   // check for task to return to caller
   if (! m_tracerConfigList.empty ())
   {
      return (TASK_START_TRACER);
   }
   if (m_oCmdOptInterpretTraceFile.WasFound ())
   {
      return (TASK_INTERPRET_TRACE);
   }
   return (TASK_UNKNOWN);
} // CConfig::EvalCommandLine





/**
 * Print hints for using this program.
 */

void CConfig::Usage (void)
{
   std::cout << std::endl;
   
   std::cout << m_strProgName << " - CAPI tracing program" << std::endl;
   std::cout << std::endl;
   std::cout << "Usage:" << std::endl;
   std::cout << '\t' << m_strProgName
             << " [-h|-?]" << std::endl;
   std::cout << '\t' << m_strProgName
             << " [options] -i [options] <trace file>" << std::endl;
   std::cout << '\t' << m_strProgName
             << " [options] {-a|-c#} [options] <trace file> ..." << std::endl;
   std::cout << std::endl;
   std::cout << "\t-h|-?     Print out this help text." << std::endl;
   std::cout << "\t-i        Interpret a CAPI trace file previously created,"
             << std::endl;
   std::cout << "\t          the output goes to stdout by default."
             << std::endl;
   std::cout << "\t-a        Create an application CAPI trace in file <trace file>."
             << std::endl;
   std::cout << "\t-c#       Create a CAPI trace for controller # in file <trace file>."
             << std::endl;
   std::cout << std::endl;
   std::cout << "\tMuitiple traces may be created by repeating \"-a\" or \"-c#\" and"
             << std::endl;
   std::cout << "\t\"<trace file>\" on the command line." << std::endl;
   std::cout << std::endl;

   std::cout << "Options supported:" << std::endl;

   std::cout << "\t-tf[+|-]  Include function calls (+, default) or not (-)"
             << std::endl;
   std::cout << "\t-tm[+|-]  Include general CAPI messages (+, default) or not (-)"
             << std::endl;
   std::cout << "\t-td[+|-]  Include Data-B3 CAPI messages (+) or not (-, default)"
             << std::endl;
   std::cout << "\t-tb[+|-]  Include Data-B3 blocks (+) or not (-, default)"
             << std::endl;
   std::cout << "\t-bl#      Maximum block length for Data-B3 blocks, default 48"
             << std::endl;
   std::cout << "\t-x[+|-]   Include hexdump of CAPI messages (+, default) or not (-)"
             << std::endl;
   std::cout << "\t-o<file name>" << std::endl;
   std::cout << "\t\t  Direct interpretation output to a file" << std::endl;

   std::cout << std::endl;
   
} // CConfig::Usage





/**
 * General configuration: Get program name.
 */

const char *CConfig::GetProgName (void)
{
   return (m_strProgName.c_str ());
} // CConfig::GetProgName





/**
 * Interpreter configuration: Get the input file name.
 */

const std::string &CConfig::GetInterpretSourceFileName (void)
{
   return (m_strInterpretSourceFileName);
} // CConfig::GetInterpretSourceFileName





/**
 * Interpreter configuration: Get flag for Data-B3 message output.
 */

bool CConfig::GetInterpretDataB3Msgs (void)
{
   return (m_fDataB3MsgOutput);
} // CConfig::GetInterpretDataB3Msgs





/**
 * Interpreter configuration: Get flag for Data-B3 data block output.
 */

bool CConfig::GetInterpretDataB3Blocks (void)
{
   return (m_fDataB3MsgOutput && m_fDataB3BlockOutput);
} // CConfig::GetInterpretDataB3Blocks





/**
 * Interpreter configuration: Get maximum output length of Data-B3 data
 * blocks if output is enabled.
 */

size_t CConfig::GetInterpretDataB3BlockLen (void)
{
   return ((size_t) m_uDataB3BlockLen);
} // CConfig::GetInterpretDataB3BlockLen





/**
 * Interpreter configuration: Get flag for optional hexadecimal output of
 * the CAPI message.
 */

bool CConfig::GetInterpretHexOutput (void)
{
   return (m_fHexOutput);
} // CConfig::GetInterpretHexOutput





/**
 * Interpreter configuration: Get the optional output file name.
 */

const std::string &CConfig::GetInterpretOutputFileName (void)
{
   return (m_strOutputFileName);
} // CConfig::GetInterpretOutputFileName





/**
 * Tracer configuration: Get an iterator to the beginning of the list of
 * tracer specifications.
 */

const CConfig::CTracerConfigList::const_iterator CConfig::GetTracerConfigStartIterator (void)
{
   return (m_tracerConfigList.begin ());
} // CConfig::GetTracerConfigStartIterator





/**
 * Tracer configuration: Get an iterator to the end of the list of tracer
 * specifications.
 */

const CConfig::CTracerConfigList::const_iterator CConfig::GetTracerConfigEndIterator (void)
{
   return (m_tracerConfigList.end ());
} // CConfig::GetTracerConfigEndIterator





// === Definitions for public functions ==================================





// === Definitions for private functions =================================
