//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000-2018 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
//
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
//
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
//
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
// debug.cpp
#include "stdcore.h"
#include "debug.h"
#include "errorutil.h"


#ifdef DEBUG

#    ifndef va_start
#        include <cstdarg>
#    endif
#include <cwchar>
#include <fstream>
#include <cstdio>

int           cDebug::mDebugLevel(10);
uint32        cDebug::mOutMask(cDebug::OUT_TRACE);
std::ofstream cDebug::logfile;
//mDebugLevel default == 10, mOutMask default == OUT_TRACE.

///////////////////////////////////////////////////////////////////////////////
//  Constructors and Destructor
cDebug::cDebug(const char* label)
{
    int cnt = strlen(label);
    if (cnt > cDebug::MAX_LABEL)
        cnt = cDebug::MAX_LABEL - 1;

    memcpy(mLabel, label, cnt);
    mLabel[cnt] = '\0';
}

cDebug::cDebug(const cDebug& rhs)
{
    strncpy(mLabel, rhs.mLabel, MAX_LABEL);
}

cDebug::~cDebug()
{
    if (logfile)
        logfile.close();
}
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// Trace -- Outputs a format string only if the passed integer value is <=
// the "global debug level" (indicated by mDebugLevel).
///////////////////////////////////////////////////////////////////////////////
void cDebug::Trace(int levelNum, const char* format, ...)
{
    if (levelNum > mDebugLevel)
        return;
    // create the output buffer
    va_list args;
    va_start(args, format);
    DoTrace(format, args);
    va_end(args);
}


///////////////////////////////////////////////////////////////////////////////
//  DoTrace()
//      internal helper function -- does the actual printing to logfile,
//      console, etc...
///////////////////////////////////////////////////////////////////////////////

void cDebug::DoTrace(const char* format, va_list& args)
{
    size_t guard1 = 0xBABABABA;
    char   out[2048];
    size_t guard2 = 0xBABABABA;

    vsnprintf(out, 2048, format, args);

    ASSERT(guard1 == 0xBABABABA && guard2 == 0xBABABABA); // string was too long
    ASSERT(strlen(out) < 1024);

    std::ostringstream ostr;
    ostr.setf(std::ios::left);
    ostr.width(40);
    ostr << mLabel;
    ostr.width(0);
    ostr << out;


    if ((mOutMask & OUT_STDOUT) != 0)
    {
        std::cout << ostr.str().c_str();
        std::cout.flush();
    }

    //
    //make it output to log file!
    //
    if ((mOutMask & OUT_FILE) != 0)
    {
        // the logfile is narrow chars only...
        logfile.setf(std::ios::left);
        logfile.width(40);
        logfile << mLabel;
        logfile.width(0);
        logfile << out;
        logfile.flush();
    }
}
#    ifdef DEBUG

//
// wrappers around Trace() that requires less typing
//      TODO: this is quick and dirty, but lets me check in all these files right away.  --ghk
//

void cDebug::TraceAlways(const char* format, ...)
{
    if (D_ALWAYS > mDebugLevel)
        return;

    // fill up arglist, and pass to printing routine
    va_list args;
    va_start(args, format);
    DoTrace(format, args);
    va_end(args);
}

void cDebug::TraceError(const char* format, ...)
{
    if (D_ERROR > mDebugLevel)
        return;

    // fill up arglist, and pass to printing routine
    va_list args;
    va_start(args, format);
    DoTrace(format, args);
    va_end(args);
}

void cDebug::TraceWarning(const char* format, ...)
{
    if (D_WARNING > mDebugLevel)
        return;

    // fill up arglist, and pass to printing routine
    va_list args;
    va_start(args, format);
    DoTrace(format, args);
    va_end(args);
}

void cDebug::TraceDebug(const char* format, ...)
{
    if (D_DEBUG > mDebugLevel)
        return;

    // fill up arglist, and pass to printing routine
    va_list args;
    va_start(args, format);
    DoTrace(format, args);
    va_end(args);
}

void cDebug::TraceDetail(const char* format, ...)
{
    if (D_DETAIL > mDebugLevel)
        return;

    // fill up arglist, and pass to printing routine
    va_list args;
    va_start(args, format);
    DoTrace(format, args);
    va_end(args);
}

void cDebug::TraceNever(const char* format, ...)
{
    if (D_NEVER > mDebugLevel)
        return;

    // fill up arglist, and pass to printing routine
    va_list args;
    va_start(args, format);
    DoTrace(format, args);
    va_end(args);
}

void cDebug::TraceVaArgs(int iDebugLevel, const char* format, va_list& args)
{
    if (iDebugLevel <= mDebugLevel)
        DoTrace(format, args);
}
#    endif // DEBUG

///////////////////////////////////////////////////////////////////////////////
//  AddOutTarget -- Attempts to add a new target for trace/debug output.
//  FAILS ONLY IF caller attempts to SET OUT_FILE via this function.
///////////////////////////////////////////////////////////////////////////////
bool cDebug::AddOutTarget(OutTarget target)
{
    if (target == OUT_STDOUT)
        mOutMask |= OUT_STDOUT;
    if (target == OUT_TRACE)
        mOutMask |= OUT_TRACE;
    if (target == OUT_FILE)
    {
        mOutMask |= OUT_FILE;
        return false;
    }
    return true;
}

///////////////////////////////////////////////////////////////////////////////
// RemoveOutTarget -- Masks out from mOutMask the value passed.
///////////////////////////////////////////////////////////////////////////////
bool cDebug::RemoveOutTarget(OutTarget target)
{
    if (!HasOutTarget(target))
        return true;
    if (target == OUT_STDOUT)
        mOutMask ^= OUT_STDOUT;
    if (target == OUT_TRACE)
        mOutMask ^= OUT_TRACE;
    if (target == OUT_FILE)
        mOutMask ^= OUT_FILE;
    return true;
}

///////////////////////////////////////////////////////////////////////////////
// HasOutTarget -- Takes target - target must represent a single flagged bit
///////////////////////////////////////////////////////////////////////////////
bool cDebug::HasOutTarget(OutTarget target)
{

    if (target == OUT_STDOUT)
        return ((mOutMask & OUT_STDOUT) != 0);
    else if (target == OUT_TRACE)
        return ((mOutMask & OUT_TRACE) != 0);
    else if (target == OUT_FILE)
        return ((mOutMask & OUT_FILE) != 0);
    else //ambiguous input, or too many bits set in target
        return false;
}

///////////////////////////////////////////////////////////////////////////////
// SetOutputFile -- Attempts to set the output file for Logfile member to
// the string passed in.
///////////////////////////////////////////////////////////////////////////////
bool cDebug::SetOutputFile(const char* filename)
{
    // TODO -- make sure this does the right thing if a log file is
    // already open!
    // TODO -- make this work with wide chars
    if (!logfile)
        logfile.open(filename, std::ios_base::out | std::ios_base::ate | std::ios_base::app);
    else
        logfile.setf(std::ios_base::hex, std::ios_base::basefield);
    //make sure info. will not be clobbered.

    //Should be open now- if not, abort.
    if (!logfile)
    {
        mOutMask ^= OUT_FILE;
        return false;
    }
    else
        mOutMask |= OUT_FILE;
    return true;
}

//////////////////////////////////////////////////////////////////////////////
// DebugOut -- Works just like TRACE. note: there is an internal buffer size
// of 1024; traces larger than that will have unpredictable results.
//////////////////////////////////////////////////////////////////////////////
void cDebug::DebugOut(const char* lpOutputString, ...)
{
    char buf[2048];
    // create the output buffer
    va_list args;
    va_start(args, lpOutputString);
    vsnprintf(buf, 2048, lpOutputString, args);
    va_end(args);

#    ifdef DEBUG
    TCERR << buf;
#    endif //_DEBUG

    TCOUT.flush();
}
#endif // DEBUG

//////////////////////////////////////////////////////////////////////////////////
// ASSERT macro support function
//////////////////////////////////////////////////////////////////////////////////
