/** @file psTrace.c
 *  \brief basic run-time trace facilities
 *  \ingroup LogTrace
 *
 *  This file will hold the code for procedures to insert
 *  trace messages into the code.
 *
 *  @author Robert Lupton, Princeton University
 *  @author GLG, MHPCC
 *
 *  @version $Revision: 1.49 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2005-06-08 23:40:45 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

/*****************************************************************************
    NOTES:
 In the SRD, higher trace levels correspond to a numerically lower trace
 value in the code.  This is a bit confusing.  For example, a high-level
 message might be something like "Begin Processing".  The module programmer
 might give that a numerically low trace level, such as 1, so then any
 non-zero trace level in that code component will display thatmessage.
 
 We build a tree of trace components.  Every node in the tree has a
 depth, which is it's distance from the root.  However, this is not
 not the same thing as a node's "level", which corresponds to the
 trace level of that node.
 
I think the following is the correct behavior, but not sure:
    PS_UNKNOWN_TRACE_LEVEL: We never set the level of a component to this
    value.  This value is only used when psTraceGetLevel is called with
    a bad component name, or if the component root is undefined, I think.
 
    PS_DEFAULT_TRACE_LEVEL: This should only be used when adding the
    intermediate components of a psS64 name.  Ie. the "B" in .A.B.C
 
 *****************************************************************************/

#ifndef PS_NO_TRACE

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "psMemory.h"
#include "psTrace.h"
#include "psString.h"
#include "psError.h"
#include "psLogMsg.h"

#include "psSysUtilsErrors.h"

static p_psComponent* cRoot = NULL; // The root of the trace component
static FILE *traceFP = NULL;        // File destination for messages.

static void componentFree(p_psComponent* comp);
static p_psComponent* componentAlloc(const char *name, psS32 level);

/*****************************************************************************
componentAlloc(): allocate memory for a new node, and initialize members.
 *****************************************************************************/
static p_psComponent* componentAlloc(const char *name, psS32 level)
{
    p_psComponent* comp = psAlloc(sizeof(p_psComponent));

    p_psMemSetPersistent(comp,true);
    psMemSetDeallocator(comp, (psFreeFcn) componentFree);
    comp->name = psStringCopy(name);
    p_psMemSetPersistent((psPtr)comp->name,true);
    comp->level = level;
    comp->n = 0;
    comp->p_psSpecified = false;
    comp->subcomp = NULL;
    return comp;
}

/*****************************************************************************
componentFree(): free the current node in the root tree, and all children
nodes as well.
 *****************************************************************************/
static void componentFree(p_psComponent* comp)
{
    if (comp == NULL) {
        return;
    }

    if (comp->subcomp != NULL) {
        for (psS32 i = 0; i < comp->n; i++) {
            p_psMemSetPersistent(comp->subcomp[i],false);
            psFree(comp->subcomp[i]);
        }
        p_psMemSetPersistent(comp->subcomp,false);
        psFree(comp->subcomp);
    }

    p_psMemSetPersistent((psPtr)comp->name,false);
    psFree((psPtr)comp->name);
}

/*****************************************************************************
initTrace(): simply initialize the component root tree.
*****************************************************************************/
static void initTrace(void)
{
    if (cRoot == NULL) {
        cRoot = componentAlloc(".", PS_DEFAULT_TRACE_LEVEL);
    }
}

/*****************************************************************************
Set all trace levels to zero.
 
XXX: Currently, no function calls this routine.
 *****************************************************************************/
void p_psTraceReset(p_psComponent* currentNode)
{
    psS32 i = 0;

    if (NULL == currentNode) {
        return;
    }

    currentNode->level = 0;
    for (i = 0; i < currentNode->n; i++) {
        if (NULL == currentNode->subcomp[i]) {
            psLogMsg("p_psTraceReset", PS_LOG_WARN,
                     PS_ERRORTEXT_psTrace_NULL_SUBCOMPONENT,
                     i, currentNode->name);
        } else {
            p_psTraceReset(currentNode->subcomp[i]);
        }
    }
    return;
}

/*****************************************************************************
Set all trace levels to zero.
 *****************************************************************************/
void psTraceReset()
{
    psFree(cRoot);
    cRoot = NULL;
}

/*****************************************************************************
componentAdd(): Adds the component named "addNodeName" to the root tree.
 *****************************************************************************/
static psBool componentAdd(const char *addNodeName, psS32 level)
{
    psS32 i = 0;                        // Loop index variable.
    char name[strlen(addNodeName) + 1]; // buffer for writeable copy.
    char *pname = name;
    char *firstComponent = NULL;        // first component of name
    p_psComponent* currentNode = cRoot;
    psS32 nodeExists = 0;

    // XXX: Verify that this is the correct behavior.
    if (strcmp("", addNodeName) == 0) {
        psError(PS_ERR_BAD_PARAMETER_NULL,true,
                PS_ERRORTEXT_psTrace_ADD_NULL_COMPONENT);
        return false;
    }

    // Is this the root node? If so, simply set level and return.
    if (strcmp(".", addNodeName) == 0) {
        cRoot->level = level;
        return true;
    }

    if (addNodeName[0] != '.') {
        psError(PS_ERR_BAD_PARAMETER_VALUE,true,
                PS_ERRORTEXT_psTrace_MALFORMED_COMPONENT_NAME,
                addNodeName);
        return false;
    }

    strcpy(name, addNodeName);
    pname = name+1;
    // Iterate through the components of addNodeName.  Strip off the first
    // component of the name, find that in the root tree, or add it if it
    // does not exist, then move to the next component in the name.

    while (pname != NULL) {
        firstComponent = pname;
        pname = strchr(firstComponent, '.');
        if (pname != NULL) {
            *pname = '\0';
            pname++;
        }
        nodeExists = 0;
        for (i = 0; i < currentNode->n; i++) {
            if (strcmp(currentNode->subcomp[i]->name, firstComponent) == 0) {
                currentNode = currentNode->subcomp[i];
                nodeExists = 1;
                if (pname == NULL) {
                    currentNode->level = level;
                }
            }
        }

        if (nodeExists == 0) {
            currentNode->subcomp = psRealloc(currentNode->subcomp,
                                             (currentNode->n + 1) * sizeof(p_psComponent* ));
            p_psMemSetPersistent(currentNode->subcomp,true);

            currentNode->n = (currentNode->n) + 1;

            if (pname == NULL) {
                // This is the final component to add.
                currentNode->subcomp[(currentNode->n) - 1] = componentAlloc(firstComponent, level);
            } else {
                // We are adding an intermediate component.  The trace level
                // is not defined.  An undefined trace level inherits the
                // trace level of it's parent.  However, we do not set that
                // specifically here since that would inheritance to be a
                // static, one-time, type of behavior.

                currentNode->subcomp[(currentNode->n) - 1] = componentAlloc(firstComponent, PS_DEFAULT_TRACE_LEVEL);
            }
            currentNode = currentNode->subcomp[(currentNode->n) - 1];
        }
    }

    return true;
}

/*****************************************************************************
    psSetTraceLevel(): add the component named "comp" to the component tree,
 if it is not already there, and set it's trace level to "level".
 
    NOTE: We modified this so that the user may omit the leading "," in a
    component name.  Since the code was already implemented assuming the "."
    was required, rather than change all that code, in this function, I
    simply add a leading "." to the component name if there is none.
 
    Input:
 comp
 level
    Output:
 none
    Returns:
 zero
*****************************************************************************/
psBool psTraceSetLevel(const char *comp,   // component of interest
                       psS32 level)  // desired trace level
{
    char *compName = NULL;

    // If the root component tree does not exist, then initialize it.
    if (cRoot == NULL) {
        initTrace();
    }

    if (traceFP == NULL) {
        traceFP = stdout;
    }

    // If the component name has no leading dot, then supply it.
    if (comp[0] != '.') {
        compName = (char *) psAlloc(10 + strlen(comp));
        strcpy(compName, ".");
        compName = strcat(compName, comp);
    } else {
        compName = (char *) comp;
    }

    // Add the new component to the component tree.
    if ( !componentAdd(compName, level) ) {
        psError(PS_ERR_UNKNOWN, false,
                PS_ERRORTEXT_psTrace_FAILED_TO_ADD_COMPONENT,
                level,
                compName);

        if (comp[0] != '.') {
            psFree(compName);
        }
        return false;
    }

    if (comp[0] != '.') {
        psFree(compName);
    }

    return true;
}

/*****************************************************************************
    doGetTraceLevel()
 This function recursively searches the root component tree for the
 component named "name", which is supplied by a parameter.  If it
 finds that component, it returns the level of that component.
 Otherwise, it returns ???.
 
    NOTE: We modified this so that the user may omit the leading "," in a
    component name.  Since the code was already implemented assuming the "."
    was required, rather than change all that code, in this function, I
    simply add a leading "." to the component name if there is none.
 
    Inputs:
 name:
    Outputs:
 none
    Returns:
 The trace level of the "name" component.
 *****************************************************************************/
static psS32 doGetTraceLevel(const char *aname)
{
    char name[strlen(aname) + 1];       // need a writeable copy: for strsep()
    char *pname = name;
    char *firstComponent = NULL;        // first component of name
    p_psComponent* currentNode = cRoot;
    psS32 i = 0;
    psS32 defaultLevel = 0;

    if (NULL == currentNode) {
        return (PS_UNKNOWN_TRACE_LEVEL);
    }

    if (strcmp(".", aname) == 0) {
        return (cRoot->level);
    }

    if (aname[0] != '.') {
        return (PS_UNKNOWN_TRACE_LEVEL);
    }

    defaultLevel = cRoot->level;
    strcpy(name, aname);
    pname = name+1;
    while (pname != NULL) {
        firstComponent = pname;
        pname = strchr(firstComponent, '.');
        if (pname != NULL) {
            *pname = '\0';
            pname++;
        }
        for (i = 0; i < currentNode->n; i++) {
            if (NULL == currentNode->subcomp[i]) {
                psLogMsg("p_psTraceReset", PS_LOG_WARN,
                         PS_ERRORTEXT_psTrace_NULL_SUBCOMPONENT,
                         i, currentNode->name);
            }

            if (strcmp(currentNode->subcomp[i]->name, firstComponent) == 0) {
                currentNode = currentNode->subcomp[i];
                // For level inheritance purpose, we save the level of this
                // component if it is not DEFAULT.
                if (currentNode->level != PS_DEFAULT_TRACE_LEVEL) {
                    defaultLevel = currentNode->level;
                }
                // Determine if this is the last component:
                if (pname == NULL) {
                    if (currentNode->level != PS_DEFAULT_TRACE_LEVEL) {
                        return (currentNode->level);
                    } else {
                        return(defaultLevel);
                    }
                }
            }
        }
    }
    return(defaultLevel);
}

/*****************************************************************************
    psTraceLevelGet()
 Return a trace level of "name" in the root component tree.  If the
 exact string of components in "name" does not exist in the root
 tree, we return the deepest level of the match.
    Input:
 name
    Output:
 none
    Return:
 The level of "name" in the root component tree.
 *****************************************************************************/
psS32 psTraceGetLevel(const char *name)
{
    char *compName = NULL;
    psS32 traceLevel;

    if (cRoot == NULL) {
        return (PS_UNKNOWN_TRACE_LEVEL);
    }

    // If the component name has no leading dot, then supply it.
    if (name[0] != '.') {
        compName = (char *) psAlloc(10 + strlen(name));
        strcpy(compName, ".");
        compName = strcat(compName, name);
        traceLevel = doGetTraceLevel(compName);
        psFree(compName);
    } else {
        // Search the component root tree, determine the trace level.
        traceLevel = doGetTraceLevel(name);
    }

    // XXX: The default trace level is currently set at -1, which is not a
    // valid trace level.  This is convenient in determining whether or not
    // a component should inherit the trace level from parent nodes.  However,
    // it's not clear that -1 should ever be returned by this function.
    // The SDR is unclear on this point and we should probably request IfA
    // comment.
    if (traceLevel == PS_DEFAULT_TRACE_LEVEL) {
        traceLevel = PS_THE_OTHER_DEFAULT_TRACE_LEVEL;
    }

    return(traceLevel);
}

/*****************************************************************************
    doPrintTraceLevels()
 This function recursively searches the component tree supplied by the
 parameter "comp" and prints the name and level of each component.
    Inputs:
 comp: a node in the component tree.
 level: the level of that node
    Outputs:
 none
    Returns:
 null
 *****************************************************************************/
static void doPrintTraceLevels(const p_psComponent* comp,
                               psS32 depth,
                               psS32 defLevel)
{
    psS32 i = 0;

    if (traceFP == NULL) {
        traceFP = stdout;
    }

    if (comp->name[0] == '\0') {
        return;
    } else {
        if (comp->level == PS_DEFAULT_TRACE_LEVEL) {
            fprintf(traceFP,"%*s%-*s %d\n", depth, "", 20 - depth, comp->name, defLevel);
        } else {
            fprintf(traceFP, "%*s%-*s %d\n", depth, "", 20 - depth, comp->name, comp->level);
        }
    }

    for (i = 0; i < comp->n; i++) {
        if (comp->level == PS_DEFAULT_TRACE_LEVEL) {
            doPrintTraceLevels(comp->subcomp[i], depth + 1, defLevel);
        } else {
            doPrintTraceLevels(comp->subcomp[i], depth + 1, comp->level);
        }
    }
}


/*****************************************************************************
psPrintTraceLevels(): Simply print all the trace levels in the trace level
component tree.
Inputs:
 none
Outputs:
 none
Returns:
 null
*****************************************************************************/
void psTracePrintLevels(void)
{
    if (cRoot == NULL) {
        return;
    }

    doPrintTraceLevels(cRoot, 0, PS_THE_OTHER_DEFAULT_TRACE_LEVEL);
}

/*****************************************************************************
p_psTrace(): we display the trace message to standard output if the trace
level of that message, supplied by the parameter "level" is higher than the
trace level that is currently associated with the component named by the
parameter "comp".
Input:
 comp
 level
 ...  a printf-style output string.
Output:
 none
Return:
 null
 *****************************************************************************/
void p_psTrace(const char *comp,        // component being traced
               psS32 level,       // desired trace level
               ...)             // arguments
{
    char *fmt = NULL;
    va_list ap;
    psS32 i = 0;

    if (traceFP == NULL) {
        traceFP = stdout;
    }

    if (NULL == comp) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psTrace_NULL_TRACETREE,
                __func__);
        return;
    }
    // Only display this message if it's trace level is less than the level
    // of it's associatedcomponent.
    if (level <= psTraceGetLevel(comp)) {
        va_start(ap, level);

        // The following functions get the variable list of parameters with
        // which this function was called, and print them to the standard
        // output.
        fmt = va_arg(ap, char *);

        // We indent each message one space for each level of the message.
        for (i = 0; i < level; i++) {
            fprintf(traceFP, " ");
        }
        vfprintf(traceFP, fmt, ap);
        va_end(ap);
    }
    fflush(traceFP);
}

// XXX EAM : I've added code to close the old traceFP (safely)
void psTraceSetDestination(FILE * fp)
{

    bool special;

    // XXX EAM perhaps return an error?
    if (fp == NULL) {
        return;
    }

    // cannot close traceFP if one of the special FILE ptrs
    special  = (traceFP == NULL);
    special |= (traceFP == stdin);
    special |= (traceFP == stdout);
    special |= (traceFP == stderr);

    if (!special) {
        fclose (traceFP);
    }
    traceFP = fp;
}

FILE *psTraceGetDestination()
{
    if (traceFP == NULL) {
        traceFP = stdout;
    }
    return traceFP;
}

#endif // #ifndef PS_NO_TRACE
