/*
 * Debug handling
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/**
 * \ingroup rwdebug
 * \page rwdebugoverview RwDebug  Overview
 *
 * (DEBUG BUILD ONLY.)
 *
 * This object represents a debug handler and stream. Error messages can be sent 
 * to this object and it will pass them onto the currently registered debug handler. 
 * A default handler is registered by default. This handler sends messages to a file 
 * called 'rwdebug.log'.
 * 
 * Developers should note that this API may degrade performance if too many messages
 * are being sent to the handler.
 *
*/

/****************************************************************************
 Includes
 */

#include <stdlib.h>
#include <string.h>

#include "batypes.h"
#include "balibtyp.h"

#include "bamemory.h"

/* Abstraction of string functionality - for unicode support */
#include "rwstring.h"

#include "osintf.h"

#include "baerr.h"
#include "badebug.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = "@@(#)$Id: badebug.c,v 1.65 2001/07/16 15:38:29 johns Exp $";
#endif /* (!defined(DOXYGEN)) */

/****************************************************************************
 Local Types
 */

/****************************************************************************
 Local (Static) Prototypes
 */
#ifdef RWDEBUG
static void     debugWriteHeader(void);
#endif /* RWDEBUG */

/****************************************************************************
 Local Defines
 */

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

/* The strings used in the debug error reporting are derived from the 
 * .def files
 */

#define RWECODE(a,b) RWSTRING(b),
const RwChar   *rw_errcomstr[] =
{
    RWSTRING("No Error"),
#include "errcom.def"
};

#undef RWECODE
#define RWECODE(a,b) RWSTRING(#a),
const RwChar   *rw_errcomcstr[] =
{
    RWSTRING("E_RW_NOERROR"),
#include "errcom.def"
};

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Debugging layer

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

#ifdef RWDEBUG

/*
 * The default system error handler. This function will write all debug
 * messages to the file rwdebug.log in the current directory
 */

static void
rwDebugDefaultHandler(RwDebugType type __RWUNUSED__, const RwChar * debugText)
{
    static const RwChar cr[] = RWSTRING("\n");

#if (0)
    RWFUNCTION(RWSTRING("rwDebugDefaultHandler"));
#endif /* (0) */

    if (!RWSRCGLOBAL(debugFile))
    {
        /* 
         * default handler not initialised
         */

        RWSRCGLOBAL(debugFile) = RwFopen(RWSTRING("rwdebug.log"),
                                         RWSTRING("at"));
        if (RWSRCGLOBAL(debugFile))
        {
            debugWriteHeader();
        }
        else
        {
            return;
        }
    }

    RwFwrite(debugText, (rwstrlen(debugText) * sizeof (RwChar)), 1,
             RWSRCGLOBAL(debugFile));
    RwFwrite(cr, (rwstrlen(cr) * sizeof (RwChar)), 1, RWSRCGLOBAL(debugFile));
    RwFflush(RWSRCGLOBAL(debugFile));

    return;
}

/****************************************************************************
 _rwDebugOpen

 On entry   :
 On exit    : TRUE on success
 */

RwBool
_rwDebugOpen(void)
{
#if (0)
    RWFUNCTION(RWSTRING("_rwDebugOpen"));
#endif /* (0) */

    /* Note the DEBUG macros are deliberately not used here */
    RWSRCGLOBAL(debugStackDepth) = 0;
    RWSRCGLOBAL(debugFunction) = rwDebugDefaultHandler;
    RWSRCGLOBAL(debugFile) = NULL;
    RWSRCGLOBAL(debugTrace) = FALSE;

    return (TRUE);
}

/****************************************************************************
 _rwDebugClose

 On entry   :
 On exit    : TRUE on success
 */

RwBool
_rwDebugClose(void)
{
#if (0)
    RWFUNCTION(RWSTRING("_rwDebugClose"));
#endif /* (0) */

    if (RWSRCGLOBAL(debugFile))
    {
        RwFclose(RWSRCGLOBAL(debugFile));
        RWSRCGLOBAL(debugFile) = NULL;
    }
    return TRUE;

}

/**
 * \ingroup rwdebug
 * \ref RwDebugSetTraceState is used to enable or disable the reporting 
 * of debug trace messages. Initially, the trace state is set to FALSE.
 *
 * This function is only meaningful when used in conjunction with the debug
 * version of the RenderWare libraries.
 *
 * \param state  A RwBool value equal to the new message reporting state:
 *
 *               \li TRUE - Enable debug trace message reporting.
 *               \li FALSE - Disable debug trace message reporting. 
 *
 * \return None.
 *
 * \see RwDebugSendMessage
 * \see RwDebugSetHandler
 * \see RwErrorGet
 *
 */
void
RwDebugSetTraceState(RwBool state)
{
    RWAPIFUNCTION(RWSTRING("RwDebugSetTraceState"));
    RWSRCGLOBAL(debugTrace) = state;
    RWRETURNVOID();
}

/**
 * \ingroup rwdebug
 * \ref RwDebugSetHandler is used to register a new handler for the
 * debug stream. A default system error handler is initially defined. The 
 * default handler writes all debug messages to the file rwdebug.log in the 
 * current directory. However, when a new handler is registered the previous 
 * output file is closed, so it’s necessary for the handler to open its own 
 * output file the first time it is called. 
 * 
 * Note it is possible to return to the default message handler by specifying
 * NULL for the handler pointer.
 *
 * This function is only meaningful when used in conjunction with the debug
 * version of the RenderWare libraries.
 *
 * \param handler  Pointer to the new debug message handler. Set to NULL to return 
 *       to the default handler.
 *
 * \return Returns pointer to the previously registered debug message handler
 *        if successful or NULL if there is an error.
 * 
 * \see RwDebugSendMessage
 * \see RwDebugSetTraceState
 * \see RwErrorGet
 *
 */
RwDebugHandler
RwDebugSetHandler(RwDebugHandler handler)
{
    RwDebugHandler  old = RWSRCGLOBAL(debugFunction);

    RWAPIFUNCTION(RWSTRING("RwDebugSetHandler"));

    if (handler)
    {
        /* New handler specified */
        if (handler != old)
        {
            /* It's really a new one */
            RWSRCGLOBAL(debugFunction) = handler;
            debugWriteHeader();

            if (old == rwDebugDefaultHandler)
            {
                /* clean up the defaults */
                if (RWSRCGLOBAL(debugFile))
                {
                    RwFclose(RWSRCGLOBAL(debugFile));
                    RWSRCGLOBAL(debugFile) = 0;
                }
            }
        }
    }
    else
    {
        /* NULL specified - reset to default */
        RwDebugSetHandler(rwDebugDefaultHandler);
    }
    RWRETURN(old);
}

/****************************************************************************
 *
 * This is for documentation purposes only, so must be '#if 0'ed out
 *
 */

#if (defined(DOXYGEN))

/**
 * \ingroup rwdebug
 * \ref RwDebugSendMessage is used to send a message to the currently
 * installed debug handler.
 *
 * This function is only meaningful when used in conjunction with the debug
 * version of the RenderWare libraries.
 *
 * \param type  A RwDebugType value equal to the message type:
 *              \li rwDEBUGASSERT  - Send an assert message. 
 *              \li rwDEBUGERROR   - Send an error message. 
 *              \li rwDEBUGMESSAGE - Send an informational message. 
 *              \li rwDEBUGTRACE   - Send a trace message.
 * 
 * \param fileName  Pointer to a string containing the name of the file the message
 *       originated from.
 * \param line  A RwInt32 value equal to the number of the line the message 
 *       originated from.
 * \param funcName  Pointer to a string containing the name of the function the 
 *       message originated from.
 * \param message  Pointer to a string containing the text of the message. 
 *
 * \return None.
 *
 * \see RwDebugSetHandler
 * \see RwDebugSetTraceState
 * \see RwErrorGet
 *
 */
void
RwDebugSendMessage(RwDebugType type,
                   const RwChar *fileName, const RwInt32 line,
                   const RwChar *funcName, const RwChar *message)
{
    RWAPIFUNCTION(RWSTRING("RwDebugSendMessage"));

    RWRETURNVOID(FALSE);
}

#endif /* 0 */

void
_rwDebugSendMessage(RwDebugType type,
                    const RwChar *fileName, const RwInt32 line,
                    const RwChar *funcName, const RwChar *message)
{
    const RwChar   *typeString = (const RwChar *)NULL;
    RwChar          buffer[512];

#if (0)
    RWFUNCTION(RWSTRING("_rwDebugSendMessage"));
#endif /* (0) */

    switch (type)
    {
        case rwNADEBUGTYPE:
            break;
        case rwDEBUGASSERT:
            typeString = RWSTRING("ASSERT");
            break;
        case rwDEBUGERROR:
            typeString = RWSTRING("ERROR");
            break;
        case rwDEBUGMESSAGE:
            typeString = RWSTRING("MESSAGE");
            break;
        case rwDEBUGTRACE:
            typeString = RWSTRING("TRACE");
            break;
        case rwDEBUGTYPEFORCEENUMSIZEINT:
            break;
    }

    if (!typeString)
    {
        /* not a valid message type */
        rwstrcpy(buffer, RWSTRING("Invalid Type"));
    }
    else
    {
        rwsprintf(buffer, RWSTRING("%s(%d): %s: %s: %s"), fileName,
                  line, typeString, funcName, message);
    }

    if (RWSRCGLOBAL(debugFunction))
    {
        RWSRCGLOBAL(debugFunction) (type, buffer);
    }
    return;
}

/*
 *  debugWriteHeader()
 *  Writes an introductory string to the top
 *  of the Error Stream when it's first opened.
 *  Details RenderWare implementation number, version number and time
 */

static void
debugWriteHeader(void)
{
    RwChar          buffer[256];
    RwChar          timeBuffer[256];

#if (0)
    RWFUNCTION(RWSTRING("debugWriteHeader"));
#endif /* (0) */

    if (!_rwgetcurrenttime(timeBuffer, sizeof (timeBuffer)))
    {
        /* We failed to get time, use a NUL string */
        timeBuffer[0] = '\0';
    }
    else
    {
        /* Remove newline that ctime in get current time appends */
        timeBuffer[rwstrlen(timeBuffer) - 1] = '\0';
    }

    /* Construct a header string */
    rwsprintf(buffer,
              RWSTRING("RenderWare(TM) V%d.%d.%d(%s) Debugging Session: %s"),
              (RwEngineGetVersion() & 0xff00) >> 8,
              (RwEngineGetVersion() & 0xf0) >> 4,
              RwEngineGetVersion() & 0xf,
              __DATE__,
              timeBuffer);

    RWSRCGLOBAL(debugFunction) (rwDEBUGMESSAGE, RWSTRING(""));
    RWSRCGLOBAL(debugFunction) (rwDEBUGMESSAGE, buffer);

    /* Now write a row of dashes to underline the header */
    {
        int             i;
        RwChar         *b;

        b = buffer;
        i = rwstrlen(buffer);
        while (i--)
            *b++ = '-';
    }

    RWSRCGLOBAL(debugFunction) (rwDEBUGMESSAGE, buffer);
    RWSRCGLOBAL(debugFunction) (rwDEBUGMESSAGE, RWSTRING(""));
    return;
}

static RwChar   dberr[512];

RwChar         *
_rwdberrcommon(RwInt32 code,...)
{
    va_list         ap;

#if (0)
    RWFUNCTION(RWSTRING("_rwdberrcommon"));
#endif /* (0) */

    va_start(ap, code);

    code &= ~0x80000000;       /* remove MSB */
    rwstrcpy(dberr, rw_errcomcstr[code]);
    rwstrcat(dberr, RWSTRING(" : "));
    rwvsprintf(&dberr[rwstrlen(dberr)], rw_errcomstr[code], ap);
    va_end(ap);
    return dberr;
}

RwChar         *
_rwdbsprintf(const RwChar * format,...)
{
    va_list         ap;

#if (0)
    RWFUNCTION(RWSTRING("_rwdbsprintf"));
#endif /* (0) */

    va_start(ap, format);

    rwvsprintf(dberr, format, ap);
    va_end(ap);
    return dberr;
}

#endif /* RWDEBUG */

